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