Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 310] [Versions 39 and 311]

   1  <?php
   2  /**
   3   * XMPPHP: The PHP XMPP Library
   4   * Copyright (C) 2008  Nathanael C. Fritz
   5   * This file is part of SleekXMPP.
   6   * 
   7   * XMPPHP is free software; you can redistribute it and/or modify
   8   * it under the terms of the GNU General Public License as published by
   9   * the Free Software Foundation; either version 2 of the License, or
  10   * (at your option) any later version.
  11   * 
  12   * XMPPHP is distributed in the hope that it will be useful,
  13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15   * GNU General Public License for more details.
  16   * 
  17   * You should have received a copy of the GNU General Public License
  18   * along with XMPPHP; if not, write to the Free Software
  19   * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  20   *
  21   * @category   xmpphp 
  22   * @package	 XMPPHP
  23   * @author	  Nathanael C. Fritz <JID: fritzy@netflint.net>
  24   * @author	  Stephan Wentz <JID: stephan@jabber.wentz.it>
  25   * @author	  Michael Garvin <JID: gar@netflint.net>
  26   * @copyright  2008 Nathanael C. Fritz
  27   */
  28  
  29  /** XMPPHP_XMLStream */
  30  require_once dirname(__FILE__) . "/XMLStream.php";
  31  require_once dirname(__FILE__) . "/Roster.php";
  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 XMPPHP_XMPP extends XMPPHP_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->basejid = $this->user . '@' . $this->host;
 121  
 122  	 	 $this->roster = new Roster();
 123  	 	 $this->track_presence = true;
 124  
 125  	 	 $this->stream_start = '<stream:stream to="' . $server . '" xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" version="1.0">';
 126  	 	 $this->stream_end   = '</stream:stream>';
 127  	 	 $this->default_ns   = 'jabber:client';
 128  	 	 
 129  	 	 $this->addXPathHandler('{http://etherx.jabber.org/streams}features', 'features_handler');
 130  	 	 $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-sasl}success', 'sasl_success_handler');
 131  	 	 $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-sasl}failure', 'sasl_failure_handler');
 132  	 	 $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-tls}proceed', 'tls_proceed_handler');
 133  	 	 $this->addXPathHandler('{jabber:client}message', 'message_handler');
 134  	 	 $this->addXPathHandler('{jabber:client}presence', 'presence_handler');
 135  	 	 $this->addXPathHandler('iq/{jabber:iq:roster}query', 'roster_iq_handler');
 136  	 }
 137  
 138  	 /**
 139  	  * Turn encryption on/ff
 140  	  *
 141  	  * @param boolean $useEncryption
 142  	  */
 143  	public function useEncryption($useEncryption = true) {
 144  	 	 $this->use_encryption = $useEncryption;
 145  	 }
 146  	 
 147  	 /**
 148  	  * Turn on auto-authorization of subscription requests.
 149  	  *
 150  	  * @param boolean $autoSubscribe
 151  	  */
 152  	public function autoSubscribe($autoSubscribe = true) {
 153  	 	 $this->auto_subscribe = $autoSubscribe;
 154  	 }
 155  
 156  	 /**
 157  	  * Send XMPP Message
 158  	  *
 159  	  * @param string $to
 160  	  * @param string $body
 161  	  * @param string $type
 162  	  * @param string $subject
 163  	  */
 164  	public function message($to, $body, $type = 'chat', $subject = null, $payload = null) {
 165  	     if(is_null($type))
 166  	     {
 167  	         $type = 'chat';
 168  	     }
 169  	     
 170  	 	 $to	   = htmlspecialchars($to);
 171  	 	 $body	 = htmlspecialchars($body);
 172  	 	 $subject = htmlspecialchars($subject);
 173  	 	 
 174  	 	 $out = "<message from=\"{$this->fulljid}\" to=\"$to\" type='$type'>";
 175  	 	 if($subject) $out .= "<subject>$subject</subject>";
 176  	 	 $out .= "<body>$body</body>";
 177  	 	 if($payload) $out .= $payload;
 178  	 	 $out .= "</message>";
 179  	 	 
 180  	 	 $this->send($out);
 181  	 }
 182  
 183  	 /**
 184  	  * Set Presence
 185  	  *
 186  	  * @param string $status
 187  	  * @param string $show
 188  	  * @param string $to
 189  	  */
 190  	public function presence($status = null, $show = 'available', $to = null, $type='available', $priority=0) {
 191  	 	 if($type == 'available') $type = '';
 192  	 	 $to	  = htmlspecialchars($to);
 193  	 	 $status = htmlspecialchars($status);
 194  	 	 if($show == 'unavailable') $type = 'unavailable';
 195  	 	 
 196  	 	 $out = "<presence";
 197  	 	 if($to) $out .= " to=\"$to\"";
 198  	 	 if($type) $out .= " type='$type'";
 199  	 	 if($show == 'available' and !$status) {
 200  	 	 	 $out .= "/>";
 201  	 	 } else {
 202  	 	 	 $out .= ">";
 203  	 	 	 if($show != 'available') $out .= "<show>$show</show>";
 204  	 	 	 if($status) $out .= "<status>$status</status>";
 205  	 	 	 if($priority) $out .= "<priority>$priority</priority>";
 206  	 	 	 $out .= "</presence>";
 207  	 	 }
 208  	 	 
 209  	 	 $this->send($out);
 210  	 }
 211  	 /**
 212  	  * Send Auth request
 213  	  *
 214  	  * @param string $jid
 215  	  */
 216  	public function subscribe($jid) {
 217  	 	 $this->send("<presence type='subscribe' to='{$jid}' from='{$this->fulljid}' />");
 218  	 	 #$this->send("<presence type='subscribed' to='{$jid}' from='{$this->fulljid}' />");
 219  	 }
 220  
 221  	 /**
 222  	  * Message handler
 223  	  *
 224  	  * @param string $xml
 225  	  */
 226  	public function message_handler($xml) {
 227  	 	 if(isset($xml->attrs['type'])) {
 228  	 	 	 $payload['type'] = $xml->attrs['type'];
 229  	 	 } else {
 230  	 	 	 $payload['type'] = 'chat';
 231  	 	 }
 232  	 	 $payload['from'] = $xml->attrs['from'];
 233  	 	 $payload['body'] = $xml->sub('body')->data;
 234  	 	 $payload['xml'] = $xml;
 235  	 	 $this->log->log("Message: {$xml->sub('body')->data}", XMPPHP_Log::LEVEL_DEBUG);
 236  	 	 $this->event('message', $payload);
 237  	 }
 238  
 239  	 /**
 240  	  * Presence handler
 241  	  *
 242  	  * @param string $xml
 243  	  */
 244  	public function presence_handler($xml) {
 245  	 	 $payload['type'] = (isset($xml->attrs['type'])) ? $xml->attrs['type'] : 'available';
 246  	 	 $payload['show'] = (isset($xml->sub('show')->data)) ? $xml->sub('show')->data : $payload['type'];
 247  	 	 $payload['from'] = $xml->attrs['from'];
 248  	 	 $payload['status'] = (isset($xml->sub('status')->data)) ? $xml->sub('status')->data : '';
 249  	 	 $payload['priority'] = (isset($xml->sub('priority')->data)) ? intval($xml->sub('priority')->data) : 0;
 250  	 	 $payload['xml'] = $xml;
 251  	 	 if($this->track_presence) {
 252  	 	 	 $this->roster->setPresence($payload['from'], $payload['priority'], $payload['show'], $payload['status']);
 253  	 	 }
 254  	 	 $this->log->log("Presence: {$payload['from']} [{$payload['show']}] {$payload['status']}",  XMPPHP_Log::LEVEL_DEBUG);
 255  	 	 if(array_key_exists('type', $xml->attrs) and $xml->attrs['type'] == 'subscribe') {
 256  	 	 	 if($this->auto_subscribe) {
 257  	 	 	 	 $this->send("<presence type='subscribed' to='{$xml->attrs['from']}' from='{$this->fulljid}' />");
 258  	 	 	 	 $this->send("<presence type='subscribe' to='{$xml->attrs['from']}' from='{$this->fulljid}' />");
 259  	 	 	 }
 260  	 	 	 $this->event('subscription_requested', $payload);
 261  	 	 } elseif(array_key_exists('type', $xml->attrs) and $xml->attrs['type'] == 'subscribed') {
 262  	 	 	 $this->event('subscription_accepted', $payload);
 263  	 	 } else {
 264  	 	 	 $this->event('presence', $payload);
 265  	 	 }
 266  	 }
 267  
 268  	 /**
 269  	  * Features handler
 270  	  *
 271  	  * @param string $xml
 272  	  */
 273  	protected function features_handler($xml) {
 274  	 	 if($xml->hasSub('starttls') and $this->use_encryption) {
 275  	 	 	 $this->send("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'><required /></starttls>");
 276  	 	 } elseif($xml->hasSub('bind') and $this->authed) {
 277  	 	 	 $id = $this->getId();
 278  	 	 	 $this->addIdHandler($id, 'resource_bind_handler');
 279  	 	 	 $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>");
 280  	 	 } else {
 281  	 	 	 $this->log->log("Attempting Auth...");
 282  	 	 	 if ($this->password) {
 283  	 	 	 $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>" . base64_encode("\x00" . $this->user . "\x00" . $this->password) . "</auth>");
 284  	 	 	 } else {
 285                          $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='ANONYMOUS'/>");
 286  	 	 	 }	 
 287  	 	 }
 288  	 }
 289  
 290  	 /**
 291  	  * SASL success handler
 292  	  *
 293  	  * @param string $xml
 294  	  */
 295  	protected function sasl_success_handler($xml) {
 296  	 	 $this->log->log("Auth success!");
 297  	 	 $this->authed = true;
 298  	 	 $this->reset();
 299  	 }
 300  	 
 301  	 /**
 302  	  * SASL feature handler
 303  	  *
 304  	  * @param string $xml
 305  	  */
 306  	protected function sasl_failure_handler($xml) {
 307  	 	 $this->log->log("Auth failed!",  XMPPHP_Log::LEVEL_ERROR);
 308  	 	 $this->disconnect();
 309  	 	 
 310  	 	 throw new XMPPHP_Exception('Auth failed!');
 311  	 }
 312  
 313  	 /**
 314  	  * Resource bind handler
 315  	  *
 316  	  * @param string $xml
 317  	  */
 318  	protected function resource_bind_handler($xml) {
 319  	 	 if($xml->attrs['type'] == 'result') {
 320  	 	 	 $this->log->log("Bound to " . $xml->sub('bind')->sub('jid')->data);
 321  	 	 	 $this->fulljid = $xml->sub('bind')->sub('jid')->data;
 322  	 	 	 $jidarray = explode('/',$this->fulljid);
 323  	 	 	 $this->jid = $jidarray[0];
 324  	 	 }
 325  	 	 $id = $this->getId();
 326  	 	 $this->addIdHandler($id, 'session_start_handler');
 327  	 	 $this->send("<iq xmlns='jabber:client' type='set' id='$id'><session xmlns='urn:ietf:params:xml:ns:xmpp-session' /></iq>");
 328  	 }
 329  
 330  	 /**
 331  	 * Retrieves the roster
 332  	 *
 333  	 */
 334  	public function getRoster() {
 335  	 	 $id = $this->getID();
 336  	 	 $this->send("<iq xmlns='jabber:client' type='get' id='$id'><query xmlns='jabber:iq:roster' /></iq>");
 337  	 }
 338  
 339  	 /**
 340  	 * Roster iq handler
 341  	 * Gets all packets matching XPath "iq/{jabber:iq:roster}query'
 342  	 *
 343  	 * @param string $xml
 344  	 */
 345  	protected function roster_iq_handler($xml) {
 346  	 	 $status = "result";
 347  	 	 $xmlroster = $xml->sub('query');
 348  	 	 foreach($xmlroster->subs as $item) {
 349  	 	 	 $groups = array();
 350  	 	 	 if ($item->name == 'item') {
 351  	 	 	 	 $jid = $item->attrs['jid']; //REQUIRED
 352  	 	 	 	 $name = $item->attrs['name']; //MAY
 353  	 	 	 	 $subscription = $item->attrs['subscription'];
 354  	 	 	 	 foreach($item->subs as $subitem) {
 355  	 	 	 	 	 if ($subitem->name == 'group') {
 356  	 	 	 	 	 	 $groups[] = $subitem->data;
 357  	 	 	 	 	 }
 358  	 	 	 	 }
 359  	 	 	 	 $contacts[] = array($jid, $subscription, $name, $groups); //Store for action if no errors happen
 360  	 	 	 } else {
 361  	 	 	 	 $status = "error";
 362  	 	 	 }
 363  	 	 }
 364  	 	 if ($status == "result") { //No errors, add contacts
 365  	 	 	 foreach($contacts as $contact) {
 366  	 	 	 	 $this->roster->addContact($contact[0], $contact[1], $contact[2], $contact[3]);
 367  	 	 	 }
 368  	 	 }
 369  	 	 if ($xml->attrs['type'] == 'set') {
 370  	 	 	 $this->send("<iq type=\"reply\" id=\"{$xml->attrs['id']}\" to=\"{$xml->attrs['from']}\" />");
 371  	 	 }
 372  	 }
 373  
 374  	 /**
 375  	  * Session start handler
 376  	  *
 377  	  * @param string $xml
 378  	  */
 379  	protected function session_start_handler($xml) {
 380  	 	 $this->log->log("Session started");
 381  	 	 $this->session_started = true;
 382  	 	 $this->event('session_start');
 383  	 }
 384  
 385  	 /**
 386  	  * TLS proceed handler
 387  	  *
 388  	  * @param string $xml
 389  	  */
 390  	protected function tls_proceed_handler($xml) {
 391  	 	 $this->log->log("Starting TLS encryption");
 392  	 	 stream_socket_enable_crypto($this->socket, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT);
 393  	 	 $this->reset();
 394  	 }
 395  
 396  	 /**
 397  	 * Retrieves the vcard
 398  	 *
 399  	 */
 400  	public function getVCard($jid = Null) {
 401  	 	 $id = $this->getID();
 402  	 	 $this->addIdHandler($id, 'vcard_get_handler');
 403  	 	 if($jid) {
 404  	 	 	 $this->send("<iq type='get' id='$id' to='$jid'><vCard xmlns='vcard-temp' /></iq>");
 405  	 	 } else {
 406  	 	 	 $this->send("<iq type='get' id='$id'><vCard xmlns='vcard-temp' /></iq>");
 407  	 	 }
 408  	 }
 409  
 410  	 /**
 411  	 * VCard retrieval handler
 412  	 *
 413  	 * @param XML Object $xml
 414  	 */
 415  	protected function vcard_get_handler($xml) {
 416  	 	 $vcard_array = array();
 417  	 	 $vcard = $xml->sub('vcard');
 418  	 	 // go through all of the sub elements and add them to the vcard array
 419  	 	 foreach ($vcard->subs as $sub) {
 420  	 	 	 if ($sub->subs) {
 421  	 	 	 	 $vcard_array[$sub->name] = array();
 422  	 	 	 	 foreach ($sub->subs as $sub_child) {
 423  	 	 	 	 	 $vcard_array[$sub->name][$sub_child->name] = $sub_child->data;
 424  	 	 	 	 }
 425  	 	 	 } else {
 426  	 	 	 	 $vcard_array[$sub->name] = $sub->data;
 427  	 	 	 }
 428  	 	 }
 429  	 	 $vcard_array['from'] = $xml->attrs['from'];
 430  	 	 $this->event('vcard', $vcard_array);
 431  	 }
 432  }