1<?php
2/**
3 * phpfreechat.class.php
4 *
5 * Copyright © 2006 Stephane Gully <stephane.gully@gmail.com>
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, 51 Franklin St, Fifth Floor,
20 * Boston, MA  02110-1301  USA
21 */
22
23require_once dirname(__FILE__).'/pfccommand.class.php';
24require_once dirname(__FILE__).'/pfcglobalconfig.class.php';
25require_once dirname(__FILE__).'/pfcuserconfig.class.php';
26require_once dirname(__FILE__).'/pfctemplate.class.php';
27require_once dirname(__FILE__).'/../lib/utf8/utf8_substr.php';
28require_once dirname(__FILE__).'/pfcresponse.class.php';
29
30/**
31 * phpFreeChat is the entry point for developpers
32 *
33 * @example ../demo/demo1_simple.php
34 * @author Stephane Gully <stephane.gully@gmail.com>
35 */
36class phpFreeChat
37{
38  function phpFreeChat( &$params )
39  {
40    if (!is_array($params))
41      die('phpFreeChat parameters must be an array');
42
43    // initialize the global config object
44    $c =& pfcGlobalConfig::Instance( $params );
45
46    // need to initiate the user config object here because it uses sessions
47    $u =& pfcUserConfig::Instance();
48
49    // this code is used to handle the AJAX call and build the response
50    if (isset($_REQUEST['pfc_ajax']))
51    {
52      $function = isset($_REQUEST['f']) ? $_REQUEST['f'] : '';
53      $cmd      = isset($_REQUEST['cmd']) ? stripslashes($_REQUEST['cmd']) : '';
54      $r = null;
55      if ($function && method_exists($this,$function))
56      {
57        require_once dirname(__FILE__).'/pfcresponse.class.php';
58        $r =& $this->$function($cmd);
59      }
60      echo $r->getOutput();
61      die();
62    }
63  }
64
65  /**
66   * depreciated
67   */
68  function printJavaScript( $return = false )
69  {
70    $output = '';
71    if ($return)
72      return $output;
73    else
74      echo $output;
75  }
76
77  /**
78   * depreciated
79   */
80  function printStyle( $return = false )
81  {
82    $output = '';
83    if($return)
84      return $output;
85    else
86      echo $output;
87  }
88
89  /**
90   * printChat must be called somewhere in the page
91   * it inserts necessary html which will receive chat's data
92   * usage:
93   * <code>
94   *   <?php $chat->printChat(); ?>
95   * </code>
96   */
97  function printChat( $return = false )
98  {
99    $c =& pfcGlobalConfig::Instance();
100    $u =& pfcUserConfig::Instance();
101
102    $output = '';
103
104    if (count($c->getErrors()) > 0)
105    {
106      $output .= "<p>phpFreeChat cannot be initialized, please correct these errors:</p><ul>";
107      foreach( $c->getErrors() as $e ) $output .= "<li>".$e."</li>"; $output .= "</ul>";
108    }
109    else
110    {
111      pfcI18N::SwitchOutputEncoding($c->output_encoding);
112
113      $path = $c->getFilePathFromTheme('chat.js.tpl.php');
114      $t = new pfcTemplate($path);
115      $t->assignObject($u,"u");
116      $t->assignObject($c,"c");
117      $output .= $t->getOutput();
118
119      pfcI18N::SwitchOutputEncoding();
120    }
121
122    if($return)
123      return $output;
124    else
125      echo $output;
126  }
127
128  /**
129   * Encode special caracteres and remove extra slashes
130   */
131  function FilterSpecialChar($str)
132  {
133    return htmlspecialchars($str, ENT_NOQUOTES);
134  }
135
136  /**
137   * Just check the nicknames doesn't start with spaces and is not longer than the max_nick_len
138   */
139  function FilterNickname($nickname)
140  {
141    $c =& pfcGlobalConfig::Instance();
142    //$nickname = str_replace("\\", "", $nickname); // '\' is a forbidden charactere for nicknames
143    $nickname = trim($nickname);
144    $nickname = utf8_substr($nickname, 0, $c->max_nick_len);
145    return $nickname;
146  }
147
148  /**
149   * search/replace smileys
150   */
151  function FilterSmiley($msg)
152  {
153    $c =& pfcGlobalConfig::Instance();
154    // build a preg_replace array
155    $search = array();
156    $replace = array();
157    $query = "/(";
158    foreach($c->smileys as $s_file => $s_strs)
159    {
160      foreach ($s_strs as $s_str)
161      {
162        $s_str = stripslashes($s_str); /* the :'( smileys needs this filter */
163      	$query .= preg_quote($s_str,'/')."|";
164      	$search[] = "/".preg_quote($s_str,'/')."/";
165      	$replace[] = '<img src="'.$s_file.'" alt="'.$s_str.'" title="'.$s_str.'" />';
166      }
167    }
168    $query = substr($query, 0, strlen($query)-1);
169    $query .= ")/i";
170
171    $split_words = preg_split($query, $msg, -1, PREG_SPLIT_DELIM_CAPTURE);
172    $msg = "";
173    foreach($split_words as $word)
174      $msg .= preg_replace($search, $replace, $word);
175    return $msg;
176  }
177
178
179  /**
180   * Filter messages before they are sent to container
181   */
182  function PreFilterMsg($msg)
183  {
184    $c =& pfcGlobalConfig::Instance();
185    if (preg_match("/^\[/i",$msg))
186      // add 25 characteres if the message starts with [ : means there is a bbcode
187      $msg = utf8_substr($msg, 0, $c->max_text_len+25);
188    else
189      $msg = utf8_substr($msg, 0, $c->max_text_len);
190    $msg = phpFreeChat::FilterSpecialChar($msg);
191
192    //    $msg = phpFreeChat::FilterSmiley($msg);
193
194    /*    if ($msg[0] == "\n") $msg = substr($msg, 1); */ // delete the first \n generated by FF
195    /* if (strpos($msg,"\n") > 0) $msg  = "<br/>".$msg;
196    $msg = str_replace("\r\n", "<br/>", $msg);
197    $msg = str_replace("\n", "<br/>", $msg);
198    $msg = str_replace("\t", "    ", $msg);*/
199    //$msg = str_replace("  ", "&nbsp;&nbsp;", $msg);
200    //    $msg = preg_replace('/(http\:\/\/[^\s]*)/i',  "<a href=\"$1\">$1</a>", $msg );
201    return $msg;
202  }
203
204  /**
205   * Filter messages when they are recived from container
206   */
207  function PostFilterMsg($msg)
208  {
209    //$c =& pfcGlobalConfig::Instance();
210    //    $msg = preg_replace('/('.preg_quote($c->nick,'/').')/i', "<strong>$1</strong>", $msg );
211    $msg = preg_replace('/\n/i', "", $msg );
212    return $msg;
213  }
214
215  function &handleRequest($request)
216  {
217    $c =& pfcGlobalConfig::Instance();
218    $u =& pfcUserConfig::Instance();
219
220    if ($c->debug) ob_start(); // capture output
221
222    $xml_reponse = new pfcResponse();
223
224    // check the command
225    $cmdstr      = "";
226    $cmdname     = "";
227    $clientid    = "";
228    $recipient   = "";
229    $recipientid = "";
230    $param       = "";
231    $sender      = "";
232
233    $res = pfcCommand::ParseCommand($request);
234
235    $cmdstr      = isset($res['cmdstr']) ? $res['cmdstr'] : $request;
236    $cmdname     = strtolower(isset($res['cmdname']) ? $res['cmdname'] : '');
237    $clientid    = isset($res['params'][0]) ? $res['params'][0] : '';
238    $recipientid = isset($res['params'][1]) ? $res['params'][1] : "";
239    $params      = array_slice(is_array($res['params']) ? $res['params'] : array() ,2);
240    $param       = implode(" ",$params); // to keep compatibility (will be removed)
241    $sender      = $u->getNickname();
242
243    // translate the recipientid to the channel name
244    if (isset($u->channels[$recipientid]))
245    {
246      $recipient = $u->channels[$recipientid]["recipient"];
247    }
248    if (isset($u->privmsg[$recipientid]))
249    {
250      $recipient = $u->privmsg[$recipientid]["recipient"];
251
252
253      // @todo: move this code in a proxy
254      if ($cmdname != "update" &&
255          $cmdname != "leave" &&  // do not open the pv tab when other user close the tab
256          $cmdname != "quit" &&
257          $cmdname != "privmsg2")
258      {
259        // alert the other from the new pv
260        // (warn other user that someone talk to him)
261
262        $ct =& pfcContainer::Instance();
263        $nickidtopv = $u->privmsg[$recipientid]["pvnickid"];
264        $cmdstr = 'privmsg2';
265        $cmdp = array();
266        $cmdp['param']    = $u->nickid;//$sender;
267        $cmdp['params'][] = $u->nickid;//$sender;
268        pfcCommand::AppendCmdToPlay($nickidtopv, $cmdstr, $cmdp);
269      }
270
271    }
272
273
274    $cmdp = array();
275    $cmdp["clientid"]    = $clientid;
276    $cmdp["sender"]      = $sender;
277    $cmdp["recipient"]   = $recipient;
278    $cmdp["recipientid"] = $recipientid;
279    // before playing the wanted command
280    // play the found commands into the meta 'cmdtoplay'
281    pfcCommand::RunPendingCmdToPlay($u->nickid, $cmdp, $xml_reponse);
282    // play the wanted command
283    $cmd =& pfcCommand::Factory($cmdname);
284    $cmdp["param"]       = $param;
285    $cmdp["params"]      = $params;
286    if ($cmd != NULL)
287    {
288      // call the command
289      if ($c->debug)
290      	$cmd->run($xml_reponse, $cmdp);
291      else
292      	@$cmd->run($xml_reponse, $cmdp);
293    }
294    else
295    {
296      $cmd =& pfcCommand::Factory("error");
297      $cmdp = array();
298      $cmdp["clientid"]    = $clientid;
299      $cmdp["param"]       = _pfc("Unknown command [%s]",stripslashes("/".$cmdname." ".$param));
300      $cmdp["sender"]      = $sender;
301      $cmdp["recipient"]   = $recipient;
302      $cmdp["recipientid"] = $recipientid;
303      if ($c->debug)
304        $cmd->run($xml_reponse, $cmdp);
305      else
306        @$cmd->run($xml_reponse, $cmdp);
307    }
308
309    // do not update twice
310    // do not update when the user just quit
311    if ($cmdname != "update" &&
312      	$cmdname != "quit" &&
313      	$u->nickid != '')
314    {
315      // force an update just after a command is sent
316      // thus the message user just poster is really fastly displayed
317      $cmd =& pfcCommand::Factory("update");
318      $cmdp = array();
319      $cmdp["clientid"]    = $clientid;
320      $cmdp["param"]       = $param;
321      $cmdp["sender"]      = $sender;
322      $cmdp["recipient"]   = $recipient;
323      $cmdp["recipientid"] = $recipientid;
324      if ($c->debug)
325      	$cmd->run($xml_reponse, $cmdp);
326      else
327      	@$cmd->run($xml_reponse, $cmdp);
328    }
329
330    if ($c->debug)
331    {
332      // capture echoed content
333      // if a content not empty is captured it is a php error in the code
334      $data = ob_get_contents();
335      if ($data != "")
336      {
337        // todo : display the $data somewhere to warn the user
338      }
339      ob_end_clean();
340    }
341
342    // do nothing else if the xml response is empty in order to save bandwidth
343    if ($xml_reponse->getCommandCount() == 0) die();
344
345    return $xml_reponse;
346  }
347
348
349  function &loadStyles($theme = 'default', &$xml_reponse)
350  {
351    if ($xml_reponse == null) $xml_reponse = new pfcResponse();
352
353    $c =& pfcGlobalConfig::Instance();
354
355    // do not overload the theme parameter as long as
356    // the ajax request do not give the correct one
357    //    $c->theme = $theme;
358
359    $u =& pfcUserConfig::Instance();
360
361    $js = '';//file_get_contents(dirname(__FILE__).'/client/createstylerule.js');
362    $js .= 'var c = $H();';
363    $path = $c->getFilePathFromTheme('style.css.php');
364    require_once dirname(__FILE__).'/../lib/ctype/ctype.php'; // to keep compatibility for php without ctype support
365    require_once dirname(__FILE__).'/../lib/csstidy-1.2/class.csstidy.php';
366    $css = new csstidy();
367    $css->set_cfg('preserve_css',false);
368
369
370    $css_code = '';
371    $t = new pfcTemplate();
372    $t->assignObject($u,"u");
373    $t->assignObject($c,"c");
374    if (!$c->isDefaultFile('style.css.php'))
375    {
376      $t->setTemplate($c->theme_default_path.'/default/style.css.php');
377      $css_code .= $t->getOutput();
378    }
379    $t->setTemplate($c->getFilePathFromTheme('style.css.php'));
380    $css_code .= $t->getOutput();
381
382    $css->parse($css_code);
383    foreach($css->css as $k => $v)
384    {
385      foreach($v as $k2 => $v2)
386      {
387        $rules = '';
388        foreach($v2 as $k3 => $v3)
389          $rules .= $k3.':'.$v3.';';
390        $js .= "c.set('".$k2."', '".str_replace("\n", "", $rules)."');\n";
391      }
392    }
393    $js .= "var pfccss = new pfcCSS(); var k = c.keys(); c.each(function (a) { pfccss.applyRule(a[0],a[1]); });";
394    $xml_reponse->script($js);
395
396    return $xml_reponse;
397  }
398
399  function &loadScripts($theme, &$xml_reponse)
400  {
401    if ($xml_reponse == null) $xml_reponse = new pfcResponse();
402
403    $c =& pfcGlobalConfig::Instance();
404
405    $js = '';
406
407    // load customize.js.php
408    $path = $c->getFilePathFromTheme('customize.js.php');
409    $t = new pfcTemplate($path);
410    $t->assignObject($c,"c");
411    $js .= $t->getOutput();
412
413    // load translations
414    require_once dirname(__FILE__).'/pfcjson.class.php';
415    $json = new pfcJSON();
416
417    $labels_to_load =
418      array( "Do you really want to leave this room ?", // _pfc
419             "Are you sure you want to close this tab ?", // _pfc
420             "Hide nickname marker", // _pfc
421             "Show nickname marker", // _pfc
422             "Hide dates and hours", // _pfc
423             "Show dates and hours", // _pfc
424             "Disconnect", // _pfc
425             "Connect", // _pfc
426             "Magnify", // _pfc
427             "Cut down", // _pfc
428             "Hide smiley box", // _pfc
429             "Show smiley box", // _pfc
430             "Hide online users box", // _pfc
431             "Show online users box", // _pfc
432             "Please enter your nickname", // _pfc
433             "Private message", // _pfc
434             "Close this tab", // _pfc
435             "Enter your message here", // _pfc
436             "Enter your nickname here", // _pfc
437             "Bold", // _pfc
438             "Italics", // _pfc
439             "Underline", // _pfc
440             "Delete", // _pfc
441             "Mail", // _pfc
442             "Color", // _pfc
443             "PHP FREE CHAT [powered by phpFreeChat-%s]", // _pfc
444             "Enter the text to format", // _pfc
445             "Configuration has been rehashed", // _pfc
446             "A problem occurs during rehash", // _pfc
447             "Chosen nickname is already used", // _pfc
448             "phpfreechat current version is %s", // _pfc
449             "Maximum number of joined channels has been reached", // _pfc
450             "Maximum number of private chat has been reached", // _pfc
451             "Click here to send your message", // _pfc
452             "Send", // _pfc
453             "You are not allowed to speak to yourself", // _pfc
454             "Close", // _pfc
455             "Chosen nickname is not allowed", // _pfc
456             "Enable sound notifications", // _pfc
457             "Disable sound notifications", // _pfc
458             "Input Required", // _pfc
459             "OK", // _pfc
460             "Cancel", // _pfc
461             "You are trying to speak to a unknown (or not connected) user", // _pfc
462             );
463    foreach($labels_to_load as $l)
464    {
465      $js .= "pfc.res.setLabel(".$json->encode($l).",".$json->encode(_pfc2($l)).");\n";
466    }
467
468    // load ressources
469    $fileurl_to_load =
470      array( 'images/ch.gif',
471             'images/pv.gif',
472             'images/tab_remove.gif',
473             'images/ch-active.gif',
474             'images/pv-active.gif',
475             'images/user.gif',
476             'images/user-me.gif',
477             'images/user_female.gif',
478             'images/user_female-me.gif',
479             'images/color-on.gif',
480             'images/color-off.gif',
481             'images/clock-on.gif',
482             'images/clock-off.gif',
483             'images/logout.gif',
484             'images/login.gif',
485             'images/maximize.gif',
486             'images/minimize.gif',
487             'images/smiley-on.gif',
488             'images/smiley-off.gif',
489             'images/online-on.gif',
490             'images/online-off.gif',
491             'images/bt_strong.gif',
492             'images/bt_em.gif',
493             'images/bt_ins.gif',
494             'images/bt_del.gif',
495             'images/bt_mail.gif',
496             'images/bt_color.gif',
497             'images/color_transparent.gif',
498             'images/close-whoisbox.gif',
499             'images/openpv.gif',
500             'images/user-admin.gif',
501             'images/sound-on.gif',
502             'images/sound-off.gif',
503             'sound.swf',
504             );
505
506    foreach($fileurl_to_load as $f)
507    {
508      $js .= "pfc.res.setFileUrl(".$json->encode($f).",\"".$c->getFileUrlFromTheme($f)."\");\n";
509    }
510
511    foreach($c->smileys as $s_file => $s_str) {
512      for($j = 0; $j<count($s_str) ; $j++) {
513        $js .= "pfc.res.setSmiley(".$json->encode($s_str[$j]).",\"".$c->getFileUrlFromTheme($s_file)."\");\n";
514      }
515    }
516
517    $js .= '
518pfc.gui.loadSmileyBox();
519pfc.gui.loadBBCodeColorList();
520pfc.connectListener();
521pfc.refreshGUI();
522if (pfc_connect_at_startup) pfc.connect_disconnect();
523';
524
525    $xml_reponse->script($js);
526    return $xml_reponse;
527  }
528
529
530  function loadInterface($theme = 'default', &$xml_reponse)
531  {
532    if ($xml_reponse == null) $xml_reponse = new pfcResponse();
533
534    $c =& pfcGlobalConfig::Instance();
535
536    // do not overload the theme parameter as long as
537    // the ajax request do not give the correct one
538    //    $c->theme = $theme;
539
540    $u =& pfcUserConfig::Instance();
541
542    $html = '';
543
544    //    pfcI18N::SwitchOutputEncoding($c->output_encoding);
545
546    $path = $c->getFilePathFromTheme('chat.html.tpl.php');
547    $t = new pfcTemplate($path);
548    $t->assignObject($u,"u");
549    $t->assignObject($c,"c");
550    $html .= $t->getOutput();
551
552    //    pfcI18N::SwitchOutputEncoding();
553
554    $xml_reponse->remove('pfc_loader'); // to hide the loading box
555    $xml_reponse->update('pfc_container', $html);
556
557    return $xml_reponse;
558  }
559
560  function &loadChat($theme = 'default')
561  {
562    $xml_reponse = new pfcResponse();
563
564    $this->loadInterface($theme,$xml_reponse);
565    $this->loadStyles($theme,$xml_reponse);
566    $this->loadScripts($theme,$xml_reponse);
567
568    return $xml_reponse;
569  }
570
571}
572
573?>
574