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
8 *********************************************************************/
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';
26 public function __construct ( )
28 $this->status
= "online";
29 $this->setupXmlParser ();
32 // figure out what server to connect to and make the connection, returns true if successful, false otherwise
33 private function connect ()
35 if ( ! isset ( $this->jid
) )
36 return $this->connection
= false;
37 if ( ! isset ( $this->idle
) )
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
) )
47 $this->server
= $temp[1];
49 $r = dns_get_record("_xmpp-client._tcp.". $this->server
, DNS_SRV
);
50 if ( 0 < count ( $r ) )
52 $this->original_server
= $this->server
;
53 $this->server
= $r[0]['target'];
54 $this->original_port
= $this->port
;
55 $this->port
= $r[0]['port'];
57 if ( ! isset ( $this->port
) )
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
;
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
)
74 $this->initializeQueue ( );
75 socket_set_blocking ( $this->connection
, false );
79 // handles the features tag, mostly related to authentication
80 private function handleFeatures ( &$node )
82 if ( $this->debug
) $this->log ( 'handling features' );
83 if ( 'STARTTLS' == $node->firstChild
->nodeName
)
85 $this->sendQueue
[] = "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>";
88 $elements = $this->query ( '*/MECHANISM', $node );
89 if ( ! is_null ( $elements ) && $elements !== false )
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
99 $this->sendQueue
[] = "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>";
100 $this->recvHandlers
['challenge'] = 'digestAuth' ;
102 $this->recvHandlers
['success'] = 'handleSuccess' ;
104 $elements = $this->query ( '*/BIND', $node );
105 if ( ! is_null ( $elements ) && $elements->length
> 0 )
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 ");
113 // handle proceed tag/enable tls
114 private function enableTLS ( $node )
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'>";
123 private function digestAuth ( &$node )
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 ) )
130 $challlenge = array ();
131 $parts = explode ( ',', base64_decode ( $contents ) );
132 foreach ( $parts as $text )
134 $temp = explode ( '=', $text );
135 $challenge[$temp[0]] = $temp[1];
137 if ( $challenge['realm'] == $this->server
) // might fail need to handle a response with multiple realms
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
151 elseif ( $challenge['rspauth'] )
152 $this->sendQueue
[] = "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>" ;
156 // do basic setup to get the connection logged in and going
157 private function handleSuccess ( &$node )
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;
168 // do something with standard iq messages also does some standard setup like setting presence
169 private function handleIq ( &$node )
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' )
174 $commandId = $node->getAttribute ( 'id' );
175 $this->command
[$commandId] = true;
176 if ( isset ( $this->handleCommand
[$commandId] ) )
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 ) );
182 call_user_func_array ( $this->handleCommand
[$commandId], array ( &$node ) );
185 if ( $node->getAttribute ( 'id' ) == $this->mesgcount
&& $this->mesgcount
< 3 )
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>";
190 if ( $node->getAttribute ( 'id' ) == '2' && $this->command
['2'] == true )
192 $this->nextreply
= $this->mesgcount++
;
193 $this->sendQueue
[] = "<presence id='" . $this->nextreply
. "' ><status>" . $this->status
. '</status></presence>';
198 // do something with standard messages
199 private function handleMessage ( &$node )
201 if ( $node->getAttribute ( 'type' ) == 'chat' )
203 $this->command
[$node->getAttribute ( 'id' )] = true;
204 $elements = $this->query ( '//*/body', $node );
205 if ( 0 < $elements->length
)
207 $temp = $elements->items(0);
208 if ( $this->debug
) $this->log ( "received message " . $temp->nodeValue
);
213 // handle stream errors by logging a message and closing the connection
214 private function handleError ( &$node )
216 $this->log ( 'STREAM ERROR OCCURRED! XMPP closing connection, this is probably a bug' );
221 // disco a pubsub collection
222 private function disco ( $to, $type, $name )
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'/>";
228 $this->handleCommand
[$msg] = 'discoResult';
229 $this->sendQueue
[] = $send;
234 private function discoResult ( &$node )
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
)
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 ] );
248 // send a message to a jid
249 public function sendMessage ( $to, $message )
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;
258 // get a pubsub collection/leaf node and create if it doesn't exist
259 public function pubsubCreate ( $to, $type, $name, $configure = null )
261 if ( 1 > strlen ( $to ) )
262 $to = 'pubsub.' . $this->server
;
263 if ( 1 > strlen ( $type ) )
265 if ( 'hometree' == $this->pubsubLayout
)
266 $node = '/home/' . $this->server
. '/' . $this->username
. $name;
269 $this->pubsub
['create'][$this->mesgcount+
1] = array ( $to, $type, $name, $configure );
270 $this->disco ( $to, 'info', $node );
273 // create a pubsub collection/leaf node
274 private function pubsubCreateNode ( $to, $type, $name, $configure = null )
276 if ( 'hometree' == $this->pubsubLayout
)
277 $node = '/home/' . $this->server
. '/' . $this->username
. $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 . '"/>';
284 $out .= '<configure>' . $configure .' </configure>';
286 $out .= '<configure/>';
289 $this->sendQueue
[] = $out;
290 $this->handleCommand
[ $msg ] = 'pubsubResult';
294 // configure a pubsub collection or leaf
295 public function pubsubConfig ( $to, $type, $name )
297 if ( 'hometree' == $this->pubsubLayout
)
298 $node = '/home/' . $this->server
. '/' . $this->username
. $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 . '"/>';
306 $this->handleCommand
[ $msg ] = 'pubsubResult';
307 $this->sendQueue
[] = $out;
311 // delete a pubsub collection or leaf
312 public function pubsubDelete ( $to, $type, $name )
314 if ( 'hometree' == $this->pubsubLayout
)
315 $node = '/home/' . $this->server
. '/' . $this->username
. $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 . '"/>';
323 $this->handleCommand
[ $msg ] = 'pubsubResult';
324 $this->sendQueue
[] = $out;
328 // purge a pubsub collection or leaf
329 public function pubsubPurge ( $to, $type, $name )
331 if ( 'hometree' == $this->pubsubLayout
)
332 $node = '/home/' . $this->server
. '/' . $this->username
. $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 . '"/>';
340 $this->handleCommand
[ $msg ] = 'pubsubResult';
341 $this->sendQueue
[] = $out;
345 // publish to a pubsub collection
346 public function pubsubPublish ( $to, $type, $name, $contents, $nodeId )
348 if ( 1 > strlen ( $to ) )
349 $to = 'pubsub.' . $this->server
;
350 if ( 1 > strlen ( $type ) )
352 if ( 1 > strlen ( $nodeId ) )
353 $id = "id='$nodeId'";
356 if ( 'hometree' == $this->pubsubLayout
)
357 $node = '/home/' . $this->server
. '/' . $this->username
. $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 ) )
366 $out .= '<item ' . $id . '>' . $contents . '</item>';
367 $out .= '</publish></pubsub>';
369 $this->sendQueue
[] = $out;
370 $this->handleCommand
[ $msg ] = 'pubsubResult';
374 // subscribe to a pubsub collection,leaf or item
375 private function pubsubSubscribe ( $to, $type, $name )
377 $msg = $this->mesgcount++
;
378 if ( 'hometree' == $this->pubsubLayout
)
379 $node = '/home/' . $this->server
. '/' . $this->username
. $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 . '"/>';
386 $this->sendQueue
[] = $out;
387 $this->handleCommand
[ $msg ] = 'pubsubResult';
391 private function pubsubResult ( &$node )
393 if ( $this->debug
) $this->log ( "pubsub RESULT !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
394 if ( $node->getAttribute ( 'type' ) == 'error' )
396 $errnode = $this->query ( 'ERROR', $node );
397 if ( $errnode->length
> 0 && ( '403' == $errnode->item( 0 )->getAttribute ( 'code' ) ||
'500' == $errnode->item( 0 )->getAttribute ( 'code' ) ) )
399 if ( 'CREATE' == $node->firstChild
->firstChild
->tagName
)
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 );
405 if ( ! in_array ( 'create', $this->pubsubNext
) )
408 foreach ( explode ( '/', $name ) as $v )
410 $newnode .= '/' . $v;
413 'to' => $node->getAttribute ( 'from' ),
414 'name' => $newnode );
416 foreach ( array_reverse ( $a ) as $v )
417 array_unshift ( $this->pubsubNext
, $v );
418 $this->pubsubDoNext ( );
422 elseif ( $errnode->length
> 0 && '404' == $errnode->item( 0 )->getAttribute ( 'code' ) )
424 if ( 'PUBLISH' == $node->firstChild
->firstChild
->tagName
)
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 (
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 ) );
438 elseif ( $errnode->length
> 0 && '409' == $errnode->item( 0 )->getAttribute ( 'code' ) )
440 if ( 'CANCEL' == $errnode->item( 0 )->firstChild
->tagName ||
'CONFLICT' == $errnode->item( 0 )->firstChild
->tagName
)
441 $this->pubsubDoNext ( );
444 elseif ( 0 < count ( $this->pubsubNext
) )
445 $this->pubsubDoNext ( );
448 // do next pubsub request
449 private function pubsubDoNext ( )
451 if ( 0 < count ( $this->pubsubNext
) )
453 $pub = array_shift ( $this->pubsubNext
);
454 if ( 'publish' == $pub['call'] )
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'] );
459 if ( 'create' == $pub['call'] )
461 if ( $this->debug
) $this->log ( "attempting to create node '" . $pub['name'] . "' ");
462 $this->pubsubCreateNode ( $pub[$to], 'set', $pub['name'] );
467 // do basic setup to get the connection logged in and going
468 private function initializeQueue ( )
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' ;
479 // send data out the socket
480 private function send ( $data )
482 $len = strlen ( $data );
483 if ( $this->debug
) $this->log ( "SEND: $data");
484 if ( false !== $this->connection
)
486 if ( fwrite ( $this->connection
, $data, $len) === $len )
494 // receive any data waiting on the socket
495 private function recv ()
497 if ( false !== $this->connection
)
500 $data = fgets ( $this->connection
, 4096 );
501 if ( 4094 < strlen ( $data ) )
504 while ( 0 != strlen ( $moredata = fgets ( $this->connection
, 1024 ) ) && 20 < $count++
)
510 if ( 0 < strlen ( $data ) )
512 $data = preg_replace ( '/^<\?xml version=\'1.0\'\?'.'>/', '', $data );
513 $this->stream
.= $data;
514 if ( $this->debug
) $this->log ( "RECV: $data" );
523 private function go ()
525 $this->recvQueue
= implode ( '', $this->sendQueue
);
527 $this->moredata
= false;
528 while ( false !== $this->connection
)
530 if ( 0 < count ( $this->sendQueue
) )
533 while ( $data = array_shift ( $this->sendQueue
) )
534 $this->send ( $data );
536 $data = $this->recv ( );
537 xml_parse ( $this->xmlparser
, $data, false );
538 while ( $rnode = array_shift ( $this->recvTags
) )
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] ) )
544 if ( method_exists ( $this, $this->recvHandlers
[$rname] ) )
545 call_user_func_array ( array ( $this, $this->recvHandlers
[$rname] ), array ( &$rnode ) );
547 call_user_func_array ( $this->recvHandlers
[$rname], array ( &$rnode ) );
553 if ( $this->idle
=== true )
560 if ( $this->ready
== true && count ( $this->handleCommand
) <= count ( $this->command
) )
573 // xml parser start element
574 private function startElement ( $parser, $name, $attrs )
579 if ( 'STREAM:STREAM' == $name )
580 $this->processDepth++
;
581 foreach ( $attrs as $k => $v )
582 if ( preg_match ( '/^xmlns:?(.*)/i', $k, $matches ) )
584 if ( strlen ( $matches[1] ) > 0 && ! isset ( $this->namespaces
[ $matches[1] ] ) )
586 $this->xquery
->registerNamespace ( $matches[1], $v );
587 $this->namespaces
[ $matches[1] ] = $v;
589 if ( $this->debug
) $this->log ( " adding namespace $k => $v ");
592 if ( $namespace != '' )
593 $node = $this->doc
->createElementNS ( $namespace, $name );
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 );
601 // xml parser start element
602 private function endElement ( $parser, $name )
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 )
608 if ( $this->debug
) $this->log ( " adding $name to tags to process ");
609 array_push ( $this->recvTags
, $this->currentXMLNode
); // replace with tag
611 $this->currentXMLNode
= $this->currentXMLNode
->parentNode
;
614 // xml parser start element
615 private function parseData ( $parser, $text )
617 $this->currentXMLNode
->appendChild ( $this->doc
->createTextNode ( $text ) );
620 // xml parser start element
621 private function setupXmlParser ( )
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' ) );
635 private function query ( $expression, &$node = '' )
638 return $this->xquery
->query ( $expression );
640 return $this->xquery
->query ( $expression , $node );
644 // open xmpp connection, will accept jid and password
645 public function open ( $jid = null, $password = null)
649 if ( null != $password )
650 $this->password
= $password;
651 $this->ready
= false;
652 if ( false !== $this->connect () )
662 public function close ()
664 if ( false !== $this->connection
)
666 $this->send ( '</stream:stream>');
667 fclose ( $this->connection
);
668 $this->connection
= false;
672 // add a send or recv handler, direction = [ send | recv ], command = command to handle, handler = function ref
673 public function addHandler ( $direction, $command, $handler )
675 if ( 'send' == $direction )
676 $this->sendHandler
[$command] = $handler;
677 if ( 'recv' == $direction )
678 $this->recvHandler
[$command] = $handler;
682 private function log ( $message )
684 error_log ( 'XMPP: ' . $message );
685 //echo 'XMPP: ' . $message . "\n";
692 * * @param string $action_type INSERT / UPDATE or DELETE
693 * * @param string $uid The UID of the modified item
694 * * @param integer $user_no The user owning the containing collection.
695 * * @param integer $collection_id The ID of the containing collection.
696 * * @param string $dav_name The DAV path of the item, relative to the DAViCal base path
698 function log_caldav_action( $action_type, $uid, $user_no, $collection_id, $dav_name )
704 if ( 1 == $c->dbg
["ALL"] ||
1 == $c->dbg
["push"] )
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();
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 );