<?PHP

  /**
   * tiggerXMPP
   * ----------
   * An XMPP-Implementation in PHP 5
   * 
   * This work is distributed within the terms of
   * creative commons attribution-share alike 3.0 germany
   * 
   * See http://creativecommons.org/licenses/by-sa/3.0/ for more information
   * 
   * @author Bernd Holzmueller <bernd@tiggerswelt.net>
   * @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 &copy; 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 ()] . "</" . $Tag . ">";
      
      foreach ($Data as $Lang=>$Msg)
        if ($Lang != $this->getLanguage ())
          $out .= "<" . $Tag . " xml:lang=\"" . $Lang . "\">" . $Msg . "</" . $Tag . ">";
      
      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');
    }
    // }}}
  }

?>