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?>