Assembla home | Assembla project page
 

root/trunk/Phergie/Plugin/Abstract/Base.php

Revision 278, 22.5 kB (checked in by Seldaek, 1 year ago)

* Fixed a bug in staticPluginLoaded() and updated all calls to it

Line 
1 <?php
2
3 /**
4  * Base class for handlers of events received from the IRC server to provide
5  * empty handler functions for cases where no action should be taken.
6  */
7 abstract class Phergie_Plugin_Abstract_Base
8 {
9     /**
10      * Flag indicating whether or not the plugin requires its own directory
11      * for local storage
12      *
13      * @see $dir
14      * @var bool
15      */
16     protected $needsDir = false;
17
18     /**
19      * Path to the directory for the plugin if $needsDir is enabled
20      *
21      * @see $needsDir
22      */
23     protected $dir;
24
25     /**
26      * Reference back to the client, used to initiate commands
27      *
28      * @var Phergie_Driver_Abstract
29      */
30     protected $client;
31
32     /**
33      * Short class name
34      *
35      * @var string
36      */
37     protected $name;
38
39     /**
40      * Last intercepted event
41      *
42      * @var Phergie_Event_Request|Phergie_Event_Response
43      */
44     protected $event;
45
46     /**
47      * List of administrator hostmasks
48      *
49      * @var array
50      */
51     protected $adminList = array();
52
53     /**
54      * List of channel ops
55      *
56      * @var array
57      */
58     protected $opList = array();
59
60     /**
61      * Flag indicating whether or not the plugin is enabled and receives events
62      *
63      * @var bool
64      */
65     public $enabled = true;
66
67     /**
68      * Flag indicating whether or not the plugin is muted and can output events
69      *
70      * @var array
71      */
72     public $muted = array();
73
74     /**
75      * Flag indicating whether or not the plugin is a passive plugin or not
76      *
77      * @var bool
78      */
79     public $passive = false;
80
81     /**
82      * Flag indicating whether or not the plugin is an admin plugin or not
83      *
84      * @var bool
85      */
86     public $needsAdmin = false;
87
88     /**
89      * Sets a reference to the client used to initiate commands.
90      *
91      * @param Phergie_Driver_Abstract $client
92      * @return void
93      */
94     final public function __construct(Phergie_Driver_Abstract $client)
95     {
96         // Temp fix till a better one is added
97         @set_error_handler(array(__CLASS__, 'onBaseError'));
98         $this->client = $client;
99
100         $name = get_class($this);
101         $this->name = substr($name, strrpos($name, '_') + 1);
102
103         if ($this->needsDir) {
104             $class = new ReflectionClass($name);
105             $dir = dirname($class->getFilename()) . DIRECTORY_SEPARATOR . $this->name . DIRECTORY_SEPARATOR;
106             if (!file_exists($dir)) {
107                 mkdir($dir);
108             }
109             $this->dir = $dir;
110         }
111         $this->onInit();
112     }
113
114     /**
115      * Base error handler for PHP errors. This functions called onPhpError for
116      * any clas extending it to give each plugin its own error handler.
117      *
118      * @return bool
119      */
120     public final function onBaseError($errno, $errstr, $errfile, $errline)
121     {
122         if (!isset($this)) {
123             return false;
124         }
125
126         return $this->onPhpError($errno, $errstr, $errfile, $errline);
127     }
128
129     /**
130      * Returns the short name of the current plugin.
131      *
132      * @return string
133      */
134     public function getName()
135     {
136         return $this->name;
137     }
138
139     /**
140      * Returns the value of a specified configuration setting.
141      *
142      * @param string $setting Full name of the setting including the plugin
143      *                        name prefix (ex: pluginname.settingname)
144      * @return mixed
145      */
146     public function getIni($setting)
147     {
148         return $this->client->getIni($setting);
149     }
150
151     /**
152      * Returns the value of a specified configuration setting for the current
153      * plugin.
154      *
155      * @param string $setting Name of the setting without the plugin name
156      *                        prefix
157      * @return mixed
158      */
159     public function getPluginIni($setting)
160     {
161         return $this->client->getIni($this->getName() . '.' . $setting);
162     }
163
164     /**
165      * Sets the value of a specified configuration setting.
166      *
167      * @param string $setting Full name of the setting including the plugin
168      *                        name prefix (ex: pluginname.settingname)
169      * @param mixed $value New value for the setting
170      * @return void
171      */
172     public function setIni($setting, $value)
173     {
174         $this->client->setIni($setting, $value);
175     }
176
177     /**
178      * Sets the value of a specified configuration setting for the current
179      * plugin.
180      *
181      * @param string $setting Name of the setting without the plugin name
182      *                        prefix
183      * @param mixed $value New value for the setting
184      * @return void
185      */
186     public function setPluginIni($setting, $value)
187     {
188         $this->client->setIni($this->getName() . '.' . $setting, $value);
189     }
190
191     /**
192      * Stores the last intercepted event. Should only be called by drivers.
193      *
194      * @param Phergie_Event_Request|Phergie_Event_Response $event
195      * @return void
196      */
197     public function setEvent($event)
198     {
199         $this->event = $event;
200     }
201
202     /**
203      * Shorthand for the underlying driver's debugging function.
204      *
205      * @param string $message Message to log
206      * @param bool $displayDebug Toggle whether to display the message in the
207      *                           console or not
208      * @return void
209      */
210     public function debug($message, $displayDebug = true)
211     {
212         $this->client->debug('<' . strtolower($this->name) . '> ' . $message, $displayDebug);
213     }
214
215     /**
216      * Decodes the entities of a given string and
217      * transliterates the UTF-8 string into corresponding ASCII characters.
218      *
219      * @param string $text The text to decode.
220      * @param string $charSetFrom The charset used as the base chrset to convert from
221      * @param string $charSetTo The charset used to convert to
222      * @return string
223      */
224     public function decodeTranslit($text, $charSetFrom = 'UTF-8', $charSetTo = 'ISO-8859-1')
225     {
226         $text = html_entity_decode($text, ENT_QUOTES, $charSetFrom);
227         if (strpos($text, '&#') !== false) {
228             $text = preg_replace('/&#0*([0-9]+);/me', '$this->codeToUtf(\\1)', $text);
229             $text = preg_replace('/&#x0*([a-f0-9]+);/mei', '$this->codeToUtf(hexdec(\\1))', $text);
230         }
231
232         // Use the translit extension if installed else fallback on to basic transliteration
233         if (extension_loaded('iconv')) {
234             $text = iconv($charSetFrom, $charSetTo . '//TRANSLIT', $text);
235         // Transliteration supprt via the translit extension is still experimental
236         } else if (false && extension_loaded('translit')) {
237             $text = transliterate($text, array('han_transliterate', 'diacritical_remove'), $charSetFrom, $charSetTo);
238         } else {
239             $text = strtr($text, 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ',
240                                  'AAAAAAACEEEEIIIIDNOOOOOOUUUUYPYaaaaaaaceeeeiiiidnoooooouuuuypy');
241             $text = preg_replace('{[^a-z0-9&|"#\'\{\}()§^!°\[\]$*¨µ£%´`~=+:/;.,?><\\ _-]}i', '', $text);
242             $text = utf8_decode($text);
243         }
244
245         return $text;
246     }
247
248     /**
249      * Converts a given unicode to its UTF-8 equivalent.
250      *
251      * @param int $code The code to decode to UTF-8.
252      * @return string
253      */
254     public function codeToUtf($code)
255     {
256         $code = intval($code);
257         switch ($code) {
258             // 1 byte, 7 bits
259             case 0:
260                 return chr(0);
261             case ($code&0x7F):
262                 return chr($code);
263
264             // 2 bytes, 11 bits
265             case ($code&0x7FF):
266                 return chr(0xC0|(($code>>6) &0x1F)) .
267                        chr(0x80|($code&0x3F));
268
269             // 3 bytes, 16 bits
270             case ($code&0xFFFF):
271                 return chr(0xE0|(($code>>12) &0x0F)) .
272                        chr(0x80|(($code>>6) &0x3F)) .
273                        chr(0x80|($code&0x3F));
274
275             // 4 bytes, 21 bits
276             case ($code&0x1FFFFF):
277                 return chr(0xF0|($code>>18)) .
278                        chr(0x80|(($code>>12) &0x3F)) .
279                        chr(0x80|(($code>>6) &0x3F)) .
280                        chr(0x80|($code&0x3F));
281         }
282     }
283
284     /**
285      * Parses a string of command line like arguments and returns them as an array
286      * Parses the following:
287      *    -Strings: "" | "foo" | "foo bar" | "foo \" bar" | foo
288      *    -Options: --opt | --opt foo | --opt= | --opt=foo | --opt="" | --opt="foo" | --opt="foo bar"
289      *      @Note: If no parameter is given, the value returned by the option defaults to
290      *            1 unless its an empty value such as --opt="" or --opt=
291      *    -Flags: -flag +flag
292      *      @Note: Flags are handled using bitwise. 1 = -flag, 2 = +flag, 3 = Both a +flag and -flag
293      *
294      * The results get stored in an array for the following structure:
295      *     Array -> strings []                   -Array containing all the string matches
296      *     Array -> commands [command] => value  -Array containing all the string matches
297      *     Array -> flags [flag] => bitwise      -Array containing all the string matches
298      *     Array -> all [strings|commands|flags] -Array containing a list of all the
299      *                                            strings, commands and flags
300      *
301      * @param string $args String of arguments that will be parsed
302      * @return array
303      */
304     public function parseArguments($args)
305     {
306         // Strip away multiple spaces
307         $args = ' ' . preg_replace('#\s+#', ' ', trim($args)) . ' ';
308
309         // Check the args string for agurments
310         preg_match_all('{' .
311         // String Regex: Matches "" | "foo" | "foo bar" | "foo \" bar" | foo
312         '(".*?(?<!\\\)" | [^-+\s]+) | ' .
313         // Option Regex: Matches --opt | --opt foo | --opt= | --opt=foo | --opt="" | --opt="foo" | --opt="foo bar"
314         '(--\w+ (?:=".*?(?<!\\\)" | [=\s][^-+"\s]*)?) | ' .
315         // Flag Regex: Matches -flag +flag
316         '([-+]\w+)}xi', $args, $matches);
317         // Shifts the first group matches element off of matches
318         $matches = array_shift($matches);
319
320         // Returned data structure
321         $data = array(
322             'strings'  => array(),
323             'commands' => array(),
324             'flags'    => array(),
325             'all'      => array(
326                 'strings'  => '',
327                 'commands' => array(),
328                 'flags'    => array()
329             )
330         );
331
332         // Loop through the results
333         foreach($matches as $match) {
334             $match = trim($match);
335             // Check to see if the current match an an --option
336             if (substr($match, 0, 2) === '--') {
337                 $value = preg_split('/[=\s]/', $match, 2);
338                 $com = substr(array_shift($value), 2);
339                 $value = trim(join($value));
340                 // Strip quotes from the option's value
341                 $realValue = (substr($value, 0, 1) === '"' && substr($value, -1) === '"' || substr($match, -1) === '=');
342                 if (substr($value, 0, 1) === '"' && substr($value, -1) === '"') {
343                     $value = trim(substr($value, 1, -1));
344                 }
345                 if (!in_array($com, $data['all']['commands'])) {
346                     $data['all']['commands'][] = $com;
347                 }
348                 $data['commands'][$com] = (!empty($value) || $realValue ? str_replace('\"', '"', $value) : true);
349                 continue;
350             }
351
352             // Check to see if the current match is a -flag
353             if (substr($match, 0, 1) === '-' || substr($match, 0, 1) === '+') {
354                 $flag = substr($match, 1);
355                 if (!in_array($flag, $data['all']['flags'])) {
356                     $data['all']['flags'][] = $flag;
357                 }
358                 // Handle the flags using bitwise operations. 1=-flag, 2=+flag, 3=Both a plus and minus
359                 if (isset($data['flags'][$flag])) {
360                     $data['flags'][$flag] |= (substr($match, 0, 1) === '-' ? 0x1 : 0x2);
361                 } else {
362                     $data['flags'][$flag] = (substr($match, 0, 1) === '-' ? 0x1 : 0x2);
363                 }
364                 continue;
365             }
366
367             // Strip the quotes away from match
368             if (substr($match, 0, 1) === '"' && substr($match, -1) === '"') {
369                 $match = trim(substr($match, 1, -1));
370             }
371             // The match value isn't a flag or an option so consider it a string
372             if (!empty($match)) {
373                 $data['strings'][] = str_replace('\"', '"', $match);
374             }
375         }
376
377         $data['all']['strings'] = trim(implode(' ', $data['strings']));
378         return $data;
379     }
380
381     /**
382      * Converts a given integer/timestamp into days, minutes and seconds
383      *
384      * @param int $time The time/integer to calulate the values from
385      * @return string
386      */
387     public function getCountdown($time)
388     {
389         $return = array();
390
391         $days = floor($time / 86400);
392         if ($days > 0) {
393             $return[] = $days . 'd';
394             $time %= 86400;
395         }
396
397         $hours = floor($time / 3600);
398         if ($hours > 0) {
399             $return[] = $hours . 'h';
400             $time %= 3600;
401         }
402
403         $minutes = floor($time / 60);
404         if ($minutes > 0) {
405             $return[] = $minutes . 'm';
406             $time %= 60;
407         }
408
409         if ($time > 0 || count($return) <= 0) {
410             $return[] = ($time > 0 ? $time : '0') . 's';
411         }
412
413         return implode(' ', $return);
414     }
415
416     /**
417      * Checks if a specified plugin is loaded
418      *
419      * @param string $plugin Plugin to check
420      * @param Phergie_Driver_Abstract $client Client instance
421      * @param array $plugins List of short names for plugins that the
422      *                       bootstrap file intends to instantiate
423      * @return bool
424      */
425     public function pluginLoaded($plugin, $plugins = null, $client = null)
426     {
427         $plugin = trim($plugin);
428         if (!empty($plugin)) {
429             if (!$client) {
430                 $client = $this->client;
431             }
432             if (!is_array($plugins) || count($plugins) <= 0) {
433                 $plugins = $this->getPluginList();
434             }
435             $plugins = array_map('strtolower', $plugins);
436             if (in_array(strtolower($plugin), $plugins) && class_exists('Phergie_Plugin_' . $plugin) &&
437                 call_user_func(array('Phergie_Plugin_' . $plugin, 'checkDependencies'), $client, $plugins) === true) {
438                 return true;
439             }
440         }
441         return false;
442     }
443
444     /**
445      * Static call to check if a specified plugin is loaded
446      *
447      * @param string $plugin Plugin to check
448      * @param Phergie_Driver_Abstract $client Client instance
449      * @param array $plugins List of short names for plugins that the
450      *                       bootstrap file intends to instantiate
451      * @return bool
452      */
453     public static function staticPluginLoaded($plugin, $client, array $plugins)
454     {
455         $plugin = trim($plugin);
456         if (!empty($plugin) && is_array($plugins)) {
457             $plugins = array_map('strtolower', $plugins);
458             if (in_array(strtolower($plugin), $plugins) && class_exists('Phergie_Plugin_' . $plugin) &&
459                 call_user_func(array('Phergie_Plugin_' . $plugin, 'checkDependencies'), $client, $plugins) === true) {
460                 return true;
461             }
462         }
463         return false;
464     }
465
466     /**
467      * Returns whether or not a message originated from an authorized admin or
468      * op.
469      *
470      * @param bool $hostmaskAdminOnly Whether or not to allow just hostmask
471      *                                admins or not
472      * @return bool TRUE if the message originated from an authorized
473      *              individual, FALSE otherwise
474      */
475     public function fromAdmin($hostmaskAdminOnly = false)
476     {
477         $class = $this->getName();
478         // Check to see if the current class has any admin or ops settings specified
479         $ini = $this->getPluginIni('ops');
480         if (is_null($ini)) {
481             $ini = $this->getIni('admincommand.ops');
482         }
483         $this->opList[$class] = ((strtolower($ini) === 'true' || $ini === '1'));
484
485         // Handle the admin settings
486         $ini = trim($this->getPluginIni('admins') . ' ' . $this->getIni('admincommand.admins'));
487         $this->adminList[$class] = (!empty($ini) ? $this->hostmasksToRegex($ini) : null);
488         unset($ini);
489
490         // Try to match mask against admin masks
491         if (!empty($this->adminList[$class]) &&
492             preg_match($this->adminList[$class], $this->event->getHostmask())) {
493             return true;
494         }
495
496         // Check if is op and ops are admins
497         if (!$hostmaskAdminOnly && !empty($this->opList[$class]) && $this->pluginLoaded('ServerInfo')) {
498             $nick = $this->event->getNick();
499             $source = $this->event->getSource();
500             if (Phergie_Plugin_ServerInfo::isOp($nick, $source)) {
501                 return true;
502             }
503         }
504         return false;
505     }
506
507     /**
508      * Returns whether or not the current environment meets the requirements
509      * of the plugin in order for it to be run, including the PHP version,
510      * loaded PHP extensions, and other plugins intended to be loaded.
511      * Plugins with such requirements should override this method.
512      *
513      * @param Phergie_Driver_Abstract $client Client instance
514      * @param array $plugins List of short names for plugins that the
515      *                       bootstrap file intends to instantiate
516      * @return bool TRUE if dependencies are met, FALSE otherwise
517      */
518     public static function checkDependencies(Phergie_Driver_Abstract $client, array $plugins)
519     {
520         return true;
521     }
522
523     /**
524      * Initializes the plugin. Should it require initialization, just
525      * override this method.
526      *
527      * @return void
528      */
529     public function onInit() { }
530
531     /**
532      * Shuts down the plugin, called just before the client exits. Should the
533      * plugin require any cleanup actions, just override this method.
534      *
535      * @return void
536      */
537     public function onShutdown() { }
538
539     /**
540      * Handler for when the server prompts the client for a nick.
541      *
542      * @return void
543      */
544     public function onNick() { }
545
546     /**
547      * Handler for when a user obtains operator privileges.
548      *
549      * @return void
550      */
551     public function onOper() { }
552
553     /**
554      * Handler for when the client session is about to be terminated.
555      *
556      * @return void
557      */
558     public function onQuit() { }
559
560     /**
561      * Handler for when a user joins a channel.
562      *
563      * @return void
564      */
565     public function onJoin() { }
566
567     /**
568      * Handler for when a user leaves a channel.
569      *
570      * @return void
571      */
572     public function onPart() { }
573
574     /**
575      * Handler for when a user or channel mode is changed.
576      *
577      * @return void
578      */
579     public function onMode() { }
580
581     /**
582      * Handler for when a channel topic is viewed or changed.
583      *
584      * @return void
585      */
586     public function onTopic() { }
587
588     /**
589      * Handler for when a message is received from a channel or user.
590      *
591      * @return void
592      */
593     public function onPrivmsg() { }
594
595     /**
596      * Handler for when an action is received from a channel or user
597      *
598      * @return void
599      */
600     public function onAction() { }
601
602     /**
603      * Handler for when a notice is received.
604      *
605      * @return void
606      */
607     public function onNotice() { }
608
609     /**
610      * Handler for when a user is kicked from a channel.
611      *
612      * @return void
613      */
614     public function onKick() { }
615
616     /**
617      * Handler for when the server or a userchecks the client connection to
618      * ensure activity.
619      *
620      * @return void
621      */
622     public function onPing() { }
623
624     /**
625      * Handler for when the server sends a CTCP Time request
626      *
627      * @return void
628      */
629     public function onTime() { }
630
631     /**
632      * Handler for when the server sends a CTCP Version request
633      *
634      * @return void
635      */
636     public function onVersion() { }
637
638     /**
639      * Handler for when the server sends a CTCP request
640      *
641      * @return void
642      */
643     public function onCtcp() { }
644
645     /**
646      * Handler for the reply received when a ping CTCP is sent
647      *
648      *
649      * @return void
650      */
651     public function onPingReply() { }
652
653     /**
654      * Handler for the reply received when a time CTCP is sent
655      *
656      * @return void
657      */
658     public function onTimeReply() { }
659
660     /**
661      * Handler for the reply received when a version CTCP is sent
662      *
663      * @return void
664      */
665     public function onVersionReply() { }
666
667     /**
668      * Handler for the reply received when a CTCP is sent
669      *
670      * @return void
671      */
672     public function onCtcpReply() { }
673
674     /**
675      * Handler for raw requests from the server
676      *
677      * @return void
678      */
679     public function onRaw() { }
680
681     /**
682      * Handler for when an unhandled error occurs.
683      *
684      * @return void
685      */
686     public function onError() { }
687
688     /**
689      * Handler for when the server sends a kill request.
690      *
691      * @return void
692      */
693     public function onKill() { }
694
695     /**
696      * Handler for when a server response is received to a client-issued
697      * command.
698      *
699      * @return void
700      */
701     public function onResponse() { }
702
703     /**
704      * Handler for when the bot connects to the server
705      *
706      * @return void
707      */
708     public function onConnect() { }
709
710     /**
711      * Handler for each iteration of the while loop while connected to the
712      * server
713      *
714      * @return void
715      */
716     public function onTick() { }
717
718     /**
719      * Handler for when a user sends an invite request
720      *
721      * @return void
722      */
723     public function onInvite() { }
724
725     /**
726      * Handler for when PHP throws an error
727      *
728      * @return void
729      */
730     public function onPhpError($errno, $errstr, $errfile, $errline) { return false; }
731
732
733     /**
734      * Defers calls to command methods to the client, used to alleviate the
735      * need to explicitly refer to the client instance for all command method
736      * calls.
737      *
738      * @param string $method Name of the method called
739      * @param array $arguments Arguments passed in the method call
740      * @return mixed Return value of the method call
741      */
742     public function __call($method, $arguments)
743     {
744         // Silence output calls if the plugin is muted for that source or globally
745         $source = null;
746         if (isset($arguments[0])) {
747             $source = trim(strtolower($arguments[0]));
748         }
749         if (((!empty($source) && isset($this->muted[$source]) && $this->muted[$source]) ||
750             (isset($this->muted['global']) && $this->muted['global'])) &&
751             substr($method, 0, 2) === 'do') {
752             return false;
753         }
754
755         return call_user_func_array(array($this->client, $method), $arguments);
756     }
757 }
758
Note: See TracBrowser for help on using the browser.