<?php
/**
 * @brief User workspace for preferences handler
 *
 * @package Dotclear
 * @subpackage Core
 *
 * @copyright Olivier Meunier & Association Dotclear
 * @copyright GPL-2.0-only
 */

if (!defined('DC_RC_PATH')) {return;}

class dcWorkspace
{
    protected $con;     ///< <b>connection</b> Database connection object
    protected $table;   ///< <b>string</b> Preferences table name
    protected $user_id; ///< <b>string</b> User ID

    protected $global_prefs = array(); ///< <b>array</b> Global prefs array
    protected $local_prefs  = array(); ///< <b>array</b> Local prefs array
    protected $prefs        = array(); ///< <b>array</b> Associative prefs array
    protected $ws;                     ///< <b>string</b> Current workspace

    /**
    Object constructor. Retrieves user prefs and puts them in $prefs
    array. Local (user) prefs have a highest priority than global prefs.

    @param    name        <b>string</b>        ID for this workspace
     */
    public function __construct(&$core, $user_id, $name, $rs = null)
    {
        if (preg_match('/^[a-zA-Z][a-zA-Z0-9]+$/', $name)) {
            $this->ws = $name;
        } else {
            throw new Exception(sprintf(__('Invalid dcWorkspace: %s'), $name));
        }

        $this->con     = &$core->con;
        $this->table   = $core->prefix . 'pref';
        $this->user_id = &$user_id;

        try { $this->getPrefs($rs);} catch (Exception $e) {
            if (version_compare($core->getVersion('core'), '2.3', '>')) {
                trigger_error(__('Unable to retrieve prefs:') . ' ' . $this->con->error(), E_USER_ERROR);
            }
        }
    }

    private function getPrefs($rs = null)
    {
        if ($rs == null) {
            $strReq = 'SELECT user_id, pref_id, pref_value, ' .
            'pref_type, pref_label, pref_ws ' .
            'FROM ' . $this->table . ' ' .
            "WHERE (user_id = '" . $this->con->escape($this->user_id) . "' " .
            'OR user_id IS NULL) ' .
            "AND pref_ws = '" . $this->con->escape($this->ws) . "' " .
                'ORDER BY pref_id ASC ';

            try {
                $rs = $this->con->select($strReq);
            } catch (Exception $e) {
                throw $e;
            }
        }
        while ($rs->fetch()) {
            if ($rs->f('pref_ws') != $this->ws) {
                break;
            }
            $id    = trim($rs->f('pref_id'));
            $value = $rs->f('pref_value');
            $type  = $rs->f('pref_type');

            if ($type == 'array') {
                $value = @json_decode($value, true);
            } else {
                if ($type == 'float' || $type == 'double') {
                    $type = 'float';
                } elseif ($type != 'boolean' && $type != 'integer') {
                    $type = 'string';
                }
            }

            settype($value, $type);

            $array = $rs->user_id ? 'local' : 'global';

            $this->{$array . '_prefs'}[$id] = array(
                'ws'     => $this->ws,
                'value'  => $value,
                'type'   => $type,
                'label'  => (string) $rs->f('pref_label'),
                'global' => $rs->user_id == ''
            );
        }

        $this->prefs = $this->global_prefs;

        foreach ($this->local_prefs as $id => $v) {
            $this->prefs[$id] = $v;
        }

        return true;
    }

    public function prefExists($id, $global = false)
    {
        $array = $global ? 'global' : 'local';
        return isset($this->{$array . '_prefs'}[$id]);
    }

    /**
    Returns pref value if exists.

    @param    n        <b>string</b>        Pref name
    @return    <b>mixed</b>
     */
    public function get($n)
    {
        if (isset($this->prefs[$n]['value'])) {
            return $this->prefs[$n]['value'];
        }

        return;
    }

    /**
    Returns global pref value if exists.

    @param    n        <b>string</b>        Pref name
    @return    <b>mixed</b>
     */
    public function getGlobal($n)
    {
        if (isset($this->global_prefs[$n]['value'])) {
            return $this->global_prefs[$n]['value'];
        }

        return;
    }

    /**
    Returns local pref value if exists.

    @param    n        <b>string</b>        Pref name
    @return    <b>mixed</b>
     */
    public function getLocal($n)
    {
        if (isset($this->local_prefs[$n]['value'])) {
            return $this->local_prefs[$n]['value'];
        }

        return;
    }
    /**
    Magic __get method.
    @copydoc ::get
     */
    public function __get($n)
    {
        return $this->get($n);
    }

    /**
    Sets a pref in $prefs property. This sets the pref for script
    execution time only and if pref exists.

    @param    n        <b>string</b>        Pref name
    @param    v        <b>mixed</b>        Pref value
     */
    public function set($n, $v)
    {
        if (isset($this->prefs[$n])) {
            $this->prefs[$n]['value'] = $v;
        }
    }

    /**
    Magic __set method.
    @copydoc ::set
     */
    public function __set($n, $v)
    {
        $this->set($n, $v);
    }

    /**
    Creates or updates a pref.

    $type could be 'string', 'integer', 'float', 'boolean' or null. If $type is
    null and pref exists, it will keep current pref type.

    $value_change allow you to not change pref. Useful if you need to change
    a pref label or type and don't want to change its value.

    @param    id            <b>string</b>        Pref ID
    @param    value        <b>mixed</b>        Pref value
    @param    type            <b>string</b>        Pref type
    @param    label        <b>string</b>        Pref label
    @param    value_change    <b>boolean</b>        Change pref value or not
    @param    global        <b>boolean</b>        Pref is global
     */
    public function put($id, $value, $type = null, $label = null, $value_change = true, $global = false)
    {
        if (!preg_match('/^[a-zA-Z][a-zA-Z0-9_]+$/', $id)) {
            throw new Exception(sprintf(__('%s is not a valid pref id'), $id));
        }

        # We don't want to change pref value
        if (!$value_change) {
            if (!$global && $this->prefExists($id, false)) {
                $value = $this->local_prefs[$id]['value'];
            } elseif ($this->prefExists($id, true)) {
                $value = $this->global_prefs[$id]['value'];
            }
        }

        # Pref type
        if ($type == 'double') {
            $type = 'float';
        } elseif ($type === null) {
            if (!$global && $this->prefExists($id, false)) {
                $type = $this->local_prefs[$id]['type'];
            } elseif ($this->prefExists($id, true)) {
                $type = $this->global_prefs[$id]['type'];
            } else {
                if (is_array($value)) {
                    $type = 'array';
                } else {
                    $type = 'string';
                }
            }
        } elseif ($type != 'boolean' && $type != 'integer' && $type != 'float' && $type != 'array') {
            $type = 'string';
        }

        # We don't change label
        if ($label == null) {
            if (!$global && $this->prefExists($id, false)) {
                $label = $this->local_prefs[$id]['label'];
            } elseif ($this->prefExists($id, true)) {
                $label = $this->global_prefs[$id]['label'];
            }
        }

        if ($type != 'array') {
            settype($value, $type);
        } else {
            $value = json_encode($value);
        }

        $cur             = $this->con->openCursor($this->table);
        $cur->pref_value = ($type == 'boolean') ? (string) (integer) $value : (string) $value;
        $cur->pref_type  = $type;
        $cur->pref_label = $label;

        #If we are local, compare to global value
        if (!$global && $this->prefExists($id, true)) {
            $g         = $this->global_prefs[$id];
            $same_pref = $g['ws'] == $this->ws && $g['value'] == $value
                && $g['type'] == $type && $g['label'] == $label;

            # Drop pref if same value as global
            if ($same_pref && $this->prefExists($id, false)) {
                $this->drop($id);
            } elseif ($same_pref) {
                return;
            }
        }

        if ($this->prefExists($id, $global) && $this->ws == $this->prefs[$id]['ws']) {
            if ($global) {
                $where = 'WHERE user_id IS NULL ';
            } else {
                $where = "WHERE user_id = '" . $this->con->escape($this->user_id) . "' ";
            }

            $cur->update($where . "AND pref_id = '" . $this->con->escape($id) . "' AND pref_ws = '" . $this->con->escape($this->ws) . "' ");
        } else {
            $cur->pref_id = $id;
            $cur->user_id = $global ? null : $this->user_id;
            $cur->pref_ws = $this->ws;

            $cur->insert();
        }
    }

    /**
    Rename an existing pref in a Workspace

    @param     $oldId     <b>string</b>     Current pref name
    @param     $newId     <b>string</b>     New pref name
    @return     <b>boolean</b>
     */
    public function rename($oldId, $newId)
    {
        if (!$this->ws) {
            throw new Exception(__('No workspace specified'));
        }

        if (!array_key_exists($oldId, $this->prefs) || array_key_exists($newId, $this->prefs)) {
            return false;
        }

        // Rename the pref in the prefs array
        $this->prefs[$newId] = $this->prefs[$oldId];
        unset($this->prefs[$oldId]);

        // Rename the pref in the database
        $strReq = 'UPDATE ' . $this->table .
        " SET pref_id = '" . $this->con->escape($newId) . "' " .
        " WHERE pref_ws = '" . $this->con->escape($this->ws) . "' " .
        " AND pref_id = '" . $this->con->escape($oldId) . "' ";
        $this->con->execute($strReq);
        return true;
    }

    /**
    Removes an existing pref. Workspace

    @param    id        <b>string</b>        Pref ID
    @param    force_global    <b>boolean</b>    Force global pref drop
     */
    public function drop($id, $force_global = false)
    {
        if (!$this->ws) {
            throw new Exception(__('No workspace specified'));
        }

        $strReq = 'DELETE FROM ' . $this->table . ' ';

        if (($force_global) || ($this->user_id === null)) {
            $strReq .= 'WHERE user_id IS NULL ';
            $global = true;
        } else {
            $strReq .= "WHERE user_id = '" . $this->con->escape($this->user_id) . "' ";
            $global = false;
        }

        $strReq .= "AND pref_id = '" . $this->con->escape($id) . "' ";
        $strReq .= "AND pref_ws = '" . $this->con->escape($this->ws) . "' ";

        $this->con->execute($strReq);

        if ($this->prefExists($id, $global)) {
            $array = $global ? 'global' : 'local';
            unset($this->{$array . '_prefs'}[$id]);
        }

        $this->prefs = $this->global_prefs;
        foreach ($this->local_prefs as $id => $v) {
            $this->prefs[$id] = $v;
        }
    }

    /**
    Removes all existing pref. in a Workspace

    @param    force_global    <b>boolean</b>    Force global pref drop
     */
    public function dropAll($force_global = false)
    {
        if (!$this->ws) {
            throw new Exception(__('No workspace specified'));
        }

        $strReq = 'DELETE FROM ' . $this->table . ' ';

        if (($force_global) || ($this->user_id === null)) {
            $strReq .= 'WHERE user_id IS NULL ';
            $global = true;
        } else {
            $strReq .= "WHERE user_id = '" . $this->con->escape($this->user_id) . "' ";
            $global = false;
        }

        $strReq .= "AND pref_ws = '" . $this->con->escape($this->ws) . "' ";

        $this->con->execute($strReq);

        $array = $global ? 'global' : 'local';
        unset($this->{$array . '_prefs'});
        $this->{$array . '_prefs'} = array();

        $array       = $global ? 'local' : 'global';
        $this->prefs = $this->{$array . '_prefs'};
    }

    /**
    Returns $ws property content.

    @return    <b>string</b>
     */
    public function dumpWorkspace()
    {
        return $this->ws;
    }

    /**
    Returns $prefs property content.

    @return    <b>array</b>
     */
    public function dumpPrefs()
    {
        return $this->prefs;
    }

    /**
    Returns $local_prefs property content.

    @return    <b>array</b>
     */
    public function dumpLocalPrefs()
    {
        return $this->local_prefs;
    }

    /**
    Returns $global_prefs property content.

    @return    <b>array</b>
     */
    public function dumpGlobalPrefs()
    {
        return $this->global_prefs;
    }

}
