* @revision 05 * @license http://creativecommons.org/licenses/by-sa/3.0/de/ Creative Commons Attribution-Share Alike 3.0 Germany * @homepage http://oss.tiggerswelt.net/xmpp * @copyright Copyright © 2008 tiggersWelt.net */ require_once ('phpEvents/socket/stream/xml/tag.php'); require_once ('tiggerXMPP/initiator.php'); require_once ('tiggerXMPP/error.php'); require_once ('tiggerXMPP/packet.php'); require_once ('tiggerXMPP/presence.php'); require_once ('tiggerXMPP/message.php'); /** * XMPP Stream * * Implementation of an XMPP "Client"-Library * Useable for s2s- and c2s-links but not for * listening-implementations * * @package tiggerXMPP * @class tiggerXMPP_Stream */ class tiggerXMPP_Stream extends tiggerXMPP_Initiator { /* Callback types */ const CALLBACK_DEFAULT = "__DEFAULT"; const CALLBACK_IQ = "iq"; const CALLBACK_MESSAGE = "message"; const CALLBACK_MESSAGE_INCOMMING = "message_incomming"; const CALLBACK_PRESENCE = "presence"; const CALLBACK_STREAM_FEATURES = "stream:features"; const CALLBACK_SUBSCRIBE_EVENT = "presence_subscribe"; const CALLBACK_UNSUBSCRIBE_EVENT = "presence_unsubscribe"; const CALLBACK_ONLINE_EVENT = "presence_available"; const CALLBACK_OFFLINE_EVENT = "presence_unavailable"; /* Presence types */ const PRESENCE_ONLINE = ""; const PRESENCE_OFFLINE = "unavailable"; const PRESENCE_AWAY = "away"; const PRESENCE_CHAT = "chat"; const PRESENCE_DND = "dnd"; const PRESENCE_XA = "xa"; const PRESENCE_SUBSCRIBE = "subscribe"; const PRESENCE_SUBSCRIBED = "subscribed"; const PRESENCE_UNSUBSCRIBE = "unsubscribe"; const PRESENCE_UNSUBSCRIBED = "unsubscribed"; const PRESENCE_PROBE = "probe"; const PRESENCE_ERROR = "error"; /* Message types */ const MESSAGE_NORMAL = "normal"; const MESSAGE_CHAT = "chat"; const MESSAGE_GROUPCHAT = "groupchat"; const MESSAGE_HEADLINE = "headline"; const MESSAGE_ERROR = "error"; const DEFAULT_XML_TAG = 'tiggerXMPP_Packet'; /* Structure for callbacks */ private $_Callbacks = array (); /* Customized Namespaces */ private $_Namespaces = array (); /* Some properties of the client */ public $Domain = ""; public $Type = tiggerXMPP_Stream::STREAM_TYPE_CLIENT; public $Username = ""; public $Ressource = ""; /* Helper for Unique IDs */ private $IDCount = 0; /* Force even empty IQ Results (needed by e.g. XEP 0199) */ private $__ForceIQResult = false; private $_DefaultRules = array ( "unsubscribe" => true, "subscribe" => true, ); // {{{ __construct /** * Create a new XMPP Client * * @param string $Domain Domain to connect to * @param string $Server (optional) Manually specify server * @param int $Port (optional) Manually specify port on server * @param enum $Type (optional) Set Type of this XMPP-Client * @param enum $Debug (optional) Set initial debug-value * * @access public * @return bool */ public function __construct ($Domain, $Server = "", $Port = null, $Type = self::STREAM_TYPE_CLIENT, $Debug = null) { if ($Debug !== null) self::setDebug ($Debug); self::__debug (self::DEBUG_DEBUG, "called.", __FUNCTION__, __LINE__, __CLASS__, __FILE__); $this->Domain = $Domain; $this->Type = $Type; if ($Server == "") { if ($Type == self::STREAM_TYPE_COMPONENT) throw new xmppException ("Components need to specify a server"); $Server = $Domain; // Check if we can get better data via SRV-Records $Result = dns_get_record ("_xmpp-" . ($Type == self::STREAM_TYPE_CLIENT ? "client" : "server") . "._tcp." . $this->Domain, DNS_SRV); if (isset ($Result [0]["target"])) $Server = $Result [0]["target"]; if (isset ($Result [0]["port"])) $Port = $Result [0]["port"]; } if ($Port === null) $Port = ($Type == self::STREAM_TYPE_COMPONENT ? 5347 : 5222); // Setup the initiator parent::setNamespace ($Type); parent::setDestination ($Domain); parent::setLanguage ('de'); // Initialise Extensions $fns = get_class_methods (get_class ($this)); foreach ($fns as $fn) if (strtolower (substr ($fn, 0, 7)) == 'initxep') self::$fn (); // Register callbacks self::registerInternalCallback (self::CALLBACK_IQ, 'handleIQ'); self::registerInternalCallback (self::CALLBACK_MESSAGE, 'handleMessage'); self::registerInternalCallback (self::CALLBACK_PRESENCE, 'handlePresence'); return parent::createConnection ($Server, $Port); } // }}} // {{{ disconnect /** * Close the connection to server * * @access public * @return void */ public function disconnect () { return parent::closeConnection (); } // }}} // {{{ receiveTag /** * Handle a tag * * @param object $Tag Tag received over pipe * * @access protected * @return void */ protected function receiveTag ($Tag) { if (!self::verifyPacket ($Tag, true)) return self::__debug (self::DEBUG_FATAL, "Received malformed packet", __FUNCTION__, __LINE__, __CLASS__, __FILE__); if (!isset ($Tag)) return self::__debug (self::DEBUG_NOTICE, "Packet didn't survive cleanup", __FUNCTION__, __LINE__, __CLASS__, __FILE__); if ($Tag->getName () == "stream:error") { // Handle Stream-Error :-( // Maybe this could be improved a bit... self::disconnect (); if ($Tag->haveSubtags ("Text")) { $tagText = array_shift ($Tag->getSubtagsByName ("Text")); $Text = ": " . $tagText->getValue (); } else $Text = ""; throw new xmppException ("Got Stream-Error" . $Text); } else { // Handle other/unknown XML $Continue = true; // Run callbacks if (!self::checkNamespace ($Tag) && !self::runCallback ($Tag)) self::__debug (self::DEBUG_WARN, "Unhandled Packet " . $Tag->getName (), __FUNCTION__, __LINE__, __CLASS__, __FILE__); } } // }}} // {{{ verifyPacket /** * Verify packet integrity * * @param object $Tag Packet to verify * @param bool $Clean (optional, default) Clean up packet * * @access public * @return bool */ public function verifyPacket (&$Tag, $Clean = true) { // We need a Namespace to verify ;) if (strlen ($NS = $Tag->getNamespace ()) > 0) { $Tags = array (); $Properties = array (); $NamespaceValid = false; switch (strtolower ($NS)) { case "http://etherx.jabber.org/streams": $NamespaceValid = true; $Tags = array ("stream:features", "stream:error"); break; case "urn:ietf:params:xml:ns:xmpp-tls": $NamespaceValid = true; $Tags = array ("starttls", "proceed", "failure"); break; case "urn:ietf:params:xml:ns:xmpp-sasl": $NamespaceValid = true; $Tags = array ("mechanisms", "auth", "challenge", "response", "abort", "success", "failure"); break; case "urn:ietf:params:xml:ns:xmpp-bind": $NamespaceValid = true; $Tags = array ("bind"); break; case self::STREAM_TYPE_CLIENT: case self::STREAM_TYPE_SERVER: $NamespaceValid = true; $Tags = array ("iq", "message", "presence"); break; default: if (!isset ($this->_Namespaces [$NS])) break; $NamespaceValid = true; $Tags = $this->_Namespaces [$NS]['Tags']; break; } // Check if packet matches Tags in Namespace if ($NamespaceValid && !in_array ($Tag->getName (), $Tags) && !in_array ($Tag->getName (), $this->_Callbacks) && (count ($Tags) > 0)) $NamespaceValid = false; if (!$NamespaceValid && $Clean) { self::__debug (self::DEBUG_DEBUG, 'XML-Block ' . $Tag->getName () . ' of Namespace ' . $NS . ' is invalid', __FUNCTION__, __LINE__, __CLASS__, __FILE__); unset ($Tag); } #elseif ($Clean && isset ($Tag->Subtags) && is_array ($Tag->Subtags)) #foreach ($Tag->Subtags as $TID=>$Subtags) # foreach ($Subtags as $ID=>$Subtag) # self::verifyPacket ($Tag->Subtags [$TID][$ID], true); } return true; } // }}} // {{{ sendXML /** * Send an XML-Block over the line * * @param mixed $Data * @param object $Packet (optional) Packet to reply to * @param bool $returnLength (optional) Return length instead of a boolean * * @access public * @return bool **/ public function sendXML ($Data, $Packet = null, $returnLength = false) { if (is_object ($Packet) && $Packet->getAttribute ('id', false)) { if (!is_object ($Data)) $Data = self::toObject ($Data); $Data->setAttribute ('id', $Packet->getAttribute ('id')); } return parent::sendXML ($Data, $returnLength); } // }}} // {{{ xmlLangArray /** * Convert an array with languages to a collection of XML-Tags * * @param mixed $Data Data to convert * @param stirng $Tag Name of XML-Tag * @param object $Handle (optional) Don't create a string, but append here * * @access protected * @return string */ protected function xmlLangArray ($Data, $Tag, $Handle = null) { if (!is_array ($Data)) $Data = array ($this->getLanguage () => $Data); if (is_object ($Handle)) { foreach ($Data as $Lang=>$Body) { // Create a new XML-Tag $sHandle = new phpEvents_Socket_Stream_XML_Tag ($Tag, $Handle, $Body); // Place xml:lang if ($Lang != $this->getLanguage ()) $sHandle->setLanguage ($Lang); } return $Tag; } $out = ""; if (isset ($Data [$this->getLanguage ()])) $out .= "<" . $Tag . ">" . $Data [$this->getLanguage ()] . ""; foreach ($Data as $Lang=>$Msg) if ($Lang != $this->getLanguage ()) $out .= "<" . $Tag . " xml:lang=\"" . $Lang . "\">" . $Msg . ""; return $out; } // }}} // {{{ xmlLangFindTag /** * Search an array of tags for our language * * @param array $Tags Array of tags to search * @param string $Lang (optional) Search for this language * * @access protected * @return object */ protected function xmlLangFindTag ($Tags, $Lang = null) { if ($Lang === null) $Lang = $this->getLanguage (); if (!is_array ($Tags)) $Tags = array ($Tags); $Candidate = null; foreach ($Tags as $Tag) { if (strlen ($Tag->getLanguage ()) == 0) { if ($Candidate === null) $Candidate = $Tag; continue; } if ($Tag->getLanguage () == $Lang) return $Tag; if ($Candidate === null) $Candidate = $Tag; } return $Candidate; } // }}} // {{{ getJID /** * Request JID for our client * * @param bool $getUsername (optional) Return username (default) * @param bool $getDomain (optional) Return domainname (default) * @param bool $getRessource (optional) Return ressource (default) * @param string $fromJID (optional) Use this JID for processing * * @access public * @return string */ public function getJID ($getUsername = true, $getDomain = true, $getRessource = true, $fromJID = null) { if ($fromJID === null) $fromJID = $this->Username . ($this->Username != '' ? '@' : '') . $this->Domain . ($this->Ressource != '' ? '/' : '') . $this->Ressource; if (($getUsername && $getDomain && $getRessource) || (strlen ($fromJID) == 0)) return $fromJID; $p1 = strpos ($fromJID, '@'); $p2 = strpos ($fromJID, '/', $p1 + 1); if ($p2 == 0) $p2 = strlen ($fromJID); $Username = substr ($fromJID, 0, $p1); $Domain = substr ($fromJID, ($p1 > 0 ? $p1 + 1 : 0), $p2 - ($p1 > 0 ? $p1 + 1 : 0)); $Ressource = substr ($fromJID, $p2 + 1); return ($getUsername ? $Username : '') . ($getUsername && $getDomain && (strlen ($Username) > 0) && (strlen ($Domain) > 0) ? '@' : '') . ($getDomain ? $Domain : '') . ($getRessource && ($getUsername || $getDomain) ? '/' : '') . ($getRessource ? $Ressource : ''); } // }}} // {{{ getUniqueID /** * Get a unique packet-ID * * @access public * @return string */ public function getUniqueID () { return "tw" . dechex (++$this->IDCount); } // }}} // {{{ setPresence /** * Change/Set presence on server * * @param enum $Presence (optional) Type of presence * @param mixed $Message (optional) Status-Message (may be an array with multiple languages) * @param int $Priority (optional) Priority of Presence * @param mixed $To (optional) Receiver of Presence * @param string $From (optional) Set source of presence * * @access public * @return void */ public function setPresence ($Presence = self::PRESENCE_ONLINE, $Message = '', $Priority = null, $To = '', $From = null) { // Be sure that receiver is an array if (!is_array ($To)) $To = array ($To); // Generate source of this presence if ($From === null) $From = self::getJID (); // Create a new presence-packet $Tag = tiggerXMPP_Presence::newTag ('', $From); // Setup the packet $Tag->setLanguage ($this->getLanguage ()); $Tag->setType ($Presence); if (is_array ($Message) || (strlen ($Message) > 0)) self::xmlLangArray ($Message, 'status', $Tag); if ($Priority !== null) new phpEvents_Socket_Stream_XML_Tag ('priority', $Tag, intval ($Priority)); foreach ($To as $Receiver) { $Tag->setDestination ($Receiver); self::sendXML ($Tag); } } // }}} protected function presencePacket ($JID, $Type, $From = null) { // Create a new presence-packet $Tag = tiggerXMPP_Presence::newTag (self::getJID (true, true, false, $JID), self::getJID (true, true, false, $From)); $Tag->setType ($Type); // Send this packet over our line self::sendXML ($Tag); } // {{{ requestPresence /** * Request presence-info from a JID * * @param string $JID * * @access public * @return void */ public function requestPresence ($JID, $From = null) { self::presencePacket ($JID, "probe", $From); } // }}} // {{{ subscribeContact /** * Subscribe an entity * * @param string $JID * @param string $From (optional) * * @access public * @return void */ public function subscribeContact ($JID, $From = null) { self::presencePacket ($JID, "subscribe", $From); } // }}} // {{{ unsubscribeContact /** * Unsubscribe an entity * * @param string $JID * @param string $From (optional) * * @access public * @return void */ public function unsubscribeContact ($JID) { self::presencePacket ($JID, "unsubscribe", $From); } // }}} // ====================================================================== // Message Handling // {{{ sendMessage /** * Send an XMPP-Message to another Client * * @param mixed $To Receiving XMPP-ID * @param mixed $Message Message to send * @param enum $Type (optional) Type of Message * @param mixed $Subject (optional) Subject of Message * @param string $ThreadID (optional) Thread-ID of Message * @param enum $Error (optional) Error-Condition * * @access public * @return void */ public function sendMessage ($To, $Message, $Type = self::MESSAGE_NORMAL, $Subject = null, $ThreadID = null, $Error = null) { return $this->sendMessageFrom ($this->getJID (), $To, $Message, $Type, $Subject, $ThreadID, $Error); } // }}} // {{{ sendMessageFrom /** * Send an XMPP-Message (with custom sender) to another Client * * @param string $From Sending XMPP-ID * @param mixed $To Receiving XMPP-ID * @param mixed $Message Message to send * @param enum $Type (optional) Type of Message * @param mixed $Subject (optional) Subject of Message * @param string $ThreadID (optional) Thread-ID of Message * @param enum $Error (optional) Error-Condition * * @access public * @return void */ public function sendMessageFrom ($From, $To, $Message, $Type = self::MESSAGE_NORMAL, $Subject = null, $ThreadID = null, $Error = null) { // Be sure we have an array of receivers if (!is_array ($To)) $To = array ($To); // Create a new message $xmppMessage = tiggerXMPP_Message::newTag ('', self::getJID (true, true, true, $From)); $xmppMessage->setType ($Type); $xmppMessage->setMessage ($Message, $this->getLanguage ()); // Append subject if (($Subject !== null) && (strlen ($Subject) > 0)) $xmppMessage->setSubject ($Subject, $this->getLanguage ()); // Set thread if (($ThreadID !== null) && (strlen ($ThreadID) > 0)) $xmppMessage->setThread ($ThreadID); // Append Error if applicable if ($Type == self::MESSAGE_ERROR) { if (is_object ($Error)) $Error->generate ($xmppMessage); elseif ($Error != null) new tiggerXMPP_Error ($Error, '', $xmppMessage); } // Process all receivers foreach ($To as $Receiver) { // Set destination of packet $xmppMessage->setDestination ($Receiver); // Write the message to wire $this->sendXML ($xmppMessage); } } // }}} // ====================================================================== // Custom Extension / Namespace Handling // {{{ registerExtension /** * Register an extension using an external class * * @param mixed $Namespace Namespace of extension * @param array (optional) $Tags Tags to use for this extension * @param mixed (optional) $Class Handle or name of class to use * @param bool $Publish (optional) Allow the namespace to be published * * @access public * @return bool */ public function registerExtension ($Namespace, $Tags = null, $Class = null, $Publish = true) { // Check if we were called with an extension-handle as namespace if (is_object ($Namespace)) { $Class = $Namespace; $Namespace = $Class->getNamespaces (); } // Make sure that our namespace-definition is an array if (!is_array ($Namespace)) $Namespace = array ($Namespace); // Check wheter to auto-create an extension if (!is_object ($Class)) { if (!class_exists ($Class)) return self::__debug (self::DEBUG_ERROR, "Can not autocreate extension $Class", __FUNCTION__, __LINE__, __CLASS__, __FILE__, false); return is_object ($Instance = new $Class ($this)); } // Make sure that the extention inherits our interface if (!($Class instanceof tiggerXMPP_Extension)) return self::__debug (self::DEBUG_ERROR, "Extension for $Namespace is of wrong type", __FUNCTION__, __LINE__, __CLASS__, __FILE__, false); // Try to register us as parent if (!$Class->setParent ($this)) return fself::__debug (self::DEBUG_ERROR, "Could not set parent for extension", __FUNCTION__, __LINE__, __CLASS__, __FILE__, false); // Register the namespaces foreach ($Namespace as $NS) { self::__debug (self::DEBUG_NOTICE, "Adding Namespace " . $NS . " from Extension " . get_class ($Class), __FUNCTION__, __LINE__, __CLASS__, __FILE__); $this->_Namespaces [$NS] = array ( "Namespace" => $NS, "Callback" => array ($Class, "handle"), "Tags" => ($Tags === null ? $Class->getTags ($NS) : $Tags), "Instance" => $Class, "Publish" => $Publish, ); } return true; } // }}} // {{{ registerNamespace /** * Register a new namespace * * @param string $Namespace Name of Namespace * @param callback $Func Handler-Function * @param array $Tags (optional) Tags in Namespace * @param bool $Publish (optional) Allow the namespace to be published * * @access protected * @return void */ protected function registerNamespace ($Namespace, $Func, $Tags = array (), $Publish = true) { $this->_Namespaces [$Namespace] = array ( "Namespace" => $Namespace, "Callback" => $Func, "Tags" => $Tags, "Instance" => &$this, "Publish" => $Publish, ); } // }}} // {{{ checkNamespace /** * Try to process a packet with its assigned namespace-handler * * @param object $Tag Packet to process * @param object $Response (optional) Append responses to this packet * * @access protected * @return bool True if packet was processed */ protected function checkNamespace (&$Tag, &$Response = null) { // Check if there is a namespace specified if (strlen ($NS = $Tag->getNamespace ()) == 0) return false; // Check if we have a handler for this namespace if (!isset ($this->_Namespaces [$NS])) return false; // Find the right function if (!is_array ($Func = $this->_Namespaces [$NS]['Callback'])) $Func = array ($this, $this->_Namespaces [$NS]['Callback']); // Prevent us from crashing ;-) if (!is_callable ($Func, true)) return false; self::__debug (self::DEBUG_DEBUG, "Running " . $Func [1] . " from " . get_class ($Func [0]), __FUNCTION__, __LINE__, __CLASS__, __FILE__); $rc = call_user_func ($Func, $Tag, $Response); // Handle XMPP-Errors if (is_object ($rc) && is_a ($rc, "tiggerXMPP_Error")) { if (is_object ($Response)) $Response = $rc->generate ($Response, $Tag); else self::sendXML ($rc->generate (null, $Tag)); return false; } if (is_object ($rc)) $rc = array ($rc); if (is_array ($rc)) foreach ($rc as $i=>$P) { // Convert XMPP-Erors to XML-Packets if (is_a ($P, "tiggerXMPP_Error")) $rc [$i] = $P->generate ($Response, null, false); if (is_object ($Response)) $rc [$i]->setParent ($Response); else self::sendXML ($rc [$i]); } elseif ($rc === true) $this->__ForceIQResult = true; return true; } // }}} // {{{ getNamespaces /** * Retrive a list of supported Namespaces * * @param bool $Public (optional) Include public namespaces * @param bool $Private (optional) Include private namespaces * * @access public * @return array */ public function getNamespaces ($Public = true, $Private = false) { $out = array (); foreach ($this->_Namespaces as $XMLNS) if (($XMLNS ["Publish"] && $Public) || (!$XMLNS ["Publish"] && $Private)) $out [] = $XMLNS ["Namespace"]; return $out; } // }}} // ====================================================================== // Callback Subsystem // {{{ haveCallback /** * Check wheter there is a callback registered * * @param string $Callback Name of Callback * * @access protected * @return bool True if there is such a callback */ protected function haveCallback ($Callback) { if (!isset ($this->_Callbacks [$Callback])) return false; return (count ($this->_Callbacks [$Callback]["private"]) > 0) || (count ($this->_Callbacks [$Callback]["public"]) > 0); } // }}} // {{{ runCallback /** * Execute Callback-Stack for a packet * * @param object $Tag Tag to execute Callback with * @param string $Callback (optional) Name of callback to execute * * @access protected * @return bool True if any callback was executed */ protected function runCallback ($Tag, $Callback = null, $UseCallbackRC = false) { // Auto-determine callback if not set if ($Callback === null) $Callback = $Tag->getName (); // Check if there is such callback registered if (!isset ($this->_Callbacks [$Callback])) return false; // Handle all callbacks $Continue = true; $HadCallback = false; $CallbackRC = true; $id = $Tag->getAttribute ('id', 0); foreach (array ('private', 'public') as $Section) if (isset ($this->_Callbacks [$Callback][$Section])) foreach ($this->_Callbacks [$Callback][$Section] as $CallbackFunc) { if ($Section == 'private') $rc = $this->$CallbackFunc ($id, $Tag, $Continue, $this); else $rc = call_user_func ($CallbackFunc, $id, $Tag, &$Continue, &$this); if ($rc !== NULL) $CallbackRC = $CallbackRC && $rc; $HadCallback = true; if (!$Continue) break (2); } // Generate the return-code if ($UseCallbackRC) return $CallbackRC && $HadCallback; return $HadCallback; } // }}} // {{{ registerInternalCallback /** * Register a callback (with privileged functions) * * @param enum $Type Packet-Type for callback * @param string $Callback Function for callback * @param bool $Stack (optional) Allow multiple Callbacks for this packet-type * @param bool $Internal (optional) Determine wheter this is an internal callback * * @access private * @return void */ private function registerInternalCallback ($Type, $Callback, $Stack = true, $Internal = true) { $Sec = ($Internal ? 'private' : 'public'); if (!isset ($this->_Callbacks [$Type])) $this->_Callbacks [$Type] = array (); if (!isset ($this->_Callbacks [$Type][$Sec]) || !$Stack) $this->_Callbacks [$Type][$Sec] = array (); // Generate a key if (is_array ($Callback)) $Key = (is_object ($Callback [0]) ? get_class ($Callback [0]) : $Callback [0]) . '::' . $Callback [1]; else $Key = $Callback; // Register the callback $this->_Callbacks [$Type][$Sec][$Key] = $Callback; return true; } // }}} // {{{ registerCallback /** * Register a callback (public wrapper) * * @param enum $Type Packet-Type for callback * @param string $Function Function for callback * @param bool $Stack (optional) Allow multiple Callbacks for this packet-type * * @access private * @return void */ public function registerCallback ($Type, $Function, $Stack = true) { return self::registerInternalCallback ($Type, $Function, $Stack, false); } // }}} // ====================================================================== // Internal Callback Handlers // {{{ handleIQ /** * Hanlder for IQ-Packets, this will also raise sub-callbacks ;) * * @access protected * @return void * @todo Implement function */ protected function handleIQ ($ID, $Packet, &$Continue, &$XMPP) { // Don't force anything here $this->__ForceIQResult = false; // Prepare an answer $Resp = new phpEvents_Socket_Stream_XML_Tag ('iq'); $Resp->setNamespace ('jabber:client'); $Resp->setAttribute ('from', $Packet->to); $Resp->setAttribute ('to', $Packet->from); $Resp->setAttribute ('type', ''); // Handle different Namespaces / XEPs $Subtags = $Packet->getSubtags (); foreach ($Subtags as $t=>$nSubtags) foreach ($nSubtags as $i=>$Subtag) if (strlen ($Subtag->getNamespace ()) > 0) { self::checkNamespace ($Subtag, $Resp); if ($Resp->getAttribute ('type') == 'error') break; } switch ($Packet->type) { case 'get': case 'set': // Set type of response to result if not an error if ($Resp->getAttribute ('type') != 'error') $Resp->setAttribute ('type', 'result'); if ($this->__ForceIQResult || $Resp->haveSubtags ()) return $this->sendXML ($Resp, $Packet); self::__debug (self::DEBUG_PACKETS, self::toXML ($Resp), __FUNCTION__, __LINE__, __CLASS__, __FILE__); break; case 'error': case 'result': default: self::__debug (self::DEBUG_DEBUG, "Unhandled: " . self::toXML ($Packet), __FUNCTION__, __LINE__, __CLASS__, __FILE__); } } // }}} // {{{ handleMessage /** * Handler for incomming messages * * @access protected * @return void * @todo Implement function **/ protected function handleMessage ($ID, $Packet, &$Continue, &$XMPP) { self::__debug (self::DEBUG_DEBUG, "called", __FUNCTION__, __LINE__, __CLASS__, __FILE__); // Handle different Namespaces / Extensions $Subtags = $Packet->getSubtags (); foreach ($Subtags as $t=>$nSubtags) foreach ($nSubtags as $i=>$Subtag) if (strlen ($Subtag->getNamespace ()) > 0) self::checkNamespace ($Subtag); // Handle basic message if ($Packet->haveSubtags ('body') || $Packet->haveSubtags ('subject')) { $Obj = new StdClass; $Obj->From = $Packet->getAttribute ('from'); $Obj->To = $Packet->getAttribute ('to'); $Obj->ID = $Packet->getAttribute ('id'); $Obj->Type = $Packet->getAttribute ('type'); $Obj->Subject = null; $Obj->Body = null; $Obj->Thread = ''; if (($Subtags = $Packet->getSubtagsByName ('body')) && is_object ($Message = self::xmlLangFindTag ($Subtags))) $Obj->Body = $Message->getValue (); if ($Packet->haveSubtags ('subject') && is_object ($Subject = self::xmlLangFindTag ($Packet->getSubtagsByName ('subject')))) $Obj->Subject = $Subject->getValue (); if (is_object ($tagThread = $Packet->getSubtagByName ('thread'))) $Obj->Thread = $tagThread->getValue (); // Run callback for simplified message self::runCallback ($Obj, self::CALLBACK_MESSAGE_INCOMMING); // Run new-style "callback" $this->receiveMessage ( $Obj->From, $Obj->Subject, html_entity_decode ($Obj->Body), $Obj->Type, $Obj->ID, $Obj->To, $Obj->Thread, $Packet ); } else self::__debug (self::DEBUG_WARN, "Got unhandled Message: " . self::toXML ($Packet), __FUNCTION__, __LINE__, __CLASS__, __FILE__); } // }}} // {{{ handlePresence /** * Handler for Presence-Packets, this will also raise sub-callbacks ;) * * @param string $ID * @param object $Packet * @param bool $Continue * @param object $XMPP * * @access protected * @return void */ protected function handlePresence ($ID, $Packet, &$Continue, $XMPP) { // Tell the debugger that we have been called self::__debug (self::DEBUG_DEBUG, 'called', __FUNCTION__, __LINE__, __CLASS__, __FILE__); // Handle presence-types switch (strtolower ($Packet->getAttribute ('type', self::PRESENCE_ONLINE))) { // Subscriptions case self::PRESENCE_SUBSCRIBE: case self::PRESENCE_UNSUBSCRIBE: $Callback = (strtolower ($Packet->getAttribute ('type')) == self::PRESENCE_SUBSCRIBE ? self::CALLBACK_SUBSCRIBE_EVENT : self::CALLBACK_UNSUBSCRIBE_EVENT); self::__debug (self::DEBUG_DEBUG, $Packet->getAttribute ('type') . "-request from " . $Packet->getOriginator (), __FUNCTION__, __LINE__, __CLASS__, __FILE__); if (self::haveCallback ($Callback)) $rc = self::runCallback ($Packet, $Callback, true); elseif ($Packet->getAttribute ('type') == self::PRESENCE_SUBSCRIBE) $rc = $this->subscribeRequest ($Packet->getOriginator (), $Packet->getDestination ()); else $rc = $this->unsubscribeRequest ($Packet->getOriginator (), $Packet->getDestination ()); if ($rc !== null) { $Resp = tiggerXMPP_Presence::newTag ($Packet->getOriginator (), self::getJID ()); $Resp->setType (($rc && ($Callback == self::CALLBACK_UNSUBSCRIBE_EVENT)) || !$rc ? self::PRESENCE_UNSUBSCRIBED : self::PRESENCE_SUBSCRIBED); self::sendXML ($Resp); } break; // Subscription-results case self::PRESENCE_SUBSCRIBED: $this->subscribed ($Packet->getOriginator (), $Packet->getDestination ()); break; case self::PRESENCE_UNSUBSCRIBED: $this->unsubscribed ($Packet->getOriginator (), $Packet->getDestination ()); break; // Status-changes case self::PRESENCE_ONLINE: self::__debug (self::DEBUG_DEBUG, $Packet->getOriginator () . ' went online', __FUNCTION__, __LINE__, __CLASS__, __FILE__); self::runCallback ($Packet, self::CALLBACK_ONLINE_EVENT); // Run new-style callback $this->contactStatus ( $Packet->getAttribute ('from'), (is_object ($Subtag = $Packet->getSubtagByName ('show')) ? $Subtag->getValue () : tiggerXMPP_Stream::PRESENCE_ONLINE), (is_object ($Subtag = $Packet->getSubtagByName ('status')) ? $Subtag->getValue () : ''), (is_object ($Subtag = $Packet->getSubtagByName ('priority')) ? $Subtag->getValue () : null), $Packet->getAttribute ('to'), $Packet ); break; case self::PRESENCE_OFFLINE: self::__debug (self::DEBUG_DEBUG, $Packet->getOriginator () . ' went offline', __FUNCTION__, __LINE__, __CLASS__, __FILE__); self::runCallback ($Packet, self::CALLBACK_OFFLINE_EVENT); // Retrive status from packet if (is_object ($Status = $Packet->getSubtagByName ('status'))) $Status = $Status->getValue (); else $Status = ''; // Run new-style callback $this->contactOffline ($Packet->getOriginator (), $Status, $Packet->getDestination ()); break; case self::PRESENCE_PROBE: case self::PRESENCE_ERROR: default: self::__debug (self::DEBUG_DEBUG, self::toXML ($Packet), __FUNCTION__, __LINE__, __CLASS__, __FILE__); } } // }}} // ====================================================================== // New style callback-definitions // {{{ subscribed /** * Handle granted subscribtion * * @param string $JID * @param string $toJID Receiving JID (useful for components) * * @access protected * @return void */ protected function subscribed ($JID, $ToJID) { } // }}} // {{{ unsubscribed /** * Handle removed subscribtion * * @param string $JID * @param string $toJID Receiving JID (useful for components) * * @access protected * @return void */ protected function unsubscribed ($JID, $ToJID) { } // }}} // {{{ subscribeRequest /** * Handle incoming subscribtion-request * * @param string $JID * @param string $toJID Receiving JID (useful for components) * * @access publoc * @return bool */ protected function subscribeRequest ($JID, $ToJID) { return $this->_DefaultRules ["subscribe"]; } // }}} // {{{ unsubscribeRequest /** * Handle incoming unsubscribe-request * * @param string $JID * @param string $toJID Receiving JID (useful for components) * * @access publoc * @return bool */ protected function unsubscribeRequest ($JID, $ToJID) { return $this->_DefaultRules ["unsubscribe"]; } // }}} // {{{ contactStatus /** * Handle status-change of foreign contacts * * @param string $JID * @param enum $Status * @param string $Message * @param int $Priority * @param string $To * @param object $Packet * * @access protected * @return void */ protected function contactStatus ($JID, $Status, $Message, $Priority, $To, $Packet) { } // }}} // {{{ contactOffline /** * Handle status-change to offline * * @param string $JID * @param string $Message * @param string $Receiver * * @access protected * @return void */ protected function contactOffline ($JID, $Message, $Receiver = null) { } // }}} // {{{ receiveMessage /** * Internal hook when a message gets received * * @param string $From * @param string $Subject * @param string $Body * @param enum $Type (optional) * @param string $ID (optional) * @param string $To (optional) * @param string $Thread (optional) * @param object $Packet (optional) * * @access protected * @return void */ protected function receiveMessage ($From, $Subject, $Body, $Type = self::MESSAGE_NORMAL, $ID = null, $To = null, $Thread = null, $Packet = null) { } // }}} // ====================================================================== // XEP-0199: XMPP Ping // {{{ initXEP0199 /** * Register Namespaces for XEP 0199 XMPP Ping * * @access private * @return void */ private function initXEP0199 () { self::registerNamespace ("http://www.xmpp.org/extensions/xep-0199.html#ns", "handleXEP0199", array ("ping")); } // }}} // {{{ handleXEP0199 /** * Handle incoming PING-Requests * * @param object $Tag * * @access protected * @return object (void in this case) */ protected function &handleXEP0199 (&$Tag) { self::__debug (self::DEBUG_DEBUG, "called.", __FUNCTION__, __LINE__, __CLASS__, __FILE__); $this->__ForceIQResult = true; } // }}} // {{{ ping /** * Check the availability of another entity * * @param $JID * * @access public * @return bool */ public function ping ($JID) { $Resp = new phpEvents_Socket_Stream_XML_Tag ('iq'); $Resp->setNamespace ('jabber:client'); $Resp->setAttribute ('id', self::getUniqueID ()); $Resp->setAttribute ('type', 'get'); $Resp->setOriginator (self::getJID ()); $Resp->setDestination ($JID); $tagPing = new phpEvents_Socket_Stream_XML_Tag ('ping', $Resp); $tagPing->setNamespace ('http://www.xmpp.org/extensions/xep-0199.html#ns'); $this->sendXML ($Resp); $Resp = self::waitBlock (array (), $Resp->id); if (is_object ($tagError = $Resp->getSubtagByName ('error'))) return ($tagError->getAttribute ('type') == 'cancel'); return true; } // }}} // {{{ keepAlive /** * Better keep-alive Implementation using XEP 0199 * This function is preliminary called internal * * @access public * @return void */ public function keepAlive () { # self::ping ($this->Domain); parent::sendXML (' '); } // }}} // {{{ authenticationSuccess /** * Callback: Stream was successfully authenticated * * @access protected * @return void **/ protected function authenticationSuccess () { } // }}} // {{{ authenticationFailure /** * Callback: Authentication on the stream failed * * @access protected * @return void **/ protected function authenticationFailure () { throw new xmppException ('Authentication failed'); } // }}} } ?>