<?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 02
   * @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; 2009 tiggersWelt.net 
   */
  
  require_once ('tiggerXMPP/extension.php');
  
  /**
   * XEP-0077: In-Band Registration
   * 
   * Small implemtation of XEP 0077 for tiggerXMPP
   * 
   * @class XEP_0077
   * @package tiggerXMPP
   * @revision 02
   * @author Bernd Holzmueller <bernd@tiggerswelt.net>
   */
  class tiggerXMPP_XEP_0077 extends tiggerXMPP_Extension {
    /* Our XML-Namespace */
    const XEP_NAMESPACE = 'jabber:iq:register';
    
    /* Our entities */
    private $Entities = array ();
    
    // {{{ getTags   
    /**
     * Retrive a list of all tags we handle for a given namespace
     * 
     * @param string $Namespace
     * 
     * @access public
     * @return array
     **/
    public function getTags ($NS = '') {
      return array ('query');
    }
    // }}}
    
    // {{{ handle
    /**
     * Handle an incoming packet
     * 
     * @param object $Tag Packet to handle
     * 
     * @access public
     * @return object
     * @todo Add Support for iq set
     * @todo Add Support for data-Output
     */
    public function handle ($Tag) {
      $this->__debug (tiggerXMPP_Stream::DEBUG_DEBUG, 'called.', __FUNCTION__, __LINE__, __CLASS__, __FILE__);
      
      if (!is_object ($p = $Tag->getParent ()))
        return false;
      
      // Get receiver of this packet
      $ToJID = strtolower ($p->getDestination ());
      
      // Generate an answer
      $Resp = new phpEvents_Socket_Stream_XML_Tag ('query');
      $Resp->setNamespace ($Tag->getNamespace ());
      
      // Find a matching entity
      $Entity = $this->getEntity ($p->getDestination (), $p->getOriginator ());
      
      // Handle case where no entity was found
      if (!is_object ($Entity)) {
        $Instructions = new phpEvents_Socket_Stream_XML_Tag ('instructions', $Resp, 'In-Band registration unsupported for this entity');
        
        return array (
          $Resp,
          new tiggerXMPP_Error (tiggerXMPP_Error::ERROR_SERVICE_UNAVAILABLE, "Extension unconfigured"),
        );
      }
      
      // Check wheter to update entity
      if ($p->getAttribute ('type') == 'set') {
        // Handle removals
        if ($Tag->haveSubtags ('remove')) {
          if (!($rc = $Entity->removeData ($p->getOriginator ())))
            $rc = !$Entity->hasData ($p->getOriginator ());
          
          if (!$rc)
            return array (new tiggerXMPP_Error (tiggerXMPP_Error::ERROR_INTERNAL_SERVER_ERROR));
          
          return true;
        }
        
        if ($Entity->setData ($p->getOriginator (), $Tag->getSubtags ()))
          return true;
        
        return array (
          $Tag,
          new tiggerXMPP_Error (tiggerXMPP_Error::ERROR_NOT_ACCEPTABLE, "Entity refused to store data"),
        );
      }
      
      // Check if there is a registration stored for the requesting user
      if ($Entity->hasData ($p->getOriginator ())) {
        // Set registered state
        $Registered = new phpEvents_Socket_Stream_XML_Tag ('registered', $Resp);
        
        // Retrive data for this JID
        $Fields = $Entity->getData ($p->getOriginator ());
        $Mode = tiggerXMPP_XEP_0077_Field::MODE_FILLED;
      
      // Just hand out our registration-form
      } else {
        // Add instructing text
        $Instructions = new phpEvents_Socket_Stream_XML_Tag ('instructions', $Resp, $Entity->getInstructions ($p->getOriginator ()));
        
        // Read fields from entity
        $Fields = $Entity->getFields ($p->getOriginator ());
        $Mode = tiggerXMPP_XEP_0077_Field::MODE_DEFINITION;
      }
      
      // Handle invalid field-definition
      if (count ($Fields) < 1)
        return array (
          $Resp,
          new tiggerXMPP_Error (tiggerXMPP_Error::ERROR_SERVICE_UNAVAILABLE, "Extension misconfigured (no fields found)"),
        );
      
      // Add fields to response
      foreach ($Fields as $Field)
        $Field->addToResponse ($Resp, $Mode);
      
      return $Resp;
    }
    // }}}
    
    // {{{ getEntity
    /**
     * Search an entity matching a given JID
     * 
     * @param string $JID
     * @param stirng $For
     * 
     * @access protected
     * @return array
     */
    protected function getEntity ($ToJID, $For) {
      // Ugly method of finding best JID;
      $Match1 = null;
      $Match2 = null;
      
      if (!is_object ($P = $this->getParent ()))
        return false;
      
      // Just to save CPU-Time
      $UserHost = $P->getJID (true, true, false, $ToJID);
      $User = $P->getJID (true, false, false, $ToJID);
      $Host = $P->getJID (false, true, false, $ToJID);
      
      foreach ($this->Entities as $Handle) {
        // Get JID from entity
        $JID = $Handle->getJID ();
        
        // Check for exact match
        if ($JID == $ToJID)
          return $Handle;
        
        // Check wheter Username and Host of JID matches
        elseif ($P->getJID (true, true, false, $JID) == $UserHost)
          $Match1 = $Handle;
        
        // Check wheter user or host matches
        elseif (($P->getJID (true, false, false, $JID) == $User) ||
                ($P->getJID (false, true, false, $JID) == $Host))
          $Match2 = $JID;
      }
      
      // Check for user/host-match
      if (is_object ($Match1))
        return $Match1;
      
      // Check for user or host-match
      if (is_object ($Match2))
        return $Match2;
      
      // Don't do anything
      return false;
    }
    // }}}
    
    // {{{ registerEntity
    /**
     * Register a new Registration-Entity
     * 
     * @param string JID
     * @param string $Instructions
     * @param array $Fields (optional)
     * @param string $Class (optional)
     * 
     * @access pubic
     * @return object
     */
    public function registerEntity ($JID, $Instructions, $Fields = array (), $Class = null) {
      // Check validity of class
      if (($Class === null) || !class_exists ($Class) || !is_subclass_of ($Class, "tiggerXMPP_XEP_0077_Entity"))
        $Class = 'tiggerXMPP_XEP_0077_Entity';
      
      // Create a new Entity-Handle
      $Handle = new $Class ($JID, $this);
      
      // Set properties of handle
      $Handle->setInstructions ($Instructions);
      $Handle->setFields ($Fields);
      
      // Append handle to local storage
      return $this->Entities [$Handle->getJID ()] = $Handle;
    }
    // }}}
  }
  
  /**
   * XEP 0077 Entity
   * ---------------
   * Container for registration-informations assigned to a given JID
   * 
   * @class XEP_0077_Entity
   * @package tiggerXMPP
   * @revision 01
   * @author Bernd Holzmueller <bernd@tiggerswelt.net>
   */
  class tiggerXMPP_XEP_0077_Entity {
    // Internally stored informations
    private $JID = "";
    private $Instructions = "";
    private $Fields = array ();
    private $Callback = null;
    
    protected $Parent = null;
    
    // {{{ __construct
    /**
     * Create a new Entity
     * 
     * @param string $JID Assign this JID
     * 
     * @access friendly
     * @return void
     */
    function __construct ($JID, $Parent = null) {
      // Try to store assigned JID
      $this->setJID ($JID);
      
      $this->Parent = $Parent;
    }
    // }}}
    
    // {{{ getJID
    /**
     * Retrive currently assigned JID
     * 
     * @access public
     * @return string
     */
    public function getJID () { return $this->JID; }
    // }}}
    
    // {{{ getInstructions
    /**
     * Retrive stored Instructions (maybe for a given user)
     * 
     * @param string $ForJID (optional)
     * 
     * @access public
     * @return string
     */
    public function getInstructions ($ForJID = "") { return $this->Instructions; }
    // }}}
    
    // {{{ getFields
    /**
     * Retrive Fields of this entity
     * 
     * @param string $ForJID (optional)
     * 
     * @access public
     * @return array
     */
    public function getFields ($ForJID = "") { return $this->Fields; }
    // }}}
    
    // {{{ setJID
    /**
     * Set JID assigned to this entity
     * 
     * @param string $JID
     * 
     * @access public
     * @return void
     */
    public function setJID ($JID) {
      $this->JID = $JID;
    }
    // }}}
    
    // {{{ setInstructions
    /**
     * Set Instructions assigned to this entity
     * 
     * @param stirng $Text
     * 
     * @access public
     * @return void
     */
    public function setInstructions ($Text) {
      $this->Instructions = $Text;
    }
    // }}}
    
    // {{{ setFields
    /**
     * Store Registration-Fields for this entity
     * 
     * @param array $Fields
     * 
     * @access public
     * @return void
     */
    public function setFields ($Fields) {
      $this->Fields = $Fields;
    }
    // }}}
    
    // {{{ hasData
    /**
     * Check if there is registration-data stored for a JID
     * 
     * @param string $JID
     * 
     * @access public
     * @return bool
     */
    public function hasData ($ForJID) {
      $Data = $this->getData ($ForJID);
      
      return (is_array ($Data) && (count ($Data) > 0));
    }
    // }}}
    
    // {{{ getData
    /**
     * Retrive Data (represented by Field-Array) for a JID
     * 
     * @param string $JID
     * 
     * @access public
     * @return array
     * @todo Implement me
     */
    public function getData ($ForJID) {
      return false;
    }
    // }}}
    
    // {{{ setData
    /**
     * Store data for a JID
     * 
     * @param string $ForJID
     * @param array $Tags
     * 
     * @access public
     * @return bool
     */
    public function setData ($ForJID, $Tags) {
      if (!is_callable ($this->Callback))
        return true;
      
      return call_user_func ($this->Callback, __FUNCTION__, $ForJID, $Tags);
    }
    // }}}
    
    // {{{ removeData
    /**
     * Remove all stored data for a JID
     * 
     * @param string $JID
     * 
     * @access public
     * @return bool
     */
    public function removeData ($ForJID) {
      return false;
    }
    // }}}
    
    // {{{ setCallback
    /**
     * Register a generic callback-function for this entity
     * 
     * @param callback $CB
     * 
     * @access public
     * @return void
     **/
    public function setCallback ($CB) {
      $this->Callback = $CB;
    }
    // }}}
  }
  
  /**
   * XEP 0077 Field
   * --------------
   * Single/Simple Field-representation 
   * 
   * @class XEP_0077_Field
   * @package tiggerXMPP
   * @revision 01
   * @author Bernd Holzmueller <bernd@tiggerswelt.net>
   */
  class tiggerXMPP_XEP_0077_Field {
    const NAME_USERNAME  = "username";
    const NAME_NICKNAME  = "nick";
    const NAME_PASSWORD  = "password";
    const NAME_FULLNAME  = "name";
    const NAME_FORENAME  = "first";
    const NAME_SURNAME   = "last";
    const NAME_EMAIL     = "email";
    const NAME_ADDRESS   = "address";
    const NAME_CITY      = "city";
    const NAME_STATE     = "state";
    const NAME_ZIP       = "zip";
    const NAME_PHONE     = "phone";
    const NAME_URL       = "url";
    const NAME_DATE      = "date";
    const NAME_OPASSWORD = "old_password";
    
    const MODE_DEFINITION = "def";
    const MODE_FILLED = "fill";
    
    private $Name = "";
    private $Value = "";
    
    // {{{ __construct
    /**
     * create a new field
     * 
     * @param enum $Name
     * 
     * @access friendly
     * @return void
     */
    function __construct ($Name = "") {
      $this->setName ($Name);
    }
    // }}}
    
    // {{{ setName
    /**
     * Set the Name/Type of this field
     * 
     * @param enum $Name
     * 
     * @access public
     * @return bool
     */
    public function setName ($Name) {
      // Convert to lower case (just to be sure)
      $Name = strtolower ($Name);
      
      // Validate the name
      switch ($Name) {
        case self::NAME_USERNAME:
        case self::NAME_NICKNAME:
        case self::NAME_PASSWORD:
        case self::NAME_FULLNAME:
        case self::NAME_FORENAME:
        case self::NAME_SURNAME:
        case self::NAME_EMAIL:
        case self::NAME_ADDRESS:
        case self::NAME_CITY:
        case self::NAME_STATE:
        case self::NAME_ZIP:
        case self::NAME_PHONE:
        case self::NAME_URL:
        case self::NAME_DATE:
        case self::NAME_OPASSWORD:
          $this->Name = $Name;
          return true;
      }
      
      // Return error-case
      return false;
    }
    // }}}
    
    // {{{ addToResponse
    /**
     * Append our field to a response-packet
     * 
     * @param object $Resp
     * @param enum $Mode (optional)
     * 
     * @access public
     * @return void
     */
    public function addToResponse ($Resp, $Mode = self::MODE_DEFINITION) {
      // Check if our name is empty
      if (strlen ($this->Name) == 0)
        return false;
      
      // Create XML-Packet
      $Tag = new phpEvents_Socket_Stream_XML_Tag ($this->Name, $Resp);
      
      // Fill the packet with data
      switch ($Mode) {
        case self::MODE_DEFINITION:
          break;
        case self::MODE_FILLED:
          $Tag->setValue ($this->Value);
          break;
      }
      
      return true;
    }
    // }}}
  }

?>