1<?php
2/**
3 * pfccommand.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 */
22require_once dirname(__FILE__)."/pfci18n.class.php";
23require_once dirname(__FILE__)."/pfcuserconfig.class.php";
24
25/**
26 * pfcCommand is an abstract class (interface) which must be inherited by each concrete commands
27 * Commands examples : /nick /me /update ...
28 *
29 * @example ../demo/demo27_customized_command.php
30 * @author Stephane Gully <stephane.gully@gmail.com>
31 */
32class pfcCommand
33{
34  /**
35   * Command name (lowercase)
36   */
37  var $name = '';
38
39  /**
40   * Contains the command syntaxe (how to use the command)
41   */
42  var $usage = '';
43
44  /**
45   * Not used for now
46   */
47  var $desc = '';
48  var $help = '';
49
50  /**
51   * Used to instanciate a command
52   * $tag is the command name : "nick", "me", "update" ...
53   */
54  function &Factory($name)
55  {
56    $c =& pfcGlobalConfig::Instance();
57
58    // instanciate the real command
59    $cmd           = NULL;
60    $cmd_name      = $name;
61    $cmd_classname = "pfcCommand_".$name;
62
63    $cmd_filename  = $c->cmd_path_default.'/'.$cmd_name.'.class.php';
64    if (file_exists($cmd_filename)) require_once($cmd_filename);
65    $cmd_filename  = $c->cmd_path.'/'.$cmd_name.'.class.php';
66    if (file_exists($cmd_filename)) require_once($cmd_filename);
67
68    if (!class_exists($cmd_classname)) { $tmp = NULL; return $tmp; }
69
70    $firstproxy = new $cmd_classname;
71    $firstproxy->name = $cmd_name;
72
73    // instanciate the proxies chaine
74    $proxies = array();
75    for($i = count($c->proxies)-1; $i >= 0; $i--)
76    {
77      $proxy_name      = $c->proxies[$i];
78      $proxy_classname = "pfcProxyCommand_" . $proxy_name;
79
80      // try to include the proxy class file from the default path or from the customized path
81      $proxy_filename  = $c->proxies_path_default.'/'.$proxy_name.".class.php";
82      if (file_exists($proxy_filename)) require_once($proxy_filename);
83      $proxy_filename  = $c->proxies_path.'/'.$proxy_name.".class.php";
84      if (file_exists($proxy_filename)) require_once($proxy_filename);
85
86      if (!class_exists($proxy_classname))
87        return $firstproxy;
88
89      // instanciate the proxy
90      $proxies[$i] = new $proxy_classname;
91      $proxies[$i]->name      = $cmd_name;
92      $proxies[$i]->proxyname = $proxy_name;
93      $proxies[$i]->linkTo($firstproxy);
94      $firstproxy =& $proxies[$i];
95    }
96
97    /*
98    $tmp = '';
99    $cur = $firstproxy;
100    while($cur)
101    {
102      $tmp .= (isset($cur->proxyname)?$cur->proxyname:$cur->name).'|';
103      $cur = $cur->next;
104    }
105    $tmp .= var_export($firstproxy,true);
106    file_put_contents('/tmp/debug1',$tmp);
107*/
108
109    // return the proxy, not the command (the proxy will forward the request to the real command)
110    return $firstproxy;
111  }
112
113  /**
114   * Constructor
115   * @private
116   */
117  function pfcCommand()
118  {
119  }
120
121  /**
122   * Virtual methode which must be implemented by concrete commands
123   * It is called by the phpFreeChat::HandleRequest function to execute the wanted command
124   */
125  function run(&$xml_reponse, $p)
126  {
127    die(_pfc("%s must be implemented", get_class($this)."::".__FUNCTION__));
128  }
129
130  /**
131   * Force whois reloading
132   */
133  function forceWhoisReload($nickid)
134  {
135    $c =& pfcGlobalConfig::Instance();
136    $u =& pfcUserConfig::Instance();
137    $ct =& pfcContainer::Instance();
138
139    // list the users in the same channel as $nickid
140    $channels = $ct->getMeta("nickid-to-channelid", $nickid);
141    $channels = $channels['value'];
142    $channels = array_diff($channels, array('SERVER'));
143    $otherids = array();
144    foreach($channels as $chan)
145    {
146      $ret = $ct->getOnlineNick($ct->decode($chan));
147      $otherids = array_merge($otherids, $ret['nickid']);
148    }
149
150    // alert them that $nickid user info just changed
151    foreach($otherids as $otherid)
152    {
153      $cmdstr = 'whois2';
154      $cmdp = array();
155      $cmdp['params'] = array($nickid);
156      pfcCommand::AppendCmdToPlay($otherid, $cmdstr, $cmdp);
157
158      /*
159      $cmdtoplay = $ct->getUserMeta($otherid, 'cmdtoplay');
160      $cmdtoplay = ($cmdtoplay == NULL) ? array() : unserialize($cmdtoplay);
161      $cmdtmp = array("whois2",    // cmdname
162                      $nicktorewhois,   // param
163                      NULL,       // sender
164                      NULL,       // recipient
165                      NULL,       // recipientid
166                      );
167      if (!in_array($cmdtmp, $cmdtoplay))
168      {
169        $cmdtoplay[] = $cmdtmp;
170        $ct->setUserMeta($otherid, 'cmdtoplay', serialize($cmdtoplay));
171      }
172      */
173    }
174  }
175
176  /**
177   * Add command to be played onto command stack
178   * @param $nickid is the user that entered the command
179   * @param $cmdstr is the command
180   * @param $cmdp is the command's parameters
181   * @return false if $nickid is blank, true for all other values of $nickid
182   */
183  function AppendCmdToPlay($nickid, $cmdstr, $cmdp)
184  {
185    $c =& pfcGlobalConfig::Instance();
186    $u =& pfcUserConfig::Instance();
187
188    $ct =& pfcContainer::Instance();
189
190    // check for empty nickid
191    if ($nickid == "") return false;
192
193    // get new command id
194    $cmdtoplay_id = $ct->incMeta("nickid-to-cmdtoplayid", $nickid, 'cmdtoplayid');
195    if (count($cmdtoplay_id["value"]) == 0)
196      $cmdtoplay_id = 0;
197    else
198      $cmdtoplay_id = $cmdtoplay_id["value"][0];
199
200    // create command array
201    $cmdtoplay = array();
202    $cmdtoplay['cmdstr'] = $cmdstr;
203    $cmdtoplay['params'] = $cmdp;
204
205    // store command to play
206    $ct->setCmdMeta($nickid, $cmdtoplay_id, serialize($cmdtoplay));
207
208    return true;
209  }
210
211
212  /**
213   * Run all commands to be played for a user
214   * @param $nickid is the user that entered the command
215   * @param $context
216   * @param $xml_reponse
217   */
218  function RunPendingCmdToPlay($nickid, $context, &$xml_reponse)
219  {
220    $c =& pfcGlobalConfig::Instance();
221    $u =& pfcUserConfig::Instance();
222    $ct =& pfcContainer::Instance();
223
224    // Get all queued commands to be played
225    $cmdtoplay_ids = $ct->getCmdMeta($nickid);
226    // process each command and parse content
227    foreach ( $cmdtoplay_ids as $cid )
228    {
229      // take a command from the list
230      $cmdtoplay = $ct->getCmdMeta($nickid, $cid);
231      $cmdtoplay = ($cmdtoplay == NULL || count($cmdtoplay) == 0) ? array() : unserialize($cmdtoplay[0]);
232
233      // play the command
234      $cmd =& pfcCommand::Factory($cmdtoplay['cmdstr']);
235      $cmdp = $cmdtoplay['params'];
236      if (!isset($cmdp['param']))       $cmdp['param'] = '';
237      if (!isset($cmdp['sender']))      $cmdp['sender'] = $context['sender'];
238      if (!isset($cmdp['recipient']))   $cmdp['recipient']   = $context['recipient'];
239      if (!isset($cmdp['recipientid'])) $cmdp['recipientid'] = $context['recipientid'];
240      $cmdp['clientid']  = $context['clientid']; // the clientid must be the current user one
241      $cmdp['cmdtoplay'] = true; // used to run some specials actions in the command (ex:  if the cmdtoplay is a 'leave' command, then show an alert to the kicked or banished user)
242      if ($c->debug)
243        $cmd->run($xml_reponse, $cmdp);
244      else
245        @$cmd->run($xml_reponse, $cmdp);
246
247      // delete command when complete
248      $ct->rmMeta("nickid-to-cmdtoplay", $nickid, $cid);
249    }
250  }
251
252
253  function trace(&$xml_reponse, $msg, $data = NULL)
254  {
255    if ($data != NULL)
256    {
257      require_once dirname(__FILE__).'/pfcjson.class.php';
258      $json = new pfcJSON();
259      $js = $json->encode($data);
260      $xml_reponse->script("trace('".$msg." -> ".$js."');");
261    }
262    else
263      $xml_reponse->script("trace('".$msg."');");
264
265  }
266
267  function ParseCommand($cmd_str, $one_parameter = false)
268  {
269    $pattern_quote   = '/([^\\\]|^)"([^"]+[^\\\])"/';
270    $pattern_quote   = '/"([^"]+)"/';
271    $pattern_noquote = '/([^"\s]+)/';
272    $pattern_command = '/^\/([a-z0-9]+)\s*([a-z0-9]+)\s*([a-z0-9]+)\s*(.*)/';
273    $result = array();
274
275    // parse the command name (ex: '/invite')
276    if (preg_match($pattern_command, $cmd_str, $res))
277    {
278      $cmd         = $res[1];
279      $clientid    = $res[2];
280      $recipientid = $res[3];
281      $params_str  = $res[4];
282
283      // don't parse multiple parameters for special commands with only one parameter
284      // this make possible to send double quotes (") in these commands
285      if ($one_parameter || $cmd == 'send' || $cmd == 'notice' || $cmd == 'me')
286      {
287        $result['cmdstr']  = $cmd_str;
288        $result['cmdname'] = $cmd;
289        $result['params']  = array($clientid, $recipientid, $params_str);
290        return $result;
291      }
292
293
294      // parse the quotted parameters (ex: '/invite "nickname with spaces"')
295      preg_match_all($pattern_quote,$params_str,$res1,PREG_OFFSET_CAPTURE);
296      $params_res = $res1[1];
297      // split the parameters string
298      $nospaces = preg_split($pattern_quote,$params_str,-1,PREG_SPLIT_OFFSET_CAPTURE|PREG_SPLIT_NO_EMPTY);
299      foreach($nospaces as $p)
300      {
301        // parse the splited blocks with unquotted parameter pattern (ex: '/invite nicknamewithoutspace')
302        preg_match_all($pattern_noquote,$p[0],$res2,PREG_OFFSET_CAPTURE);
303        foreach( $res2[1] as $p2 )
304        {
305          $p2[1] += $p[1];
306          $params_res[] = $p2;
307        }
308      }
309
310      // order the array by offset
311      $params = array();
312      foreach($params_res as $p) $params[$p[1]] = $p[0];
313      ksort($params);
314      $params = array_values($params);
315      $params = array_map("trim",$params);
316      $params = array_merge(array($clientid,$recipientid), $params);
317
318      $result['cmdstr']  = $cmd_str;
319      $result['cmdname'] = $cmd;
320      $result['params']  = $params;
321    }
322    return $result;
323  }
324
325  /*
326  // THIS IS ANOTHER WAY TO PARSE THE PARAMETERS
327  // IT'S NOT SIMPLIER BUT MAYBE FASTER
328  // @todo : take the faster methode
329  function ParseCommand($cmd_str, $one_parameter = false)
330  {
331    $pattern_command = '/^\/([a-z0-9]+)\s*([a-z0-9]+)\s*([a-z0-9]+)\s*(.*)/';
332    $result = array();
333
334    // parse the command name (ex: '/invite')
335    if (preg_match($pattern_command, $cmd_str, $res))
336    {
337      $cmd         = $res[1];
338      $clientid    = $res[2];
339      $recipientid = $res[3];
340      $params_str  = $res[4];
341
342      // don't parse multiple parameters for special commands with only one parameter
343      // this make possible to send double quotes (") in these commands
344      if ($one_parameter || $cmd == 'send' || $cmd == 'notice' || $cmd == 'me')
345      {
346        $result['cmdstr']  = $cmd_str;
347        $result['cmdname'] = $cmd;
348        $result['params']  = array($clientid, $recipientid, $params_str);
349        return $result;
350      }
351
352      $params = array($clientid, $recipientid);
353      $sep    = preg_match('/[^\\\\]"/',$params_str) ? '"' : ' ';
354      if ($sep == ' ') $params_str = ' ' . $params_str;
355      $offset = 0;
356      while (1)
357      {
358        $i1 = strpos($params_str,$sep,$offset);
359        // capture the parameter value
360        if ($i1 !== FALSE)
361        {
362          // remove multi-separators
363          while (1)
364          {
365            if (strpos($params_str,$sep,$i1+1) - $i1 == 1)
366              $i1++;
367            else
368              break;
369          }
370          // search the parameter terminason
371          $offset = $i1+1;
372          $i2 = strpos($params_str,$sep,$offset);
373          if ($i2 !== FALSE)
374          {
375            $offset = $i2 + ($sep == '"' ? 1 : 0);
376            $p = substr($params_str, $i1+1, $i2-$i1-1);
377            if (!preg_match('/^\s*$/',$p))
378              $params[] = $p;
379          }
380          else
381            break;
382        }
383        else
384          break;
385      }
386      // append the tail
387      if ($offset < strlen($params_str))
388        $params[] = substr($params_str,$offset);
389
390      $result['cmdstr']  = $cmd_str;
391      $result['cmdname'] = $cmd;
392      $result['params']  = $params;
393    }
394    return $result;
395  }
396*/
397
398
399}
400
401?>