#!/usr/bin/php -q
<?PHP

  /**
   * Example message-logging application
   * 
   * This example component takes all messages and logs them to history/owner/opponent/ whenever
   * history/owner exists and is an directory.
   **/
  
  set_include_path ('../../' . PATH_SEPARATOR . '../../../' . PATH_SEPARATOR . get_include_path ());
  
  require_once ('tiggerXMPP/message.php');
  require_once ('tiggerXMPP/xep/0030.php');
  require_once ('tiggerXMPP/xep/0050.php');
  require_once ('tiggerXMPP/component/jabberd2/logger.php');
  
  
  class Example_Logger extends tiggerXMPP_Component_Jabberd2_Logger {
    const CONVERSATION_LIFETIME = 3600;
    
    private $Conversations = array ();
    
    // {{{ __construct
    /**
     * Create a new Jabberd2 logger
     * 
     * @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 $Debug (optional) Set initial debug-value
     * 
     * @access public
     * @return void 
     * @see tiggerXMPP_Stream::__construct
     */
    public function __construct ($Domain, $Server = '', $Port = null, $Debug = null) {
      // Setup the component first
      parent::__construct ($Domain, $Server, $Port, $Debug);
      
      // Setup extensions
      $XEP0030 = new tiggerXMPP_XEP_0030 ($this);
      $XEP0030->registerIdentityFunction (array ($this, 'discoInfo'));
      $XEP0030->registerItemFunction (array ($this, 'discoItems'));
      
      $XEP0050 = new tiggerXMPP_XEP_0050 ($this);
      $XEP0050->setCommandHandler (array ($this, 'commandHandler'));
    }
    // }}}
    
    // {{{ logMessage
    /**
     * Log an incoming message
     * 
     * @param object $Tag
     * 
     * @access protected
     * @return void
     **/
    protected function logMessage ($Tag) {
      // Retrive the direction
      if (($Direction = $this->getDirection ($Tag)) === false)
        return;
      
      // Retrive the owner of the packet
      if (strlen ($Owner = $this->getOwner ($Tag, $Direction)) < 2)
        return;
      
      $Owner = $this->getJID (true, true, false, $Owner);
      
      // Check if the owner is subscribed
      if (!is_dir ('history/' . $Owner))
        return;
      
      // Retrive the Opponent
      if (strlen ($Opponent = $this->getOpponent ($Tag, $Direction)) < 2)
        return;
      
      $Opponent = $this->getJID (true, true, false, $Opponent);
      
      // Retrive the message from payload
      if (!is_string ($Msg = $Tag->getMessage ($this->getLanguage ())))
        return;
      
      // Its senseless to log OTR
      if (substr ($Msg, 0, 5) == '?OTR:')
        return;
      
      // Start/Resume conversation
      if (!isset ($this->Conversations [$Owner]))
        $this->Conversations [$Owner] = array ();
      
      $convBase = 'history/' . $Owner . '/' . $Opponent;
      
      if (!isset ($this->Conversations [$Owner][$Opponent]))
        // There was never a conversation with this opponent before
        if (!is_dir ($convBase)) {
          if (!mkdir ($convBase))
            return;
          
          $this->Conversations [$Owner][$Opponent] = array ($convBase . '/' . time (), time ());
        
        // Check wheter to resume a conversation
        } elseif (is_object ($d = @dir ($convBase))) {
          $t = time ();
          $f = false;
          
          while ($f = $d->read ())
            if ($t - filemtime ($convBase . '/' . $f) < self::CONVERSATION_LIFETIME) {
              $this->Conversations [$Owner][$Opponent] = array ($convBase . '/' . $f, time ());
              $f = true;
              break;
            }
          
          $d->close ();
          
          if (!$f)
            $this->Conversations [$Owner][$Opponent] = array ($convBase . '/' . time (), time ());
        
        // Strange error...
        } else {
          return;
        }
      
      // Check the lifetime
      if (time () - ($LastAction = $this->Conversations [$Owner][$Opponent][1]) > self::CONVERSATION_LIFETIME)
        $this->Conversations [$Owner][$Opponent] = array ($convBase . '/' . time (), $LastAction = time ());
      
      $File = $this->Conversations [$Owner][$Opponent][0];
      
      if (!is_resource ($f = @fopen ($File, 'a')))
        return;
      
      fwrite ($f, $Direction . ':' . time () . ':' . base64_encode ($Msg) . "\n");
      fclose ($f);
    }
    // }}}
    
    // {{{ discoInfo
    /**
     * Check wheter a requested domain is exposed to a given sm-domain (and forward to service-discovery)
     * 
     * @param string $JID
     * @param stirng $Receipient
     * 
     * @access public
     * @return array    
     **/
    public function discoInfo ($JID, $Receipient, $Node = null) {
      // Check if we are bound to the queried domain
      if (!$this->boundDomain ($JID))
        return false;
      
      // Return the root-identity
      if ($Node === null)
        return array (new tiggerXMPP_XEP_0030_Identity ('tiggersWelt.net Message Archiver', 'component', 'archive'));
      
      // Retrive the owner of the collection
      $Owner = basename ($this->getJID (true, true, false, $Receipient));
      
      // Check if opponent-node exists
      $Opponent = basename ($this->getJID (true, true, false, $Node));
      
      if (($Node [0] == '.') || !is_dir ('history/' . $Owner . '/' . $Opponent))
        return false;
      
      // Check if a conversation was selected
      $TS = basename ($this->getJID (false, false, true, $Node));
      
      // Return identitiy for the node
      if (strlen ($TS) == 0)
        return array (new tiggerXMPP_XEP_0030_Identity ('Conversations with ' . $Opponent, 'component', 'archive'));
      
      // Handle the resource
      if (($p = strpos ($TS, ':')) !== false) {
        $Command = substr ($TS, $p + 1);
        $TS = substr ($TS, 0, $p);
      } else
        $Command = null;
      
      // Check if there is a conversation
      if ((intval ($TS) == 0) || !is_file ('history/' . $Owner . '/' . $Opponent . '/' . $TS))
        return false;
      
      if ($Command === null)
        return array (new tiggerXMPP_XEP_0030_Identity (date ('r', $TS), 'component', 'archive', '', array (tiggerXMPP_XEP_0050::XEP_NAMESPACE)));
      
      if ($Command == 'replay')
        return array (new tiggerXMPP_XEP_0030_Identity ('Replay this conversation', 'automation', 'command-node', '', array (tiggerXMPP_XEP_0050::XEP_NAMESPACE)));
      elseif ($Command == 'remove')
        return array (new tiggerXMPP_XEP_0030_Identity ('Remove this conversation', 'automation', 'command-node', '', array (tiggerXMPP_XEP_0050::XEP_NAMESPACE)));
      
      return array ();
    }
    // }}}
    
    // {{{ discoItems
    /**
     * Retrive items below a given node/jid
     * 
     * @param string $JID
     * @param string $Receipient
     * @param string $Node (optiona)
     * 
     * @access public
     * @return array
     **/
    public function discoItems ($JID, $Receipient, $Node = null) {
      $Items = array ();
      
      // Check if we are bound to the queried domain
      if ($this->boundDomain ($JID)) {
        // Retrive owner of the collection
        $Owner = basename ($this->getJID (true, true, false, $Receipient));
        
        // Retrive list of conversation-opponentes
        if ($Node === null) {
          if (is_dir ($p = 'history/' . $Owner) && is_object ($d = @dir ($p))) {
            while ($f = $d->read ())
              if (($f [0] != '.') && is_dir ($p . '/' . $f))
                $Items [] = new tiggerXMPP_XEP_0030_Item ($JID, $f, $f, $JID);
            
            $d->close ();
          }
        
        // Retrive list of conversations for a given opponent
        } elseif (strlen ($TS = basename ($this->getJID (false, false, true, $Node))) == 0) {
          $Opponent = basename ($this->getJID (true, true, false, $Node));
          
          if (($Opponent [0] != '.') && is_object ($d = @dir ($p = 'history/' . $Owner . '/' . $Opponent)))
            while ($f = $d->read ())
              if (is_file ($p .'/'. $f))
                $Items [] = new tiggerXMPP_XEP_0030_Item ($JID, date ('r', $f), $Opponent . '/' . $f, $JID);
        
        // Append commands for a conversation
        } else {
          $Opponent = basename ($this->getJID (true, true, false, $Node));
          
          if (($Opponent [0] != '.') && is_file ('history/' . $Owner . '/' . $Opponent . '/' . $TS)) {
            $Items [] = new tiggerXMPP_XEP_0030_Item ($JID, 'Replay this conversation', $Opponent . '/' . $TS . ':replay', $JID);
            $Items [] = new tiggerXMPP_XEP_0030_Item ($JID, 'Remove this conversation', $Opponent . '/' . $TS . ':remove', $JID);
          }
        }
      }
      
      return $Items;
    }
    // }}}
    
    // {{{ commandHandler
    /**
     * Execute a command
     * 
     * @param object $XEP
     * @param object $Tag
     * @param string $JID
     * @param string $Node
     * @param string $Receipient
     * 
     * @access public
     * @return bool
     **/
    public function commandHandler ($XEP, $Tag, $JID, $Node, $Receipient) {
      // Check if we are bound to the called domain
      if (!$this->boundDomain ($JID))
        return false;
      
      // Parse all informations
      $Owner = $this->getJID (true, true, false, $Receipient);
      $Opponent = basename ($this->getJID (true, true, false, $Node));
      $TS = basename ($this->getJID (false, false, true, $Node));
      
      if (($p = strpos ($TS, ':')) !== false) {
        $Command = substr ($TS, $p + 1);
        $TS = substr ($TS, 0, $p);
      } else
        $Command = 'replay';
      
      // Check the data
      if (!is_file ('history/' . $Owner . '/' . $Opponent . '/' . $TS))
        return false;
      
      // Run the command
      switch ($Command) {
        case 'remove':
          // Try to remove
          if (!unlink ('history/' . $Owner . '/' . $Opponent . '/' . $TS))
            return false;
          
          // Remove directories (if empty)
          if (rmdir ('history/' . $Owner . '/' . $Opponent))
            rmdir ('history/' . $Owner);
          
          return true;
        case 'replay':
          // Try to open the log
          if (!is_resource ($f = fopen ('history/' . $Owner . '/' . $Opponent . '/' . $TS, 'r')))
            return false;
          
          $xmppMessage = tiggerXMPP_Message::newTag ($Receipient, $this->getJID (true, true, true));
          $xmppMessage->setType (tiggerXMPP_Message::TYPE_CHAT);
          $xmppMessage->setMessage ('Replaying Conversation to ' . $Opponent . ' on ' . date ('r', $TS));
          
          $this->sendXML ($xmppMessage);
          
          while ($line = fgets ($f, 4096)) {
            // Parse the line
            if (($p = strpos ($line, ':')) === false)
              continue;
            
            $Direction = substr ($line, 0, $p);
            $line = substr ($line, $p + 1);
            
            if (($p = strpos ($line, ':')) === false)
              continue;
            
            $TS = intval (substr ($line, 0, $p));
            $Message = base64_decode (rtrim (substr ($line, $p + 1)));
            
            // Generate XMPP-Message
            $xmppMessage = tiggerXMPP_Message::newTag ($Receipient, $this->getJID (true, true, true));
            $xmppMessage->setType (tiggerXMPP_Message::TYPE_CHAT);
            $xmppMessage->setMessage ('<' . ($Direction == self::DIRECTION_IN ? $Opponent : $Owner) . '> ' . $Message);
            $xmppMessage->setTimestamp ($TS, ($Direction == self::DIRECTION_IN ? $Opponent : $Owner), 'Archived message');
            
            $this->sendXML ($xmppMessage);
          }
          
          fclose ($f);
          
          return true;
        
        default:
          return false;
      }
      
      // Assume true here
      return true;
    }
    // }}}
  }

  $Logger = new Example_Logger ('logger', '127.0.0.1', 5200, tiggerXMPP_Stream::DEBUG_WARN);
  
  if (!$Logger->authenticate ('logger-password', 'logger-username'))
    die ('Authentication failed');
  
  $Base = new phpEvents_Base;
  $Base->addEvent ($Logger);
  $Base->loop ();

?>