DAViCal
 All Classes Namespaces Functions Variables Pages
pubsub.php
1 <?
2 
3 /**********************************************************************
4  * XMPP PubSub for DAViCal
5  * Copyright 2009 Rob Ostensen rob@boxacle.net
6  * Licenced http://gnu.org/copyleft/gpl.html GNU GPL v2
7  *
8  *********************************************************************/
9 
10 
11 class xmpp
12 {
13  private $connection,$streamTagBegin,$streamTagEnd,$mesgcount=0,$ready,$moredata=false,$username,$stream,$xmlparser,$xquery;
14  private $namespaces = Array();
15  private $recvTags = Array();
16  private $recvHandlers = Array();
17  private $sendHandlers = Array();
18  private $finishedCommands = Array();
19  private $sendQueue = Array();
20  private $recvQueue = '';
21  private $pubsubNext = Array();
22  private $depth = 0,$processDepth=0;
23  public $server,$port,$jid,$resource,$password,$tls,$idle,$status,$pubsubLayout='hometree';
24 
25  // constructor
26  public function __construct ( )
27  {
28  $this->status = "online";
29  $this->setupXmlParser ();
30  }
31 
32  // figure out what server to connect to and make the connection, returns true if successful, false otherwise
33  private function connect ()
34  {
35  if ( ! isset ( $this->jid ) )
36  return $this->connection = false;
37  if ( ! isset ( $this->idle ) )
38  $this->idle = true;
39  if ( ! isset ( $this->resource ) )
40  $this->resource = 'caldav' . getmypid();
41  if ( ! preg_match ( '/^\//', $this->resource ) )
42  $this->resource = '/' . $this->resource;
43  $temp = explode ( '@', $this->jid );
44  $this->username = $temp[0];
45  if ( ! isset ( $this->server ) )
46  {
47  $this->server = $temp[1];
48  }
49  $r = dns_get_record("_xmpp-client._tcp.". $this->server , DNS_SRV);
50  if ( 0 < count ( $r ) )
51  {
52  $this->original_server = $this->server;
53  $this->server = $r[0]['target'];
54  $this->original_port = $this->port;
55  $this->port = $r[0]['port'];
56  }
57  if ( ! isset ( $this->port ) )
58  $this->port = 5222;
59  if ( 'ssl' == $this->tls || ( ! isset ( $this->tls ) && 5223 == $this->port ) )
60  $url = 'ssl://' . $this->server;
61  elseif ( 'tls' == $this->tls || ( ! isset ( $this->tls ) && 5222 == $this->port ) )
62  $url = 'tcp://' . $this->server;
63  else
64  $url = 'tcp://' . $this->server;
65  if ( isset ( $this->original_server ) )
66  $this->server = $this->original_server;
67  $this->connection = stream_socket_client ( $url . ':' . $this->port, $errno, $errstring, 10, STREAM_CLIENT_ASYNC_CONNECT );
68  if ( false === $this->connection )
69  {
70  if ( $errno != 0 )
71  $log = $errstring;
72  return false;
73  }
74  $this->initializeQueue ( );
75  socket_set_blocking ( $this->connection, false );
76  return true;
77  }
78 
79  // handles the features tag, mostly related to authentication
80  private function handleFeatures ( &$node )
81  {
82  if ( $this->debug ) $this->log ( 'handling features' );
83  if ( 'STARTTLS' == $node->firstChild->nodeName )
84  {
85  $this->sendQueue[] = "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>";
86  return;
87  }
88  $elements = $this->query ( '*/MECHANISM', $node );
89  if ( ! is_null ( $elements ) && $elements !== false )
90  {
91  if ( $this->debug ) $this->log ( " found " . $elements->length . " matching MECHANISM nodes ");
92  $auth_mech = array ();
93  foreach ( $elements as $e )
94  $auth_mech[] = $e->nodeValue;
95  if ( in_array ( 'PLAIN', $auth_mech ) )
96  $this->sendQueue[] = "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>" . base64_encode("\x00" . preg_replace('/@.*$/','',$this->jid) . "\x00" . $this->password) . "</auth>";
97  elseif ( in_array ( 'DIGEST-MD5', $auth_mech ) ) // this code and the associated function are UNTESTED
98  {
99  $this->sendQueue[] = "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>";
100  $this->recvHandlers['challenge'] = 'digestAuth' ;
101  }
102  $this->recvHandlers['success'] = 'handleSuccess' ;
103  }
104  $elements = $this->query ( '*/BIND', $node );
105  if ( ! is_null ( $elements ) && $elements->length > 0 )
106  {
107  // failure if we don't hit this, not sure how we can detect that failure yet.
108  if ( $this->debug ) $this->log ( " found " . $elements->length . " matching BIND nodes ");
109  $this->ready = true;
110  }
111  }
112 
113  // handle proceed tag/enable tls
114  private function enableTLS ( $node )
115  {
116  stream_set_blocking ( $this->connection, true );
117  stream_socket_enable_crypto ( $this->connection, true, STREAM_CRYPTO_METHOD_TLS_CLIENT );
118  stream_set_blocking ( $this->connection, false );
119  $this->sendQueue[] = "<"."?xml version=\"1.0\"?".">\n\n<stream:stream to='" . $this->server . "' xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client' version='1.0'>";
120  }
121 
122  // do digest auth
123  private function digestAuth ( &$node )
124  {
125  // this code is based solely on the description found @ http://web.archive.org/web/20050224191820/http://cataclysm.cx/wip/digest-md5-crash.html
126  // UNTESTED please shoot me an email if you get this to work !!
127  $contents = $node->nodeValue;
128  if ( ! is_null ( $elements ) )
129  {
130  $challlenge = array ();
131  $parts = explode ( ',', base64_decode ( $contents ) );
132  foreach ( $parts as $text )
133  {
134  $temp = explode ( '=', $text );
135  $challenge[$temp[0]] = $temp[1];
136  }
137  if ( $challenge['realm'] == $this->server ) // might fail need to handle a response with multiple realms
138  {
139  $cnonce = md5((mt_rand() * time() / mt_rand())+$challenge['nonce']);
140  $X = md5 ( preg_replace('/@.*$/','',$this->jid) . ':' . $this->server . ':' . $this->password, true );
141  $HA1 = md5 ( $X . ':' . $challenge['nonce'] . ':' . $cnonce . ':' . $this->jid . $this->resource );
142  $HA2 = md5 ( "AUTHENTICATE:xmpp/" . $this->server );
143  $resp = md5 ( $HA1 . ':' . $challenge['nonce'] . ':00000001:' . $cnonce . ':auth' . $HA2 );
144  $this->sendQueue[] = "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" .
145  base64_encode("username=\"" . preg_replace('/@.*$/','',$this->jid) . "\"," .
146  "realm=\"" . $this->server . "\",nonce=\"" . $challenge['nonce'] . "\",cnonce=\"". $cnonce . "\"," .
147  "nc=00000001,qop=auth,digest-uri=\"xmpp/" . $this->server . "\",response=" . $resp .
148  ",charset=utf-8,authzid=\"". $this->jid . $this->resource . "\"" ) . "</response>" // note the PID component to the resource, just incase
149  ;
150  }
151  elseif ( $challenge['rspauth'] )
152  $this->sendQueue[] = "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>" ;
153  }
154  }
155 
156  // do basic setup to get the connection logged in and going
157  private function handleSuccess ( &$node )
158  {
159  $this->loggedIn = true;
160  $this->sendQueue[] = "<"."?xml version=\"1.0\"?".">\n\n<stream:stream to='" . $this->server . "' xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client' version='1.0'>";
161  $this->sendQueue[] = "<iq xmlns='jabber:client' type='set' id='1'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>" . preg_replace('/^\//','',$this->resource) . "</resource></bind></iq>";
162  $this->recvHandlers['stream:error'] = 'handleError' ;
163  $this->recvHandlers['iq'] = 'handleIq' ;
164  $this->recvHandlers['message'] = 'handleMessage' ;
165  $this->mesgcount = 1;
166  }
167 
168  // do something with standard iq messages also does some standard setup like setting presence
169  private function handleIq ( &$node )
170  {
171  if ( $this->debug ) $this->log ( "Handle IQ id:" . $node->getAttribute ( 'id' ) . ' type:' . $node->getAttribute ( 'type' ) . "");
172  if ( $node->getAttribute ( 'type' ) == 'result' || $node->getAttribute ( 'type' ) == 'error' )
173  {
174  $commandId = $node->getAttribute ( 'id' );
175  $this->command[$commandId] = true;
176  if ( isset ( $this->handleCommand[$commandId] ) )
177  {
178  $this->finishedCommands[$commandId] = true;
179  if ( method_exists ( $this, $this->handleCommand[$commandId] ) )
180  call_user_func_array ( array ( $this, $this->handleCommand[$commandId] ), array ( &$node ) );
181  else
182  call_user_func_array ( $this->handleCommand[$commandId], array ( &$node ) );
183  }
184  }
185  if ( $node->getAttribute ( 'id' ) == $this->mesgcount && $this->mesgcount < 3 )
186  {
187  $this->sendQueue[] = "<iq xmlns='jabber:client' type='set' id='" . ( $this->mesgcount++ ) . "'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>";
188  $this->sendQueue[] = "<iq xmlns='jabber:client' type='get' id='" . ( $this->mesgcount++ ) . "'><query xmlns='jabber:iq:roster' /></iq>";
189  }
190  if ( $node->getAttribute ( 'id' ) == '2' && $this->command['2'] == true )
191  {
192  $this->nextreply = $this->mesgcount++;
193  $this->sendQueue[] = "<presence id='" . $this->nextreply . "' ><status>" . $this->status . '</status></presence>';
194  $this->ready = true;
195  }
196  }
197 
198  // do something with standard messages
199  private function handleMessage ( &$node )
200  {
201  if ( $node->getAttribute ( 'type' ) == 'chat' )
202  {
203  $this->command[$node->getAttribute ( 'id' )] = true;
204  $elements = $this->query ( '//*/body', $node );
205  if ( 0 < $elements->length )
206  {
207  $temp = $elements->items(0);
208  if ( $this->debug ) $this->log ( "received message " . $temp->nodeValue );
209  }
210  }
211  }
212 
213  // handle stream errors by logging a message and closing the connection
214  private function handleError ( &$node )
215  {
216  $this->log ( 'STREAM ERROR OCCURRED! XMPP closing connection, this is probably a bug' );
217  $this->idle = false;
218  $this->close ();
219  }
220 
221  // disco a pubsub collection
222  private function disco ( $to, $type, $name )
223  {
224  $msg = $this->mesgcount++;
225  $send = "<iq type='get' from='" . $this->jid . $this->resource . "' to='$to' id='" . $msg . "'>";
226  $send .= " <query xmlns='http://jabber.org/protocol/disco#$type' node='$name'/>";
227  $send .= "</iq>";
228  $this->handleCommand[$msg] = 'discoResult';
229  $this->sendQueue[] = $send;
230  $this->go();
231  }
232 
233  // result from disco
234  private function discoResult ( &$node )
235  {
236  if ( $this->debug ) $this->log ( $node->ownerDocument->saveXML($node) );
237  $id = $node->getAttribute ( 'id' );
238  $identity = $this->query ( '*/IDENTITY', $node );
239  if ( @is_array ( $this->pubsub [ 'create' ] [ $id ] ) && 0 == $identity->length )
240  {
241  $this->pubsubCreateNode( $this->pubsub [ 'create' ] [ $id ] [ 0 ],
242  $this->pubsub [ 'create' ] [ $id ] [ 1 ],
243  $this->pubsub [ 'create' ] [ $id ] [ 2 ],
244  $this->pubsub [ 'create' ] [ $id ] [ 3 ] );
245  }
246  }
247 
248  // send a message to a jid
249  public function sendMessage ( $to, $message )
250  {
251  $msg = $this->mesgcount++;
252  $out .= "<message id='" . $msg . "' from='" . $this->jid . $this->resource . "' to='" . $to. "' >";
253  $out .= "<body>" . $message . "</body></message>";
254  $this->sendQueue[] = $out;
255  $this->go();
256  }
257 
258  // get a pubsub collection/leaf node and create if it doesn't exist
259  public function pubsubCreate ( $to, $type, $name, $configure = null )
260  {
261  if ( 1 > strlen ( $to ) )
262  $to = 'pubsub.' . $this->server;
263  if ( 1 > strlen ( $type ) )
264  $type = 'set';
265  if ( 'hometree' == $this->pubsubLayout )
266  $node = '/home/' . $this->server . '/' . $this->username . $name;
267  else
268  $node= $name;
269  $this->pubsub['create'][$this->mesgcount+1] = array ( $to, $type, $name, $configure );
270  $this->disco ( $to, 'info', $node );
271  }
272 
273  // create a pubsub collection/leaf node
274  private function pubsubCreateNode ( $to, $type, $name, $configure = null )
275  {
276  if ( 'hometree' == $this->pubsubLayout )
277  $node = '/home/' . $this->server . '/' . $this->username . $name;
278  else
279  $node= $name;
280  $msg = $this->mesgcount++;
281  $out = '<iq from="' . $this->jid . $this->resource . '" to="' . $to . '" type="' . $type . '" id="' . $msg . '">';
282  $out .= '<pubsub xmlns="http://jabber.org/protocol/pubsub" ><create node="' . $node . '"/>';
283  if ( $configure )
284  $out .= '<configure>' . $configure .' </configure>';
285  else
286  $out .= '<configure/>';
287  $out .= '</pubsub>';
288  $out .= '</iq>';
289  $this->sendQueue[] = $out;
290  $this->handleCommand[ $msg ] = 'pubsubResult';
291  $this->go();
292  }
293 
294  // configure a pubsub collection or leaf
295  public function pubsubConfig ( $to, $type, $name )
296  {
297  if ( 'hometree' == $this->pubsubLayout )
298  $node = '/home/' . $this->server . '/' . $this->username . $name;
299  else
300  $node= $name;
301  $msg = $this->mesgcount++;
302  $out = '<iq from="' . $this->jid . $this->resource . '" to="' . $to . '" type="get" id="' . $msg . '">';
303  $out .= '<pubsub xmlns="http://jabber.org/protocol/pubsub#owner" ><configure node="' . $node . '"/>';
304  $out .= '</pubsub>';
305  $out .= '</iq>';
306  $this->handleCommand[ $msg ] = 'pubsubResult';
307  $this->sendQueue[] = $out;
308  $this->go();
309  }
310 
311  // delete a pubsub collection or leaf
312  public function pubsubDelete ( $to, $type, $name )
313  {
314  if ( 'hometree' == $this->pubsubLayout )
315  $node = '/home/' . $this->server . '/' . $this->username . $name;
316  else
317  $node= $name;
318  $msg = $this->mesgcount++;
319  $out = '<iq from="' . $this->jid . $this->resource . '" to="' . $to . '" type="' . $type . '" id="' . $msg . '">';
320  $out .= '<pubsub xmlns="http://jabber.org/protocol/pubsub#owner"><delete node="' . $node . '"/>';
321  $out .= '</pubsub>';
322  $out .= '</iq>';
323  $this->handleCommand[ $msg ] = 'pubsubResult';
324  $this->sendQueue[] = $out;
325  $this->go();
326  }
327 
328  // purge a pubsub collection or leaf
329  public function pubsubPurge ( $to, $type, $name )
330  {
331  if ( 'hometree' == $this->pubsubLayout )
332  $node = '/home/' . $this->server . '/' . $this->username . $name;
333  else
334  $node= $name;
335  $msg = $this->mesgcount++;
336  $out = '<iq from="' . $this->jid . $this->resource . '" to="' . $to . '" type="' . $type . '" id="' . $msg . '">';
337  $out .= '<pubsub xmlns="http://jabber.org/protocol/pubsub#owner"><purge node="' . $node . '"/>';
338  $out .= '</pubsub>';
339  $out .= '</iq>';
340  $this->handleCommand[ $msg ] = 'pubsubResult';
341  $this->sendQueue[] = $out;
342  $this->go();
343  }
344 
345  // publish to a pubsub collection
346  public function pubsubPublish ( $to, $type, $name, $contents, $nodeId )
347  {
348  if ( 1 > strlen ( $to ) )
349  $to = 'pubsub.' . $this->server;
350  if ( 1 > strlen ( $type ) )
351  $type = 'set';
352  if ( 1 > strlen ( $nodeId ) )
353  $id = "id='$nodeId'";
354  else
355  $id = '';
356  if ( 'hometree' == $this->pubsubLayout )
357  $node = '/home/' . $this->server . '/' . $this->username . $name;
358  else
359  $node= $name;
360  $msg = $this->mesgcount++;
361  $out = '<iq from="' . $this->jid . $this->resource . '" to="' . $to . '" type="' . $type . '" id="' . $msg . '">';
362  $out .= '<pubsub xmlns="http://jabber.org/protocol/pubsub"><publish node="' . $node . '">';
363  if ( preg_match ( '/^<item/', $contents ) )
364  $out .= $contents;
365  else
366  $out .= '<item ' . $id . '>' . $contents . '</item>';
367  $out .= '</publish></pubsub>';
368  $out .= '</iq>';
369  $this->sendQueue[] = $out;
370  $this->handleCommand[ $msg ] = 'pubsubResult';
371  $this->go();
372  }
373 
374  // subscribe to a pubsub collection,leaf or item
375  private function pubsubSubscribe ( $to, $type, $name )
376  {
377  $msg = $this->mesgcount++;
378  if ( 'hometree' == $this->pubsubLayout )
379  $node = '/home/' . $this->server . '/' . $this->username . $name;
380  else
381  $node= $name;
382  $out = '<iq from="' . $this->jid . $this->resource . '" to="' . $to . '" type="' . $type . '" id="' . $msg . '">';
383  $out .= '<pubsub xmlns="http://jabber.org/protocol/pubsub"><subscribe node="' . $name . '" jid="' . $this->jid . $this->resource . '"/>';
384  $out .= '</pubsub>';
385  $out .= '</iq>';
386  $this->sendQueue[] = $out;
387  $this->handleCommand[ $msg ] = 'pubsubResult';
388  $this->go();
389  }
390 
391  private function pubsubResult ( &$node )
392  {
393  if ( $this->debug ) $this->log ( "pubsub RESULT !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
394  if ( $node->getAttribute ( 'type' ) == 'error' )
395  {
396  $errnode = $this->query ( 'ERROR', $node );
397  if ( $errnode->length > 0 && ( '403' == $errnode->item( 0 )->getAttribute ( 'code' ) || '500' == $errnode->item( 0 )->getAttribute ( 'code' ) ) )
398  {
399  if ( 'CREATE' == $node->firstChild->firstChild->tagName )
400  {
401  $pubnode = $node->firstChild->firstChild->getAttribute ( 'node' );
402  if ( $this->debug ) $this->log ( "403 error during CREATE for node '" . $pubnode . "' ");
403  $name = preg_replace ( '/^.*?\/' . $this->username . '\//','', $pubnode );
404  $newnode = '';
405  if ( ! in_array ( 'create', $this->pubsubNext ) )
406  {
407  $a = array ( );
408  foreach ( explode ( '/', $name ) as $v )
409  {
410  $newnode .= '/' . $v;
411  $a[] = array (
412  'call' => 'create',
413  'to' => $node->getAttribute ( 'from' ),
414  'name' => $newnode );
415  }
416  foreach ( array_reverse ( $a ) as $v )
417  array_unshift ( $this->pubsubNext, $v );
418  $this->pubsubDoNext ( );
419  }
420  }
421  }
422  elseif ( $errnode->length > 0 && '404' == $errnode->item( 0 )->getAttribute ( 'code' ) )
423  {
424  if ( 'PUBLISH' == $node->firstChild->firstChild->tagName )
425  {
426  $pubnode = $node->firstChild->firstChild->getAttribute ( 'node' );
427  if ( $this->debug ) $this->log ( "404 error during PUBLISH for node '" . $pubnode . "' ");
428  $publish = $this->query ( '//*/PUBLISH', $node );
429  $this->pubsubNext[] = array (
430  'call' => 'publish',
431  'to' => $node->getAttribute ( 'from' ),
432  'name' => preg_replace ( '/^.*?\/' . $this->username . '/','', $pubnode ) ,
433  'contents' => $publish->item( 0 )->firstChild->nodeValue );
434  if ( $this->debug ) $this->log ( "attempting to create node '" . $this->pubsubNext[0]['name'] . "' ");
435  $this->pubsubCreateNode ( $node->getAttribute ( 'from' ) ,'set', preg_replace ( '/^.*?\/' . $this->username . '/','', $pubnode ) );
436  }
437  }
438  elseif ( $errnode->length > 0 && '409' == $errnode->item( 0 )->getAttribute ( 'code' ) )
439  {
440  if ( 'CANCEL' == $errnode->item( 0 )->firstChild->tagName || 'CONFLICT' == $errnode->item( 0 )->firstChild->tagName )
441  $this->pubsubDoNext ( );
442  }
443  }
444  elseif ( 0 < count ( $this->pubsubNext ) )
445  $this->pubsubDoNext ( );
446  }
447 
448  // do next pubsub request
449  private function pubsubDoNext ( )
450  {
451  if ( 0 < count ( $this->pubsubNext ) )
452  {
453  $pub = array_shift ( $this->pubsubNext );
454  if ( 'publish' == $pub['call'] )
455  {
456  if ( $this->debug ) $this->log ( "attempting to publish to node '" . $pub['name'] . "' contents '" . $pub['contents'] . "'");
457  $this->pubsubPublish ( $pub[$to], 'set', $pub['name'], $pub['contents'] );
458  }
459  if ( 'create' == $pub['call'] )
460  {
461  if ( $this->debug ) $this->log ( "attempting to create node '" . $pub['name'] . "' ");
462  $this->pubsubCreateNode ( $pub[$to], 'set', $pub['name'] );
463  }
464  }
465  }
466 
467  // do basic setup to get the connection logged in and going
468  private function initializeQueue ( )
469  {
470  $this->loggedIn = false;
471  $this->streamTagBegin = '<'."?xml version='1.0'?"."><stream:stream to='" . $this->server . "' xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client' version='1.0'>";
472  $this->streamTagEnd = '</stream:stream>';
473  $this->sendQueue[] = $this->streamTagBegin;
474  $this->recvHandlers['stream:features'] = 'handleFeatures' ;
475  $this->recvHandlers['features'] = 'handleFeatures' ;
476  $this->recvHandlers['proceed'] = 'enableTLS' ;
477  }
478 
479  // send data out the socket
480  private function send ( $data )
481  {
482  $len = strlen ( $data );
483  if ( $this->debug ) $this->log ( "SEND: $data");
484  if ( false !== $this->connection )
485  {
486  if ( fwrite ( $this->connection, $data, $len) === $len )
487  return true;
488  else
489  return false;
490  }
491  return false;
492  }
493 
494  // receive any data waiting on the socket
495  private function recv ()
496  {
497  if ( false !== $this->connection )
498  {
499  $data = '';
500  $data = fgets ( $this->connection, 4096 );
501  if ( 4094 < strlen ( $data ) )
502  {
503  $count = 0;
504  while ( 0 != strlen ( $moredata = fgets ( $this->connection, 1024 ) ) && 20 < $count++ )
505  {
506  $data .= $moredata;
507  usleep ( 10 );
508  }
509  }
510  if ( 0 < strlen ( $data ) )
511  {
512  $data = preg_replace ( '/^<\?xml version=\'1.0\'\?'.'>/', '', $data );
513  $this->stream .= $data;
514  if ( $this->debug ) $this->log ( "RECV: $data" );
515  return $data;
516  }
517  else
518  return false;
519  }
520  return false;
521  }
522 
523  private function go ()
524  {
525  $this->recvQueue = implode ( '', $this->sendQueue );
526  $count = 0;
527  $this->moredata = false;
528  while ( false !== $this->connection )
529  {
530  if ( 0 < count ( $this->sendQueue ) )
531  {
532  $count = 0;
533  while ( $data = array_shift ( $this->sendQueue ) )
534  $this->send ( $data );
535  }
536  $data = $this->recv ( );
537  xml_parse ( $this->xmlparser, $data, false );
538  while ( $rnode = array_shift ( $this->recvTags ) )
539  {
540  $rname = strtolower ( $rnode->localName );
541  if ( $this->debug ) $this->log ( " processing $rname ");
542  if ( isset ( $this->recvHandlers[$rname] ) ) //&& is_callable ( $this->recvHandlers[$r->name] ) )
543  {
544  if ( method_exists ( $this, $this->recvHandlers[$rname] ) )
545  call_user_func_array ( array ( $this, $this->recvHandlers[$rname] ), array ( &$rnode ) );
546  else
547  call_user_func_array ( $this->recvHandlers[$rname], array ( &$rnode ) );
548  }
549  }
550  $count++;
551  if ( $count > 20 )
552  {
553  if ( $this->idle === true )
554  {
555  $count = 0;
556  usleep ( 200 );
557  }
558  else
559  {
560  if ( $this->ready == true && count ( $this->handleCommand ) <= count ( $this->command ) )
561  {
562  $count = 0;
563  return ;
564  }
565  }
566  }
567  else
568  usleep ( 20 );
569  }
570  }
571 
572 
573  // xml parser start element
574  private function startElement ( $parser, $name, $attrs )
575  {
576  $this->depth++;
577  $namespace = '';
578 
579  if ( 'STREAM:STREAM' == $name )
580  $this->processDepth++;
581  foreach ( $attrs as $k => $v )
582  if ( preg_match ( '/^xmlns:?(.*)/i', $k, $matches ) )
583  {
584  if ( strlen ( $matches[1] ) > 0 && ! isset ( $this->namespaces [ $matches[1] ] ) )
585  {
586  $this->xquery->registerNamespace ( $matches[1], $v );
587  $this->namespaces [ $matches[1] ] = $v;
588  $namespace = $v;
589  if ( $this->debug ) $this->log ( " adding namespace $k => $v ");
590  }
591  }
592  if ( $namespace != '' )
593  $node = $this->doc->createElementNS ( $namespace, $name );
594  else
595  $node = $this->doc->createElement ( $name );
596  foreach ( $attrs as $k => $v )
597  $node->setAttribute ( strtolower ( $k ), $v );
598  $this->currentXMLNode = $this->currentXMLNode->appendChild ( $node );
599  }
600 
601  // xml parser start element
602  private function endElement ( $parser, $name )
603  {
604  $this->depth--;
605  //if ( $this->debug ) $this->log ( "depth: " . $this->depth . " processDepth: " . $this->processDepth . " ");
606  if ( $this->depth == $this->processDepth || 'STREAM:STREAM' == $name || 'STREAM:FEATURES' == $name || 'PROCEED' == $name )
607  {
608  if ( $this->debug ) $this->log ( " adding $name to tags to process ");
609  array_push ( $this->recvTags, $this->currentXMLNode ); // replace with tag
610  }
611  $this->currentXMLNode = $this->currentXMLNode->parentNode;
612  }
613 
614  // xml parser start element
615  private function parseData ( $parser, $text )
616  {
617  $this->currentXMLNode->appendChild ( $this->doc->createTextNode ( $text ) );
618  }
619 
620  // xml parser start element
621  private function setupXmlParser ( )
622  {
623  $this->depth = 0;
624  $this->xmlparser = xml_parser_create ( );
625  xml_set_object ( $this->xmlparser, $this );
626  xml_set_element_handler ( $this->xmlparser, 'startElement', 'endElement' );
627  xml_set_character_data_handler ( $this->xmlparser, 'parseData' );
628  $this->doc = new DOMDocument ();
629  $this->xquery = new DOMXpath ( $this->doc );
630  $this->xquery->registerNamespace ( 'stream', 'http://etherx.jabber.org/streams' );
631  $this->currentXMLNode = $this->doc->appendChild ( $this->doc->createElement ( 'start' ) );
632  }
633 
634  // xml XPath query
635  private function query ( $expression, &$node = '' )
636  {
637  if ( '' == $node )
638  return $this->xquery->query ( $expression );
639  else
640  return $this->xquery->query ( $expression , $node );
641  }
642 
643 
644  // open xmpp connection, will accept jid and password
645  public function open ( $jid = null, $password = null)
646  {
647  if ( null != $jid )
648  $this->jid = $jid;
649  if ( null != $password )
650  $this->password = $password;
651  $this->ready = false;
652  if ( false !== $this->connect () )
653  {
654  sleep(2);
655  $this->go ();
656  }
657  else
658  return false;
659  return true;
660  }
661 
662  public function close ()
663  {
664  if ( false !== $this->connection )
665  {
666  $this->send ( '</stream:stream>');
667  fclose ( $this->connection );
668  $this->connection = false;
669  }
670  }
671 
672  // add a send or recv handler, direction = [ send | recv ], command = command to handle, handler = function ref
673  public function addHandler ( $direction, $command, $handler )
674  {
675  if ( 'send' == $direction )
676  $this->sendHandler[$command] = $handler;
677  if ( 'recv' == $direction )
678  $this->recvHandler[$command] = $handler;
679  }
680 
681  // handle logging
682  private function log ( $message )
683  {
684  error_log ( 'XMPP: ' . $message );
685  //echo 'XMPP: ' . $message . "\n";
686  }
687 }
688 
689 
698 function log_caldav_action( $action_type, $uid, $user_no, $collection_id, $dav_name )
699 {
700  global $c;
701  $t = new xmpp();
702  $t->tls = 'none';
703  $t->idle = false;
704  if ( 1 == $c->dbg["ALL"] || 1 == $c->dbg["push"] )
705  $t->debug = true ;
706  else
707  $t->debug = false ;
708  // for now use a flat node tree layout
709  $t->pubsubLayout = 'flat';
710  // get the principal_id for this collection, that's what the client will be looking for
711  $qry = new AwlQuery ('SELECT principal_id FROM principal JOIN collection USING (user_no) WHERE collection_id= :collection_id',
712  array( ':collection_id' => $collection_id ) );
713  $qry->Exec('pubsub');
714  $row = $qry->Fetch();
715 
716  $t->open ( $c->notifications_server['jid'], $c->notifications_server['password'] );
717  if ( isset ( $c->notifications_server['debug_jid'] ) )
718  $t->sendMessage ( $c->notifications_server['debug_jid'], "ACTION: $action_type\nUSER: $user_no\nDAV NAME: $dav_name\nPRINCIPAL ID: " . $row->principal_id );
719  $t->pubsubCreate ( '', 'set', '/davical-' . $row->principal_id, '<x xmlns="jabber:x:data" type="submit"><field var="FORM_TYPE" type="hidden"><value>http://jabber.org/protocol/pubsub#node_config</value></field><field var="pubsub#access_model"><value>open</value></field><field var=\'pubsub#type\'>plist-apple<value></value></field></x>' );
720  $t->pubsubPublish ( '', 'set', '/davical-' . $row->principal_id , '<item xmlns="plist-apple" id="' . $uid . ' " ><plistfrag xmlns="plist-apple"><key>davical</key><string>' . $uid . '</string></plistfrag></item>', $uid );
721  $t->close();
722 }
723 
Definition: pubsub.php:11