<?php
/**
 * Tine 2.0
 *
 * @package     Felamimail
 * @subpackage  Sieve
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Lars Kneschke <l.kneschke@metaways.de>
 * @copyright   Copyright (c) 2010-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 * 
 */

/**
 * class to parse and create Sieve scripts
 * 
 * This class does not really parse the Sieve script, but either parses SmartSieves pseudo code
 * or reads the serialized rules and vacation objects.
 *
 * @package     Felamimail
 * @subpackage  Sieve
 */
class Felamimail_Sieve_Backend_Script extends Felamimail_Sieve_Backend_Abstract
{
    /**
     * the script to parse
     * 
     * @var string
     */
    protected $_scriptToParse = NULL;
    
    /**
     * constructor
     * 
     * @param   string  $script     the Sieve script or null
     */
    public function __construct($_script = null)
    {
        if ($_script !== null) {
            $this->setScriptToParse($_script);
            $this->readScriptData();
        }
    }
    
    /**
     * set script
     * 
     * @param string $_script
     */
    public function setScriptToParse($_script)
    {
        $this->_scriptToParse = $_script;
    }
    
    /**
     * parse Sieve script (only pseudo scripts get loaded)
     * 
     * @throws Felamimail_Exception
     * 
     * @todo try to repair script on unserialize error and/or prevent bad chars (?) while serializing
     */
    public function readScriptData()
    {
        if ($this->_scriptToParse === NULL) {
            throw new Felamimail_Exception('No script to parse has been set.');
        }
        
        $line = strtok($this->_scriptToParse, "\n");

        while ($line !== false) {
            if (preg_match("/^#rule&&(.*)&&(.*)&&(.*)&&(.*)&&(.*)&&(.*)&&(.*)&&(.*)&&(.*)&&(.*)&&(.*)$/i", $line, $matches)) {
                $this->_parseSmartSieveRule($matches);
            } elseif (preg_match("/^#vacation&&(.*)&&(.*)&&(.*)&&(.*)/i", $line, $matches)) {
                $this->_parseSmartSieveVacation($matches);
            } elseif (preg_match("/^#SieveRule(.*)/i", $line, $matches)) {
                $rule = unserialize($this->_unescapeChars($matches[1]));
                if ($rule === FALSE) {
                    throw new Exception('Error in unserialize() - could not parse rule.');
                }
                $this->addRule($rule);
            } elseif (preg_match("/^#SieveVacation(.*)/i", $line, $matches)) {
                $vacation = unserialize($this->_unescapeChars($matches[1]));
                if ($vacation === FALSE) {
                    throw new Exception('Error in unserialize() - could not parse vacation.');
                }
                $this->setVacation($vacation);
            }
            
            $line = strtok("\n");
        }
    }
    
    /**
     * parse rule generated by SmartSieve
     * 
     * @param   array   $parts
     */
    protected function _parseSmartSieveRule(array $parts)
    {
        $rule = new Felamimail_Sieve_Rule();
        
        $rule->setId($parts[1])
            ->setEnabled(($parts[2] == 'ENABLED') ? true : false);
        
        // conditions
        
        // from test
        if (!empty($parts[3])) {
            $condition = new Felamimail_Sieve_Rule_Condition();
            
            $condition->setTest(Felamimail_Sieve_Rule_Condition::TEST_ADDRESS)
                ->setComperator(Felamimail_Sieve_Rule_Condition::COMPERATOR_CONTAINS)
                ->setHeader('from')
                ->setKey($this->_unescapeChars($parts[3]));

            $rule->addCondition($condition);
        }
        // to test
        if (!empty($parts[4])) {
            $condition = new Felamimail_Sieve_Rule_Condition();
            
            $condition->setTest(Felamimail_Sieve_Rule_Condition::TEST_ADDRESS)
                ->setComperator(Felamimail_Sieve_Rule_Condition::COMPERATOR_CONTAINS)
                ->setHeader(array('to', 'cc'))
                ->setKey($this->_unescapeChars($parts[4]));

            $rule->addCondition($condition);
        }
        // subject test
        if (!empty($parts[5])) {
            $condition = new Felamimail_Sieve_Rule_Condition();
            
            $condition->setTest(Felamimail_Sieve_Rule_Condition::TEST_HEADER)
                ->setComperator(Felamimail_Sieve_Rule_Condition::COMPERATOR_CONTAINS)
                ->setHeader('subject')
                ->setKey($this->_unescapeChars($parts[5]));

            $rule->addCondition($condition);
        }
        // header test
        if (!empty($parts[9])) {
            $condition = new Felamimail_Sieve_Rule_Condition();
            
            $condition->setTest(Felamimail_Sieve_Rule_Condition::TEST_HEADER)
                ->setComperator(Felamimail_Sieve_Rule_Condition::COMPERATOR_CONTAINS)
                ->setHeader($this->_unescapeChars($parts[9]))
                ->setKey($this->_unescapeChars($parts[10]));

            $rule->addCondition($condition);
        }
        // size
        if (!empty($parts[11])) {
            $condition = new Felamimail_Sieve_Rule_Condition();
            
            $condition->setTest(Felamimail_Sieve_Rule_Condition::TEST_SIZE)
                ->setComperator(Felamimail_Sieve_Rule_Condition::COMPERATOR_OVER)
                ->setKey($this->_unescapeChars($parts[11]));

            $rule->addCondition($condition);
        }
        
        // action
        $action = new Felamimail_Sieve_Rule_Action();
        
        switch ($this->_unescapeChars($parts[6])) {
            case ('folder'):
                $action->setType(Felamimail_Sieve_Rule_Action::FILEINTO)
                    ->setArgument($this->_unescapeChars($parts[7]));
                break;
            case ('address'):
                $action->setType(Felamimail_Sieve_Rule_Action::REDIRECT)
                    ->setArgument($this->_unescapeChars($parts[7]));
                break;
            case ('reject'):
                $action->setType(Felamimail_Sieve_Rule_Action::REJECT)
                    ->setArgument($this->_unescapeChars($parts[7]));
                break;
            case ('discard'):
                $action->setType(Felamimail_Sieve_Rule_Action::DISCARD);
                break;
        }
        $rule->setAction($action);
        
        //parts[8] == regex bit
        
        $this->addRule($rule);
    }
    
    /**
     * parse sieve parts and set vacation
     * 
     * @param array $parts
     */
    protected function _parseSmartSieveVacation($parts)
    {
        $vacation = new Felamimail_Sieve_Vacation();
        
        $vacation->setDays($parts[1])
            ->setReason($this->_unescapeChars($parts[3]))
            ->setEnabled(($parts[4] == 'on') ? true : false);
        
        $addresses = explode(',', $this->_unescapeChars($parts[2]));
        $addresses = array_map('trim', $addresses, array('"'));
        foreach($addresses as $address) {
            $vacation->addAddress($address);
        }
        
        $this->setVacation($vacation);
    }
    
    
    /**
     * get sieve script as string
     * 
     * @return string
     */
    public function getSieve()
    {
        $sieve = parent::getSieve();
        
        $pseudoScript = $this->_getPseudoScript();
        $sieve = $sieve . $pseudoScript;
        
        return $sieve;
    }
    
    /**
     * get pseudo script
     * 
     * @return string
     */
    protected function _getPseudoScript()
    {
        $pseudoScript = '';
        
        foreach ($this->_rules as $rule) {
            $pseudoScript .= '#SieveRule' . $this->_escapeChars(serialize($rule)) . "\r\n";
        }      
        
        if ($this->_vacation instanceof Felamimail_Sieve_Vacation) {
            $pseudoScript .= '#SieveVacation' . $this->_escapeChars(serialize($this->_vacation)) . "\r\n";
        } 
               
        return $pseudoScript;
    }
    
    /**
     * Unescape characters breaking Sieve comments
     *
     * @param   string  $string the string to unescape
     * @return  string          The unescaped string
     */
    protected function _unescapeChars($string)
    {
        $search = array('\n', '\&', '\O\0');
        $replace = array("\r\n", "&", "\x00");
        $string = str_replace($search, $replace, $string);

        return $string;
    }
        
    /**
     * Escape characters breaking Sieve comments
     *
     * @param   string  $string the string to escape
     * @return  string          The unescaped string
     */
    protected function _escapeChars($string)
    {
        $search = array("\r\n", "\x00");
        $replace = array('\n', '\O\0');
        $string = str_replace($search, $replace, $string);

        return $string;
    }    
}
