MDL-65743 lib: update moodle library docs
[moodle.git] / lib / jabber / XMPP / XMPP.php
1 <?php
3 namespace BirknerAlex\XMPPHP;
5         /**
6          * XMPPHP: The PHP XMPP Library
7          * Copyright (C) 2008  Nathanael C. Fritz
8          * This file is part of SleekXMPP.
9          *
10          * XMPPHP is free software; you can redistribute it and/or modify
11          * it under the terms of the GNU General Public License as published by
12          * the Free Software Foundation; either version 2 of the License, or
13          * (at your option) any later version.
14          *
15          * XMPPHP is distributed in the hope that it will be useful,
16          * but WITHOUT ANY WARRANTY; without even the implied warranty of
17          * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18          * GNU General Public License for more details.
19          *
20          * You should have received a copy of the GNU General Public License
21          * along with XMPPHP; if not, write to the Free Software
22          * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
23          *
24          * @category   xmpphp
25          * @package    XMPPHP
26          * @author     Nathanael C. Fritz <JID: fritzy@netflint.net>
27          * @author     Stephan Wentz <JID: stephan@jabber.wentz.it>
28          * @author     Michael Garvin <JID: gar@netflint.net>
29          * @author     Alexander Birkner (https://github.com/BirknerAlex)
30          * @copyright  2008 Nathanael C. Fritz
31          */
33 /**
34  * XMPPHP Main Class
35  *
36  * @category   xmpphp
37  * @package    XMPPHP
38  * @author     Nathanael C. Fritz <JID: fritzy@netflint.net>
39  * @author     Stephan Wentz <JID: stephan@jabber.wentz.it>
40  * @author     Michael Garvin <JID: gar@netflint.net>
41  * @copyright  2008 Nathanael C. Fritz
42  * @version    $Id$
43  */
44 class XMPP extends XMLStream {
45         /**
46          * @var string
47          */
48         public $server;
50         /**
51          * @var string
52          */
53         public $user;
54         
55         /**
56          * @var string
57          */
58         protected $password;
59         
60         /**
61          * @var string
62          */
63         protected $resource;
64         
65         /**
66          * @var string
67          */
68         protected $fulljid;
69         
70         /**
71          * @var string
72          */
73         protected $basejid;
74         
75         /**
76          * @var boolean
77          */
78         protected $authed = false;
79         protected $session_started = false;
80         
81         /**
82          * @var boolean
83          */
84         protected $auto_subscribe = false;
85         
86         /**
87          * @var boolean
88          */
89         protected $use_encryption = true;
90         
91         /**
92          * @var boolean
93          */
94         public $track_presence = true;
95         
96         /**
97          * @var object
98          */
99         public $roster;
101         /**
102          * Constructor
103          *
104          * @param string  $host
105          * @param integer $port
106          * @param string  $user
107          * @param string  $password
108          * @param string  $resource
109          * @param string  $server
110          * @param boolean $printlog
111          * @param string  $loglevel
112          */
113         public function __construct($host, $port, $user, $password, $resource, $server = null, $printlog = false, $loglevel = null) {
114                 parent::__construct($host, $port, $printlog, $loglevel);
115                 
116                 $this->user      = $user;
117                 $this->password = $password;
118                 $this->resource = $resource;
119                 if(!$server) $server = $host;
120                 $this->server = $server;
121                 $this->basejid = $this->user . '@' . $this->host;
123                 $this->roster = new Roster();
124                 $this->track_presence = true;
126                 $this->stream_start = '<stream:stream to="' . $server . '" xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" version="1.0">';
127                 $this->stream_end   = '</stream:stream>';
128                 $this->default_ns   = 'jabber:client';
129                 
130                 $this->addXPathHandler('{http://etherx.jabber.org/streams}features', 'features_handler');
131                 $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-sasl}success', 'sasl_success_handler');
132                 $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-sasl}failure', 'sasl_failure_handler');
133                 $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-tls}proceed', 'tls_proceed_handler');
134                 $this->addXPathHandler('{jabber:client}message', 'message_handler');
135                 $this->addXPathHandler('{jabber:client}presence', 'presence_handler');
136                 $this->addXPathHandler('iq/{jabber:iq:roster}query', 'roster_iq_handler');
137         }
139         /**
140          * Turn encryption on/ff
141          *
142          * @param boolean $useEncryption
143          */
144         public function useEncryption($useEncryption = true) {
145                 $this->use_encryption = $useEncryption;
146         }
147         
148         /**
149          * Turn on auto-authorization of subscription requests.
150          *
151          * @param boolean $autoSubscribe
152          */
153         public function autoSubscribe($autoSubscribe = true) {
154                 $this->auto_subscribe = $autoSubscribe;
155         }
157         /**
158          * Send XMPP Message
159          *
160          * @param string $to
161          * @param string $body
162          * @param string $type
163          * @param string $subject
164          */
165         public function message($to, $body, $type = 'chat', $subject = null, $payload = null) {
166                 if ($this->disconnected) {
167                         throw new Exception('You need to connect first');
168                 }
170             if(empty($type)) {
171                 $type = 'chat';
172             }
174                 $to       = htmlspecialchars($to);
175                 $body   = htmlspecialchars($body);
176                 $subject = htmlspecialchars($subject);
177                 
178                 $out = "<message from=\"{$this->fulljid}\" to=\"$to\" type='$type'>";
179                 if($subject) $out .= "<subject>$subject</subject>";
180                 $out .= "<body>$body</body>";
181                 if($payload) $out .= $payload;
182                 $out .= "</message>";
183                 
184                 $this->send($out);
185         }
187         /**
188          * Set Presence
189          *
190          * @param string $status
191          * @param string $show
192          * @param string $to
193          */
194         public function presence($status = null, $show = 'available', $to = null, $type='available', $priority=null) {
195                 if ($this->disconnected) {
196                         throw new Exception('You need to connect first');
197                 }
199                 if($type == 'available') $type = '';
200                 $to      = htmlspecialchars($to);
201                 $status = htmlspecialchars($status);
202                 if($show == 'unavailable') $type = 'unavailable';
203                 
204                 $out = "<presence";
205                 if($to) $out .= " to=\"$to\"";
206                 if($type) $out .= " type='$type'";
207                 if($show == 'available' and !$status and $priority !== null) {
208                         $out .= "/>";
209                 } else {
210                         $out .= ">";
211                         if($show != 'available') $out .= "<show>$show</show>";
212                         if($status) $out .= "<status>$status</status>";
213                         if($priority !== null) $out .= "<priority>$priority</priority>";
214                         $out .= "</presence>";
215                 }
216                 
217                 $this->send($out);
218         }
219         /**
220          * Send Auth request
221          *
222          * @param string $jid
223          */
224         public function subscribe($jid) {
225                 $this->send("<presence type='subscribe' to='{$jid}' from='{$this->fulljid}' />");
226                 #$this->send("<presence type='subscribed' to='{$jid}' from='{$this->fulljid}' />");
227         }
229         /**
230          * Message handler
231          *
232          * @param string $xml
233          */
234         public function message_handler($xml) {
235                 if(isset($xml->attrs['type'])) {
236                         $payload['type'] = $xml->attrs['type'];
237                 } else {
238                         $payload['type'] = 'chat';
239                 }
240                 $body = $xml->sub('body');
241                 $payload['from'] = $xml->attrs['from'];
242                 $payload['body'] = is_object($body) ? $body->data : FALSE; // $xml->sub('body')->data;
243                 $payload['xml'] = $xml;
244                 $this->log->log("Message: {$payload['body']}", Log::LEVEL_DEBUG);
245                 $this->event('message', $payload);
246         }
248         /**
249          * Presence handler
250          *
251          * @param string $xml
252          */
253         public function presence_handler($xml) {
254                 $payload['type'] = (isset($xml->attrs['type'])) ? $xml->attrs['type'] : 'available';
255                 $payload['show'] = (isset($xml->sub('show')->data)) ? $xml->sub('show')->data : $payload['type'];
256                 $payload['from'] = $xml->attrs['from'];
257                 $payload['status'] = (isset($xml->sub('status')->data)) ? $xml->sub('status')->data : '';
258                 $payload['priority'] = (isset($xml->sub('priority')->data)) ? intval($xml->sub('priority')->data) : 0;
259                 $payload['xml'] = $xml;
260                 if($this->track_presence) {
261                         $this->roster->setPresence($payload['from'], $payload['priority'], $payload['show'], $payload['status']);
262                 }
263                 $this->log->log("Presence: {$payload['from']} [{$payload['show']}] {$payload['status']}",  Log::LEVEL_DEBUG);
264                 if(array_key_exists('type', $xml->attrs) and $xml->attrs['type'] == 'subscribe') {
265                         if($this->auto_subscribe) {
266                                 $this->send("<presence type='subscribed' to='{$xml->attrs['from']}' from='{$this->fulljid}' />");
267                                 $this->send("<presence type='subscribe' to='{$xml->attrs['from']}' from='{$this->fulljid}' />");
268                         }
269                         $this->event('subscription_requested', $payload);
270                 } elseif(array_key_exists('type', $xml->attrs) and $xml->attrs['type'] == 'subscribed') {
271                         $this->event('subscription_accepted', $payload);
272                 } else {
273                         $this->event('presence', $payload);
274                 }
275         }
277         /**
278          * Features handler
279          *
280          * @param string $xml
281          */
282         protected function features_handler($xml) {
283                 if($xml->hasSub('starttls') and $this->use_encryption) {
284                         $this->send("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'><required /></starttls>");
285                 } elseif($xml->hasSub('bind') and $this->authed) {
286                         $id = $this->getId();
287                         $this->addIdHandler($id, 'resource_bind_handler');
288                         $this->send("<iq xmlns=\"jabber:client\" type=\"set\" id=\"$id\"><bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"><resource>{$this->resource}</resource></bind></iq>");
289                 } else {
290                         $this->log->log("Attempting Auth...");
291                         if ($this->password) {
292                         $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>" . base64_encode("\x00" . $this->user . "\x00" . $this->password) . "</auth>");
293                         } else {
294                         $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='ANONYMOUS'/>");
295                         }       
296                 }
297         }
299         /**
300          * SASL success handler
301          *
302          * @param string $xml
303          */
304         protected function sasl_success_handler($xml) {
305                 $this->log->log("Auth success!");
306                 $this->authed = true;
307                 $this->reset();
308         }
309         
310         /**
311          * SASL feature handler
312          *
313          * @param string $xml
314          */
315         protected function sasl_failure_handler($xml) {
316                 $this->log->log("Auth failed!",  Log::LEVEL_ERROR);
317                 $this->disconnect();
318                 
319                 throw new Exception('Auth failed!');
320         }
322         /**
323          * Resource bind handler
324          *
325          * @param string $xml
326          */
327         protected function resource_bind_handler($xml) {
328                 if($xml->attrs['type'] == 'result') {
329                         $this->log->log("Bound to " . $xml->sub('bind')->sub('jid')->data);
330                         $this->fulljid = $xml->sub('bind')->sub('jid')->data;
331                         $jidarray = explode('/',$this->fulljid);
332                         $this->jid = $jidarray[0];
333                 }
334                 $id = $this->getId();
335                 $this->addIdHandler($id, 'session_start_handler');
336                 $this->send("<iq xmlns='jabber:client' type='set' id='$id'><session xmlns='urn:ietf:params:xml:ns:xmpp-session' /></iq>");
337         }
339         /**
340         * Retrieves the roster
341         *
342         */
343         public function getRoster() {
344                 $id = $this->getID();
345                 $this->send("<iq xmlns='jabber:client' type='get' id='$id'><query xmlns='jabber:iq:roster' /></iq>");
346         }
348         /**
349         * Roster iq handler
350         * Gets all packets matching XPath "iq/{jabber:iq:roster}query'
351         *
352         * @param string $xml
353         */
354         protected function roster_iq_handler($xml) {
355                 $status = "result";
356                 $xmlroster = $xml->sub('query');
357                 foreach($xmlroster->subs as $item) {
358                         $groups = array();
359                         if ($item->name == 'item') {
360                                 $jid = $item->attrs['jid']; //REQUIRED
361                                 $name = $item->attrs['name']; //MAY
362                                 $subscription = $item->attrs['subscription'];
363                                 foreach($item->subs as $subitem) {
364                                         if ($subitem->name == 'group') {
365                                                 $groups[] = $subitem->data;
366                                         }
367                                 }
368                                 $contacts[] = array($jid, $subscription, $name, $groups); //Store for action if no errors happen
369                         } else {
370                                 $status = "error";
371                         }
372                 }
373                 if ($status == "result") { //No errors, add contacts
374                         foreach($contacts as $contact) {
375                                 $this->roster->addContact($contact[0], $contact[1], $contact[2], $contact[3]);
376                         }
377                 }
378                 if ($xml->attrs['type'] == 'set') {
379                         $this->send("<iq type=\"reply\" id=\"{$xml->attrs['id']}\" to=\"{$xml->attrs['from']}\" />");
380                 }
381         }
383         /**
384          * Session start handler
385          *
386          * @param string $xml
387          */
388         protected function session_start_handler($xml) {
389                 $this->log->log("Session started");
390                 $this->session_started = true;
391                 $this->event('session_start');
392         }
394         /**
395          * TLS proceed handler
396          *
397          * @param string $xml
398          */
399         protected function tls_proceed_handler($xml) {
400                 $this->log->log("Starting TLS encryption");
401                 stream_socket_enable_crypto($this->socket, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT);
402                 $this->reset();
403         }
405         /**
406         * Retrieves the vcard
407         *
408         */
409         public function getVCard($jid = Null) {
410                 $id = $this->getID();
411                 $this->addIdHandler($id, 'vcard_get_handler');
412                 if($jid) {
413                         $this->send("<iq type='get' id='$id' to='$jid'><vCard xmlns='vcard-temp' /></iq>");
414                 } else {
415                         $this->send("<iq type='get' id='$id'><vCard xmlns='vcard-temp' /></iq>");
416                 }
417         }
419         /**
420         * VCard retrieval handler
421         *
422         * @param XML Object $xml
423         */
424         protected function vcard_get_handler($xml) {
425                 $vcard_array = array();
426                 $vcard = $xml->sub('vcard');
427                 // go through all of the sub elements and add them to the vcard array
428                 foreach ($vcard->subs as $sub) {
429                         if ($sub->subs) {
430                                 $vcard_array[$sub->name] = array();
431                                 foreach ($sub->subs as $sub_child) {
432                                         $vcard_array[$sub->name][$sub_child->name] = $sub_child->data;
433                                 }
434                         } else {
435                                 $vcard_array[$sub->name] = $sub->data;
436                         }
437                 }
438                 $vcard_array['from'] = $xml->attrs['from'];
439                 $this->event('vcard', $vcard_array);
440         }