Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 39 and 311]

   1  <?php
   2  
   3  namespace 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  	  */
  32  
  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;
  49  
  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;
 100  
 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;
 122  
 123  	 	 $this->roster = new Roster();
 124  	 	 $this->track_presence = true;
 125  
 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  	 }
 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) {
 166  	 	 if ($this->disconnected) {
 167  	 	 	 throw new Exception('You need to connect first');
 168  	 	 }
 169  
 170  	     if(empty($type)) {
 171  	         $type = 'chat';
 172  	     }
 173  
 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  	 }
 186  
 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  	 	 }
 198  
 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  	 }
 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  	 	 }
 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  	 }
 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 : '';
 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  	 }
 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>");
 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  	 }
 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) {
 316  	 	 $this->log->log("Auth failed!",  Log::LEVEL_ERROR);
 317  	 	 $this->disconnect();
 318  	 	 
 319  	 	 throw new Exception('Auth failed!');
 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;
 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  	 }
 338  
 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  	 }
 347  
 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  	 }
 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");
 390  	 	 $this->session_started = true;
 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  	 }
 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  	 }
 441  }