]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/Irc/extlib/phergie/Phergie/Driver/Streams.php
Added Phergie PHP IRC library
[quix0rs-gnu-social.git] / plugins / Irc / extlib / phergie / Phergie / Driver / Streams.php
1 <?php
2 /**
3  * Phergie 
4  *
5  * PHP version 5
6  *
7  * LICENSE
8  *
9  * This source file is subject to the new BSD license that is bundled
10  * with this package in the file LICENSE.
11  * It is also available through the world-wide-web at this URL:
12  * http://phergie.org/license
13  *
14  * @category  Phergie 
15  * @package   Phergie
16  * @author    Phergie Development Team <team@phergie.org>
17  * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
18  * @license   http://phergie.org/license New BSD License
19  * @link      http://pear.phergie.org/package/Phergie
20  */
21
22 /**
23  * Driver that uses the sockets wrapper of the streams extension for 
24  * communicating with the server and handles formatting and parsing of 
25  * events using PHP.
26  *
27  * @category Phergie 
28  * @package  Phergie
29  * @author   Phergie Development Team <team@phergie.org>
30  * @license  http://phergie.org/license New BSD License
31  * @link     http://pear.phergie.org/package/Phergie
32  */
33 class Phergie_Driver_Streams extends Phergie_Driver_Abstract
34 {
35     /**
36      * Socket handlers
37      *
38      * @var array
39      */
40     protected $sockets = array();
41
42     /**
43      * Reference to the currently active socket handler
44      *
45      * @var resource
46      */
47     protected $socket;
48
49     /**
50      * Amount of time in seconds to wait to receive an event each time the 
51      * socket is polled
52      *
53      * @var float 
54      */
55     protected $timeout = 0.1;
56
57     /**
58      * Handles construction of command strings and their transmission to the 
59      * server.
60      *
61      * @param string       $command Command to send
62      * @param string|array $args    Optional string or array of sequential 
63      *        arguments
64      *
65      * @return string Command string that was sent 
66      * @throws Phergie_Driver_Exception
67      */
68     protected function send($command, $args = '')
69     {
70         // Require an open socket connection to continue
71         if (empty($this->socket)) {
72             throw new Phergie_Driver_Exception(
73                 'doConnect() must be called first',
74                 Phergie_Driver_Exception::ERR_NO_INITIATED_CONNECTION
75             );
76         }
77
78         // Add the command
79         $buffer = strtoupper($command);
80
81         // Add arguments
82         if (!empty($args)) {
83
84             // Apply formatting if arguments are passed in as an array
85             if (is_array($args)) {
86                 $end = count($args) - 1;
87                 $args[$end] = ':' . $args[$end];
88                 $args = implode(' ', $args);
89             }
90
91             $buffer .= ' ' . $args;
92         }
93
94         // Transmit the command over the socket connection
95         fwrite($this->socket, $buffer . "\r\n");
96
97         // Return the command string that was transmitted
98         return $buffer;
99     }
100
101     /**
102      * Overrides the parent class to set the currently active socket handler 
103      * when the active connection is changed.
104      *
105      * @param Phergie_Connection $connection Active connection
106      *
107      * @return Phergie_Driver_Streams Provides a fluent interface
108      */
109     public function setConnection(Phergie_Connection $connection)
110     {
111         // Set the active socket handler
112         $hostmask = (string) $connection->getHostmask();
113         if (!empty($this->sockets[$hostmask])) {
114             $this->socket = $this->sockets[$hostmask];
115         }
116
117         // Set the active connection
118         return parent::setConnection($connection);
119     }
120
121     /**
122      * Returns a list of hostmasks corresponding to sockets with data to read.
123      * 
124      * @param int $sec  Length of time to wait for new data (seconds)
125      * @param int $usec Length of time to wait for new data (microseconds)
126      *
127      * @return array List of hostmasks or an empty array if none were found 
128      *         to have data to read
129      */
130     public function getActiveReadSockets($sec = 0, $usec = 200000)
131     {
132         $read = $this->sockets;
133         $write = null;
134         $error = null;
135         $active = array();
136
137         if (count($this->sockets) > 0) {
138             $number = stream_select($read, $write, $error, $sec, $usec);
139             if ($number > 0) {
140                 foreach ($read as $item) {
141                     $active[] = array_search($item, $this->sockets);
142                 }
143             }
144         }
145
146         return $active;
147     }
148
149     /**
150      * Sets the amount of time to wait for a new event each time the socket 
151      * is polled.
152      *
153      * @param float $timeout Amount of time in seconds
154      *
155      * @return Phergie_Driver_Streams Provides a fluent interface
156      */
157     public function setTimeout($timeout)
158     {
159         $timeout = (float) $timeout;
160         if ($timeout) {
161             $this->timeout = $timeout;
162         }
163         return $this;
164     }
165
166     /**
167      * Returns the amount of time to wait for a new event each time the 
168      * socket is polled.
169      *
170      * @return float Amount of time in seconds
171      */
172     public function getTimeout()
173     {
174         return $this->timeout;
175     }
176
177     /**
178      * Supporting method to parse event argument strings where the last 
179      * argument may contain a colon.
180      *
181      * @param string $args  Argument string to parse
182      * @param int    $count Optional maximum number of arguments
183      *
184      * @return array Array of argument values
185      */
186     protected function parseArguments($args, $count = -1)
187     {
188         return preg_split('/ :?/S', $args, $count);
189     }
190
191     /**
192      * Listens for an event on the current connection.
193      *
194      * @return Phergie_Event_Interface|null Event instance if an event was 
195      *         received, NULL otherwise
196      */
197     public function getEvent()
198     {
199         // Check for a new event on the current connection
200         $buffer = fgets($this->socket, 512);
201
202         // If no new event was found, return NULL
203         if (empty($buffer)) {
204             return null;
205         }
206
207         // Strip the trailing newline from the buffer
208         $buffer = rtrim($buffer);
209
210         // If the event is from the server...
211         if (substr($buffer, 0, 1) != ':') {
212
213             // Parse the command and arguments
214             list($cmd, $args) = array_pad(explode(' ', $buffer, 2), 2, null);
215
216         } else {
217             // If the event could be from the server or a user...
218
219             // Parse the server hostname or user hostmask, command, and arguments
220             list($prefix, $cmd, $args) 
221                 = array_pad(explode(' ', ltrim($buffer, ':'), 3), 3, null);
222             if (strpos($prefix, '@') !== false) {
223                 $hostmask = Phergie_Hostmask::fromString($prefix);
224             }
225         }
226
227         // Parse the event arguments depending on the event type
228         $cmd = strtolower($cmd);
229         switch ($cmd) {
230         case 'names':
231         case 'nick':
232         case 'quit':
233         case 'ping':
234         case 'join':
235         case 'error':
236             $args = array(ltrim($args, ':'));
237             break;
238
239         case 'privmsg':
240         case 'notice':
241             $ctcp = substr(strstr($args, ':'), 1);
242             if (substr($ctcp, 0, 1) === "\x01" && substr($ctcp, -1) === "\x01") {
243                 $ctcp = substr($ctcp, 1, -1);
244                 $reply = ($cmd == 'notice');
245                 list($cmd, $args) = array_pad(explode(' ', $ctcp, 2), 2, null);
246                 $cmd = strtolower($cmd);
247                 switch ($cmd) {
248                 case 'version':
249                 case 'time':
250                 case 'finger':
251                     if ($reply) {
252                         $args = $ctcp;
253                     }
254                     break;
255                 case 'ping':
256                     if ($reply) {
257                         $cmd .= 'Response';
258                     } else {
259                         $cmd = 'ctcpPing';
260                     }
261                     break;
262                 case 'action':
263                     $args = array($this->getConnection()->getNick(), $args);
264                     break;
265
266                 default:
267                     $cmd = 'ctcp';
268                     if ($reply) {
269                         $cmd .= 'Response';
270                     }
271                     $args = array($this->getConnection()->getNick(), $ctcp);
272                     break;
273                 }
274             } else {
275                 $args = $this->parseArguments($args, 2);
276             }
277             break;
278
279         case 'oper':
280         case 'topic':
281         case 'mode':
282             $args = $this->parseArguments($args); 
283             break;
284
285         case 'part':
286         case 'kill':
287         case 'invite':
288             $args = $this->parseArguments($args, 2); 
289             break;
290
291         case 'kick':
292             $args = $this->parseArguments($args, 3); 
293             break;
294
295         // Remove the target from responses
296         default:
297             $args = substr($args, strpos($args, ' ') + 1);
298             break;
299         }
300
301         // Create, populate, and return an event object
302         if (ctype_digit($cmd)) {
303             $event = new Phergie_Event_Response;
304             $event
305                 ->setCode($cmd)
306                 ->setDescription($args);
307         } else {
308             $event = new Phergie_Event_Request;
309             $event
310                 ->setType($cmd)
311                 ->setArguments($args);
312             if (isset($hostmask)) {
313                 $event->setHostmask($hostmask);
314             }
315         }
316         $event->setRawData($buffer);
317         return $event;
318     }
319
320     /**
321      * Initiates a connection with the server.
322      *
323      * @return void
324      */
325     public function doConnect()
326     {
327         // Listen for input indefinitely
328         set_time_limit(0);
329
330         // Get connection information
331         $connection = $this->getConnection();
332         $hostname = $connection->getHost();
333         $port = $connection->getPort();
334         $password = $connection->getPassword();
335         $username = $connection->getUsername();
336         $nick = $connection->getNick();
337         $realname = $connection->getRealname();
338         $transport = $connection->getTransport();
339
340         // Establish and configure the socket connection
341         $remote = $transport . '://' . $hostname . ':' . $port;
342         $this->socket = @stream_socket_client($remote, $errno, $errstr);
343         if (!$this->socket) {
344             throw new Phergie_Driver_Exception(
345                 'Unable to connect: socket error ' . $errno . ' ' . $errstr,
346                 Phergie_Driver_Exception::ERR_CONNECTION_ATTEMPT_FAILED
347             );
348         }
349
350         $seconds = (int) $this->timeout;
351         $microseconds = ($this->timeout - $seconds) * 1000000;
352         stream_set_timeout($this->socket, $seconds, $microseconds);
353
354         // Send the password if one is specified
355         if (!empty($password)) {
356             $this->send('PASS', $password);
357         }
358
359         // Send user information
360         $this->send(
361             'USER',
362             array(
363                 $username, 
364                 $hostname, 
365                 $hostname, 
366                 $realname
367             )
368         );
369
370         $this->send('NICK', $nick); 
371
372         // Add the socket handler to the internal array for socket handlers
373         $this->sockets[(string) $connection->getHostmask()] = $this->socket;
374     }
375
376     /**
377      * Terminates the connection with the server.
378      *
379      * @param string $reason Reason for connection termination (optional)
380      *
381      * @return void
382      */
383     public function doQuit($reason = null)
384     {
385         // Send a QUIT command to the server
386         $this->send('QUIT', $reason);
387
388         // Terminate the socket connection
389         fclose($this->socket);
390
391         // Remove the socket from the internal socket list
392         unset($this->sockets[(string) $this->getConnection()->getHostmask()]);
393     }
394
395     /**
396      * Joins a channel.
397      *
398      * @param string $channels Comma-delimited list of channels to join 
399      * @param string $keys     Optional comma-delimited list of channel keys
400      *
401      * @return void
402      */
403     public function doJoin($channels, $keys = null)
404     {
405         $args = array($channels);
406
407         if (!empty($keys)) {
408             $args[] = $keys;
409         }
410
411         $this->send('JOIN', $args);
412     }
413
414     /**
415      * Leaves a channel.
416      *
417      * @param string $channels Comma-delimited list of channels to leave 
418      *
419      * @return void
420      */
421     public function doPart($channels)
422     {
423         $this->send('PART', $channels);
424     }
425
426     /**
427      * Invites a user to an invite-only channel.
428      *
429      * @param string $nick    Nick of the user to invite
430      * @param string $channel Name of the channel
431      *
432      * @return void
433      */
434     public function doInvite($nick, $channel)
435     {
436         $this->send('INVITE', array($nick, $channel));
437     }
438
439     /**
440      * Obtains a list of nicks of usrs in currently joined channels.
441      *
442      * @param string $channels Comma-delimited list of one or more channels
443      *
444      * @return void
445      */
446     public function doNames($channels)
447     {
448         $this->send('NAMES', $channels);
449     }
450
451     /**
452      * Obtains a list of channel names and topics.
453      *
454      * @param string $channels Comma-delimited list of one or more channels
455      *                         to which the response should be restricted
456      *                         (optional)
457      *
458      * @return void
459      */
460     public function doList($channels = null)
461     {
462         $this->send('LIST', $channels);
463     }
464
465     /**
466      * Retrieves or changes a channel topic.
467      *
468      * @param string $channel Name of the channel
469      * @param string $topic   New topic to assign (optional)
470      *
471      * @return void
472      */
473     public function doTopic($channel, $topic = null)
474     {
475         $args = array($channel);
476
477         if (!empty($topic)) {
478             $args[] = $topic;
479         }
480
481         $this->send('TOPIC', $args);
482     }
483
484     /**
485      * Retrieves or changes a channel or user mode.
486      *
487      * @param string $target Channel name or user nick
488      * @param string $mode   New mode to assign (optional)
489      *
490      * @return void
491      */
492     public function doMode($target, $mode = null)
493     {
494         $args = array($target);
495
496         if (!empty($mode)) {
497             $args[] = $mode;
498         }
499
500         $this->send('MODE', $args);
501     }
502
503     /**
504      * Changes the client nick.
505      *
506      * @param string $nick New nick to assign
507      *
508      * @return void
509      */
510     public function doNick($nick)
511     {
512         $this->send('NICK', $nick);
513     }
514
515     /**
516      * Retrieves information about a nick.
517      *
518      * @param string $nick Nick
519      *
520      * @return void
521      */
522     public function doWhois($nick)
523     {
524         $this->send('WHOIS', $nick);
525     }
526
527     /**
528      * Sends a message to a nick or channel.
529      *
530      * @param string $target Channel name or user nick
531      * @param string $text   Text of the message to send
532      *
533      * @return void
534      */
535     public function doPrivmsg($target, $text)
536     {
537         $this->send('PRIVMSG', array($target, $text));
538     }
539
540     /**
541      * Sends a notice to a nick or channel.
542      *
543      * @param string $target Channel name or user nick
544      * @param string $text   Text of the notice to send
545      *
546      * @return void
547      */
548     public function doNotice($target, $text)
549     {
550         $this->send('NOTICE', array($target, $text));
551     }
552
553     /**
554      * Kicks a user from a channel.
555      *
556      * @param string $nick    Nick of the user
557      * @param string $channel Channel name
558      * @param string $reason  Reason for the kick (optional)
559      *
560      * @return void
561      */
562     public function doKick($nick, $channel, $reason = null)
563     {
564         $args = array($nick, $channel);
565
566         if (!empty($reason)) {
567             $args[] = $response;
568         }
569
570         $this->send('KICK', $args);
571     }
572
573     /**
574      * Responds to a server test of client responsiveness.
575      *
576      * @param string $daemon Daemon from which the original request originates
577      *
578      * @return void
579      */
580     public function doPong($daemon)
581     {
582         $this->send('PONG', $daemon);
583     }
584
585     /**
586      * Sends a CTCP ACTION (/me) command to a nick or channel.
587      *
588      * @param string $target Channel name or user nick
589      * @param string $text   Text of the action to perform
590      *
591      * @return void
592      */
593     public function doAction($target, $text)
594     {
595         $buffer = rtrim('ACTION ' . $text);
596
597         $this->doPrivmsg($target, chr(1) . $buffer . chr(1));
598     }
599
600     /**
601      * Sends a CTCP response to a user.
602      *
603      * @param string       $nick    User nick 
604      * @param string       $command Command to send
605      * @param string|array $args    String or array of sequential arguments 
606      *        (optional)
607      *
608      * @return void
609      */
610     protected function doCtcp($nick, $command, $args = null)
611     {
612         if (is_array($args)) {
613             $args = implode(' ', $args);
614         }
615
616         $buffer = rtrim(strtoupper($command) . ' ' . $args);
617
618         $this->doNotice($nick, chr(1) . $buffer . chr(1)); 
619     }
620
621     /**
622      * Sends a CTCP PING request or response (they are identical) to a user.
623      *
624      * @param string $nick User nick
625      * @param string $hash Hash to use in the handshake
626      *
627      * @return void
628      */
629     public function doPing($nick, $hash)
630     {
631         $this->doCtcp($nick, 'PING', $hash);
632     }
633
634     /**
635      * Sends a CTCP VERSION request or response to a user.
636      *
637      * @param string $nick    User nick
638      * @param string $version Version string to send for a response
639      *
640      * @return void
641      */
642     public function doVersion($nick, $version = null)
643     {
644         if ($version) {
645             $this->doCtcp($nick, 'VERSION', $version);
646         } else {
647             $this->doCtcp($nick, 'VERSION');
648         }
649     }
650
651     /**
652      * Sends a CTCP TIME request to a user.
653      *
654      * @param string $nick User nick
655      * @param string $time Time string to send for a response 
656      *
657      * @return void
658      */
659     public function doTime($nick, $time = null)
660     {
661         if ($time) {
662             $this->doCtcp($nick, 'TIME', $time);
663         } else {
664             $this->doCtcp($nick, 'TIME');
665         }
666     }
667
668     /**
669      * Sends a CTCP FINGER request to a user.
670      *
671      * @param string $nick   User nick
672      * @param string $finger Finger string to send for a response
673      *
674      * @return void
675      */
676     public function doFinger($nick, $finger = null)
677     {
678         if ($finger) {
679             $this->doCtcp($nick, 'FINGER', $finger);
680         } else {
681             $this->doCtcp($nick, 'FINGER');
682         }
683     }
684
685     /**
686      * Sends a raw command to the server.
687      *
688      * @param string $command Command string to send
689      *
690      * @return void
691      */
692     public function doRaw($command)
693     {
694         $this->send('RAW', $command);
695     }
696 }