<?php
/**
 * Copyright 2012-2016 Horde LLC (http://www.horde.org/)
 *
 * See the enclosed file LICENSE for license information (ASL).  If you
 * did not receive this file, see http://www.horde.org/licenses/apache.
 *
 * @author   Jan Schneider <jan@horde.org>
 * @author   Mike Cochrane <mike@graftonhall.co.nz>
 * @category Horde
 * @license  http://www.horde.org/licenses/apache ASL
 * @package  Ingo
 */

/**
 * Ingo_Storage_Sql implements the Ingo_Storage API to save Ingo data via
 * Horde's Horde_Db database abstraction layer.
 *
 * @author   Jan Schneider <jan@horde.org>
 * @author   Mike Cochrane <mike@graftonhall.co.nz>
 * @category Horde
 * @license  http://www.horde.org/licenses/apache ASL
 * @package  Ingo
 */
class Ingo_Storage_Sql extends Ingo_Storage
{
    /**
     * Retrieves the specified data from the storage backend.
     *
     * @param integer $field     The field name of the desired data.
     *                           See lib/Storage.php for the available fields.
     * @param boolean $readonly  Whether to disable any write operations.
     *
     * @return Ingo_Storage_Rule|Ingo_Storage_Filters  The specified data.
     * @throws Ingo_Exception
     */
    protected function _retrieve($field, $readonly = false)
    {
        switch ($field) {
        case self::ACTION_BLACKLIST:
        case self::ACTION_WHITELIST:
            if ($field == self::ACTION_BLACKLIST) {
                $ob = new Ingo_Storage_Blacklist();
                $filters = $this->retrieve(self::ACTION_FILTERS);
                $rule = $filters->findRule($field);
                if (isset($rule['action-value'])) {
                    $ob->setBlacklistFolder($rule['action-value']);
                }
            } else {
                $ob = new Ingo_Storage_Whitelist();
            }
            $query = sprintf('SELECT list_address FROM %s WHERE list_owner = ? AND list_blacklist = ?',
                             $this->_params['table_lists']);
            $values = array(Ingo::getUser(),
                            (int)($field == self::ACTION_BLACKLIST));
            try {
                $addresses = $this->_params['db']->selectValues($query, $values);
            } catch (Horde_Db_Exception $e) {
                Horde::log($e->getMessage(), 'ERR');
                throw new Ingo_Exception($e);
            }
            if ($field == self::ACTION_BLACKLIST) {
                $ob->setBlacklist($addresses);
            } else {
                $ob->setWhitelist($addresses);
            }
            break;

        case self::ACTION_FILTERS:
            $ob = new Ingo_Storage_Filters_Sql($this->_params['db'], $this->_params);
            $ob->init($readonly);
            break;

        case self::ACTION_FORWARD:
            $query = sprintf('SELECT * FROM %s WHERE forward_owner = ?',
                             $this->_params['table_forwards']);

            try {
                $data = $this->_params['db']->selectOne(
                    $query, array(Ingo::getUser())
                );
                $columns = $this->_params['db']->columns(
                    $this->_params['table_forwards']
                );
            } catch (Horde_Db_Exception $e) {
                throw new Ingo_Exception($e);
            }
            $ob = new Ingo_Storage_Forward();
            if (!empty($data)) {
                $ob->setForwardAddresses(explode(
                    "\n",
                    $columns['forward_addresses']->binaryToString(
                        $data['forward_addresses']
                    )
                ));
                $ob->setForwardKeep((bool)$data['forward_keep']);
                $ob->setSaved(true);
            } elseif ($data = @unserialize($GLOBALS['prefs']->getDefault('forward'))) {
                $ob->setForwardAddresses($data['a']);
                $ob->setForwardKeep($data['k']);
            }
            break;

        case self::ACTION_VACATION:
            $query = sprintf('SELECT * FROM %s WHERE vacation_owner = ?',
                             $this->_params['table_vacations']);

            try {
                $data = $this->_params['db']->selectOne(
                    $query, array(Ingo::getUser())
                );
                $columns = $this->_params['db']->columns(
                    $this->_params['table_vacations']
                );
            } catch (Horde_Db_Exception $e) {
                throw new Ingo_Exception($e);
            }
            $ob = new Ingo_Storage_Vacation();
            if (!empty($data)) {
                $ob->setVacationAddresses(explode(
                    "\n",
                    $columns['vacation_addresses']->binaryToString(
                        $data['vacation_addresses']
                    )
                ));
                $ob->setVacationDays((int)$data['vacation_days']);
                $ob->setVacationStart((int)$data['vacation_start']);
                $ob->setVacationEnd((int)$data['vacation_end']);
                $ob->setVacationExcludes(explode(
                    "\n",
                    $columns['vacation_excludes']->binaryToString(
                        $data['vacation_excludes']
                    )
                ));
                $ob->setVacationIgnorelist((bool)$data['vacation_ignorelists']);
                $ob->setVacationReason(Horde_String::convertCharset(
                    $columns['vacation_reason']->binaryToString(
                        $data['vacation_reason']
                    ),
                    $this->_params['charset'],
                    'UTF-8'
                ));
                $ob->setVacationSubject(Horde_String::convertCharset(
                    $data['vacation_subject'],
                    $this->_params['charset'],
                    'UTF-8'
                ));
                $ob->setSaved(true);
            } elseif ($data = @unserialize($GLOBALS['prefs']->getDefault('vacation'))) {
                $ob->setVacationAddresses($data['addresses'], false);
                $ob->setVacationDays($data['days']);
                $ob->setVacationExcludes($data['excludes']);
                $ob->setVacationIgnorelist($data['ignorelist']);
                $ob->setVacationReason($data['reason']);
                $ob->setVacationSubject($data['subject']);
                if (isset($data['start'])) {
                    $ob->setVacationStart($data['start']);
                }
                if (isset($data['end'])) {
                    $ob->setVacationEnd($data['end']);
                }
            }
            break;

        case self::ACTION_SPAM:
            $query = sprintf('SELECT * FROM %s WHERE spam_owner = ?',
                             $this->_params['table_spam']);

            try {
                $data = $this->_params['db']->selectOne($query, array(Ingo::getUser()));
            } catch (Horde_Db_Exception $e) {
                throw new Ingo_Exception($e);
            }
            $ob = new Ingo_Storage_Spam();
            if (!empty($data)) {
                $ob->setSpamFolder($data['spam_folder']);
                $ob->setSpamLevel((int)$data['spam_level']);
                $ob->setSaved(true);
            } elseif ($data = @unserialize($GLOBALS['prefs']->getDefault('spam'))) {
                $ob->setSpamFolder($data['folder']);
                $ob->setSpamLevel($data['level']);
            }
            break;

        default:
            $ob = false;
        }

        return $ob;
    }

    /**
     * Stores the specified data in the storage backend.
     *
     * @access private
     *
     * @param Ingo_Storage_Rule|Ingo_Storage_Filters $ob  The object to store.
     */
    protected function _store($ob)
    {
        switch ($ob->obType()) {
        case self::ACTION_BLACKLIST:
        case self::ACTION_WHITELIST:
            $is_blacklist = (int)($ob->obType() == self::ACTION_BLACKLIST);
            if ($is_blacklist) {
                $filters = $this->retrieve(self::ACTION_FILTERS);
                $id = $filters->findRuleId(self::ACTION_BLACKLIST);
                if ($id !== null) {
                    $rule = $filters->getRule($id);
                    if (!isset($rule['action-value']) ||
                        $rule['action-value'] != $ob->getBlacklistFolder()) {
                        $rule['action-value'] = $ob->getBlacklistFolder();
                        $filters->updateRule($rule, $id);
                    }
                }
            }
            $query = sprintf('DELETE FROM %s WHERE list_owner = ? AND list_blacklist = ?',
                             $this->_params['table_lists']);
            $values = array(Ingo::getUser(), $is_blacklist);
            try {
                $this->_params['db']->delete($query, $values);
            } catch (Horde_Db_Exception $e) {
                Horde::log($e, 'ERR');
                throw new Ingo_Exception($e);
            }
            $query = sprintf('INSERT INTO %s (list_owner, list_blacklist, list_address) VALUES (?, ?, ?)',
                             $this->_params['table_lists']);

            $addresses = $is_blacklist ? $ob->getBlacklist() : $ob->getWhitelist();
            foreach ($addresses as $address) {
                try {
                    $result = $this->_params['db']->insert(
                        $query,
                        array(Ingo::getUser(),
                              $is_blacklist,
                              $address));
                } catch (Horde_Db_Exception $e) {
                    Horde::log($e, 'ERR');
                    throw new Ingo_Exception($e);
                }
            }
            $ob->setSaved(true);
            break;

        case self::ACTION_FORWARD:
            $values = array(
                new Horde_Db_Value_Text(
                    implode("\n", $ob->getForwardAddresses())
                ),
                (int)(bool)$ob->getForwardKeep(),
                Ingo::getUser());
            try {
                if ($ob->isSaved()) {
                    $query = sprintf('UPDATE %s SET forward_addresses = ?, forward_keep = ? WHERE forward_owner = ?', $this->_params['table_forwards']);
                    $this->_params['db']->update($query, $values);
                } else {
                    $query = sprintf('INSERT INTO %s (forward_addresses, forward_keep, forward_owner) VALUES (?, ?, ?)', $this->_params['table_forwards']);
                    $this->_params['db']->insert($query, $values);
                }
            } catch (Horde_Db_Exception $e) {
                throw new Ingo_Exception($e);
            }
            $ob->setSaved(true);
            break;

        case self::ACTION_VACATION:
            $values = array(
                new Horde_Db_Value_Text(
                    implode("\n", $ob->getVacationAddresses())
                ),
                Horde_String::convertCharset(
                    $ob->getVacationSubject(),
                    'UTF-8',
                    $this->_params['charset']
                ),
                new Horde_Db_Value_Text(Horde_String::convertCharset(
                    $ob->getVacationReason(),
                    'UTF-8',
                    $this->_params['charset']
                )),
                (int)$ob->getVacationDays(),
                (int)$ob->getVacationStart(),
                (int)$ob->getVacationEnd(),
                new Horde_Db_Value_Text(
                    implode("\n", $ob->getVacationExcludes())
                ),
                (int)(bool)$ob->getVacationIgnorelist(),
                Ingo::getUser()
            );
            try {
                if ($ob->isSaved()) {
                    $query = sprintf('UPDATE %s SET vacation_addresses = ?, vacation_subject = ?, vacation_reason = ?, vacation_days = ?, vacation_start = ?, vacation_end = ?, vacation_excludes = ?, vacation_ignorelists = ? WHERE vacation_owner = ?', $this->_params['table_vacations']);
                    $this->_params['db']->update($query, $values);
                } else {
                    $query = sprintf('INSERT INTO %s (vacation_addresses, vacation_subject, vacation_reason, vacation_days, vacation_start, vacation_end, vacation_excludes, vacation_ignorelists, vacation_owner) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', $this->_params['table_vacations']);
                    $this->_params['db']->insert($query, $values);
                }
            } catch (Horde_Db_Exception $e) {
                throw new Ingo_Exception($e);
            }
            $ob->setSaved(true);
            break;

        case self::ACTION_SPAM:
            $values = array(
                (int)$ob->getSpamLevel(),
                $ob->getSpamFolder(),
                Ingo::getUser());
            try {
                if ($ob->isSaved()) {
                    $query = sprintf('UPDATE %s SET spam_level = ?, spam_folder = ? WHERE spam_owner = ?', $this->_params['table_spam']);
                    $this->_params['db']->update($query, $values);
                } else {
                    $query = sprintf('INSERT INTO %s (spam_level, spam_folder, spam_owner) VALUES (?, ?, ?)', $this->_params['table_spam']);
                    $this->_params['db']->insert($query, $values);
                }
            } catch (Horde_Db_Exception $e) {
                throw new Ingo_Exception($e);
            }
            $ob->setSaved(true);
            break;
        }
    }

    /**
     * Removes the data of the specified user from the storage backend.
     *
     * @param string $user  The user name to delete filters for.
     *
     * @throws Ingo_Exception
     */
    public function removeUserData($user)
    {
        if (!$GLOBALS['registry']->isAdmin() &&
            $user != $GLOBALS['registry']->getAuth()) {
            throw new Ingo_Exception(_("Permission Denied"));
        }

        $queries = array(sprintf('DELETE FROM %s WHERE rule_owner = ?',
                                 $this->_params['table_rules']),
                         sprintf('DELETE FROM %s WHERE list_owner = ?',
                                 $this->_params['table_lists']),
                         sprintf('DELETE FROM %s WHERE vacation_owner = ?',
                                 $this->_params['table_vacations']),
                         sprintf('DELETE FROM %s WHERE forward_owner = ?',
                                 $this->_params['table_forwards']),
                         sprintf('DELETE FROM %s WHERE spam_owner = ?',
                                 $this->_params['table_spam']));

        $values = array($user);
        foreach ($queries as $query) {
            try {
                $this->_params['db']->delete($query, $values);
            } catch (Horde_Db_Exception $e) {
                throw new Ingo_Exception($e);
            }
        }

        return true;
    }

}
