<?php
/**
 * Copyright 1999-2017 Horde LLC (http://www.horde.org/)
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.horde.org/licenses/lgpl21.
 *
 * @author   Jon Parise <jon@horde.org>
 * @author   Michael Slusarz <slusarz@horde.org>
 * @category Horde
 * @license  http://www.horde.org/licenses/lgpl21 LGPL 2.1
 * @package  Prefs
 */

/**
 * Preferences storage implementation for a SQL database.
 *
 * @author   Jon Parise <jon@horde.org>
 * @author   Michael Slusarz <slusarz@horde.org>
 * @category Horde
 * @license  http://www.horde.org/licenses/lgpl21 LGPL 2.1
 * @package  Prefs
 */
class Horde_Prefs_Storage_Sql extends Horde_Prefs_Storage_Base
{
    /**
     * Handle for the current database connection.
     *
     * @var Horde_Db_Adapter
     */
    protected $_db;

    /**
     * Constructor.
     *
     * @param string $user   The username.
     * @param array $params  Configuration parameters:
     *                       - db: (Horde_Db_Adapter) [REQUIRED] The DB
     *                         instance.
     *                       - table: (string) The name of the prefs table.
     *                         DEFAULT: 'horde_prefs'
     *
     * @throws InvalidArgumentException
     */
    public function __construct($user, array $params = array())
    {
        if (!isset($params['db'])) {
            throw new InvalidArgumentException('Missing db parameter.');
        }
        $this->_db = $params['db'];
        unset($params['db']);

        $params = array_merge(array(
            'table' => 'horde_prefs'
        ), $params);

        parent::__construct($user, $params);
    }

    /**
     * Returns the charset of the DB backend.
     *
     * @return string  The connection's charset.
     */
    public function getCharset()
    {
        return $this->_db->getOption('charset');
    }

    /**
     */
    public function get($scope_ob)
    {
        $charset = $this->_db->getOption('charset');
        $query = 'SELECT pref_name, pref_value FROM ' .
            $this->_params['table'] . ' ' .
            'WHERE pref_uid = ? AND pref_scope = ?';
        $values = array($this->_params['user'], $scope_ob->scope);

        try {
            $result = $this->_db->select($query, $values);
            $columns = $this->_db->columns($this->_params['table']);
        } catch (Horde_Db_Exception $e) {
            throw new Horde_Prefs_Exception($e);
        }

        foreach ($result as $row) {
            $name = trim($row['pref_name']);
            $value = $columns['pref_value']->binaryToString($row['pref_value']);
            $scope_ob->set($name, Horde_String::convertCharset($value, $charset, 'UTF-8'));
        }

        return $scope_ob;
    }

    /**
     */
    public function store($scope_ob)
    {
        if (!$this->_db->isActive()) {
            $this->_db->reconnect();
        }

        $charset = $this->_db->getOption('charset');

        // For each preference, check for an existing table row and
        // update it if it's there, or create a new one if it's not.
        foreach ($scope_ob->getDirty() as $name) {
            $value = $scope_ob->get($name);

            if (is_null($value)) {
                $this->remove($scope_ob->scope, $name);
            } else {
                $values = array($this->_params['user'], $name, $scope_ob->scope);

                // Does a row already exist for this preference?
                $query = 'SELECT 1 FROM ' . $this->_params['table'] .
                    ' WHERE pref_uid = ? AND pref_name = ?' .
                    ' AND pref_scope = ?';

                try {
                    $check = $this->_db->selectValue($query, $values);
                } catch (Horde_Db_Exception $e) {
                    throw new Horde_Prefs_Exception($e);
                }

                /* Driver has no support for storing locked status. */
                $value = Horde_String::convertCharset($value, 'UTF-8', $charset);
                $value = new Horde_Db_Value_Binary($value);

                if (empty($check)) {
                    // Insert a new row.
                    $values = array(
                        'pref_uid' => $this->_params['user'],
                        'pref_scope' => $scope_ob->scope,
                        'pref_name' => $name,
                        'pref_value' => $value
                    );

                    try {
                        $this->_db->insertBlob($this->_params['table'], $values, null, true);
                    } catch (Horde_Db_Exception $e) {
                        throw new Horde_Prefs_Exception($e);
                    }
                } else {
                    // Update the existing row.
                    try {
                        $this->_db->updateBlob(
                            $this->_params['table'],
                            array('pref_value' => $value),
                            array(
                                'pref_uid = ? AND pref_name = ? AND pref_scope = ?',
                                array($this->_params['user'], $name, $scope_ob->scope)
                            )
                        );
                    } catch (Horde_Db_Exception $e) {
                        throw new Horde_Prefs_Exception($e);
                    }
                }
            }
        }
    }

    /**
     */
    public function remove($scope = null, $pref = null)
    {
        $query = 'DELETE FROM ' . $this->_params['table'] .
                 ' WHERE pref_uid = ?';
        $values = array($this->_params['user']);

        if (!is_null($scope)) {
            $query .= ' AND pref_scope = ?';
            $values[] = $scope;

            if (!is_null($pref)) {
                $query .= ' AND pref_name = ?';
                $values[] = $pref;
            }
        }

        try {
            $this->_db->delete($query, $values);
        } catch (Horde_Db_Exception $e) {
            throw new Horde_Prefs_Exception($e);
        }
    }

    /**
     * Lists all available scopes.
     *
     * @return array The list of scopes stored in the backend.
     */
    public function listScopes()
    {
        $query = 'SELECT ' . $this->_db->distinct('pref_scope') . ' FROM '
            . $this->_params['table'];
        try {
            return $this->_db->selectValues($query);
        } catch (Horde_Db_Exception $e) {
            throw new Horde_Prefs_Exception($e);
        }
    }

}
