MDL-65743 lib: update moodle library docs
[moodle.git] / lib / jabber / XMPP / XMPP.php
CommitLineData
838a8eb1 1<?php
838a8eb1 2
d947f53d
SL
3namespace BirknerAlex\XMPPHP;
4
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 */
838a8eb1 32
33/**
34 * XMPPHP Main Class
d947f53d
SL
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>
838a8eb1 41 * @copyright 2008 Nathanael C. Fritz
d947f53d 42 * @version $Id$
838a8eb1 43 */
d947f53d 44class XMPP extends XMLStream {
838a8eb1 45 /**
46 * @var string
47 */
d804f9e2 48 public $server;
838a8eb1 49
50 /**
51 * @var string
52 */
d804f9e2 53 public $user;
838a8eb1 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;
d804f9e2 79 protected $session_started = false;
838a8eb1 80
81 /**
82 * @var boolean
83 */
84 protected $auto_subscribe = false;
85
86 /**
87 * @var boolean
88 */
89 protected $use_encryption = true;
90
d804f9e2
PS
91 /**
92 * @var boolean
93 */
94 public $track_presence = true;
95
96 /**
97 * @var object
98 */
99 public $roster;
100
838a8eb1 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;
d947f53d 120 $this->server = $server;
838a8eb1 121 $this->basejid = $this->user . '@' . $this->host;
122
d804f9e2
PS
123 $this->roster = new Roster();
124 $this->track_presence = true;
125
838a8eb1 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
d804f9e2
PS
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');
838a8eb1 137 }
138
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 }
156
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) {
d947f53d
SL
166 if ($this->disconnected) {
167 throw new Exception('You need to connect first');
168 }
169
170 if(empty($type)) {
d804f9e2
PS
171 $type = 'chat';
172 }
d947f53d 173
838a8eb1 174 $to = htmlspecialchars($to);
175 $body = htmlspecialchars($body);
176 $subject = htmlspecialchars($subject);
177
d804f9e2 178 $out = "<message from=\"{$this->fulljid}\" to=\"$to\" type='$type'>";
838a8eb1 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 }
186
187 /**
188 * Set Presence
189 *
190 * @param string $status
191 * @param string $show
192 * @param string $to
193 */
d947f53d
SL
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 }
198
838a8eb1 199 if($type == 'available') $type = '';
200 $to = htmlspecialchars($to);
201 $status = htmlspecialchars($status);
202 if($show == 'unavailable') $type = 'unavailable';
203
204 $out = "<presence";
d804f9e2 205 if($to) $out .= " to=\"$to\"";
838a8eb1 206 if($type) $out .= " type='$type'";
d947f53d 207 if($show == 'available' and !$status and $priority !== null) {
838a8eb1 208 $out .= "/>";
209 } else {
210 $out .= ">";
211 if($show != 'available') $out .= "<show>$show</show>";
212 if($status) $out .= "<status>$status</status>";
d947f53d 213 if($priority !== null) $out .= "<priority>$priority</priority>";
838a8eb1 214 $out .= "</presence>";
215 }
216
217 $this->send($out);
218 }
d804f9e2
PS
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 }
838a8eb1 228
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 }
d947f53d 240 $body = $xml->sub('body');
838a8eb1 241 $payload['from'] = $xml->attrs['from'];
d947f53d 242 $payload['body'] = is_object($body) ? $body->data : FALSE; // $xml->sub('body')->data;
d804f9e2 243 $payload['xml'] = $xml;
d947f53d 244 $this->log->log("Message: {$payload['body']}", Log::LEVEL_DEBUG);
838a8eb1 245 $this->event('message', $payload);
246 }
247
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 : '';
d804f9e2
PS
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 }
d947f53d 263 $this->log->log("Presence: {$payload['from']} [{$payload['show']}] {$payload['status']}", Log::LEVEL_DEBUG);
d804f9e2
PS
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 }
838a8eb1 269 $this->event('subscription_requested', $payload);
d804f9e2 270 } elseif(array_key_exists('type', $xml->attrs) and $xml->attrs['type'] == 'subscribed') {
838a8eb1 271 $this->event('subscription_accepted', $payload);
272 } else {
273 $this->event('presence', $payload);
274 }
275 }
276
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>");
d804f9e2 285 } elseif($xml->hasSub('bind') and $this->authed) {
838a8eb1 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...");
d804f9e2 291 if ($this->password) {
838a8eb1 292 $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>" . base64_encode("\x00" . $this->user . "\x00" . $this->password) . "</auth>");
d804f9e2
PS
293 } else {
294 $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='ANONYMOUS'/>");
295 }
838a8eb1 296 }
297 }
298
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) {
d947f53d 316 $this->log->log("Auth failed!", Log::LEVEL_ERROR);
838a8eb1 317 $this->disconnect();
318
d947f53d 319 throw new Exception('Auth failed!');
838a8eb1 320 }
321
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;
d804f9e2
PS
331 $jidarray = explode('/',$this->fulljid);
332 $this->jid = $jidarray[0];
838a8eb1 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 }
338
339 /**
340 * Retrieves the roster
341 *
342 */
343 public function getRoster() {
344 $id = $this->getID();
838a8eb1 345 $this->send("<iq xmlns='jabber:client' type='get' id='$id'><query xmlns='jabber:iq:roster' /></iq>");
346 }
347
348 /**
d804f9e2
PS
349 * Roster iq handler
350 * Gets all packets matching XPath "iq/{jabber:iq:roster}query'
838a8eb1 351 *
352 * @param string $xml
353 */
d804f9e2
PS
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 }
838a8eb1 381 }
382
383 /**
384 * Session start handler
385 *
386 * @param string $xml
387 */
388 protected function session_start_handler($xml) {
389 $this->log->log("Session started");
d804f9e2 390 $this->session_started = true;
838a8eb1 391 $this->event('session_start');
392 }
393
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 }
d804f9e2
PS
404
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 }
418
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 }
838a8eb1 441}