<?php
/**
* @brief Blog namespace for settings handler
*
* @package Dotclear
* @subpackage Core
*
* @copyright Olivier Meunier & Association Dotclear
* @copyright GPL-2.0-only
*/
if (!defined('DC_RC_PATH')) {
return;
}
class dcNamespace
{
protected $con; ///< <b>connection</b> Database connection object
protected $table; ///< <b>string</b> Settings table name
protected $blog_id; ///< <b>string</b> Blog ID
protected $global_settings = []; ///< <b>array</b> Global settings array
protected $local_settings = []; ///< <b>array</b> Local settings array
protected $settings = []; ///< <b>array</b> Associative settings array
protected $ns; ///< <b>string</b> Current namespace
protected const NS_NAME_SCHEMA = '/^[a-zA-Z][a-zA-Z0-9]+$/';
protected const NS_ID_SCHEMA = '/^[a-zA-Z][a-zA-Z0-9_]+$/';
/**
* Object constructor. Retrieves blog settings and puts them in $settings
* array. Local (blog) settings have a highest priority than global settings.
*
* @param dcCore $core The core
* @param string $blog_id The blog identifier
* @param string $name The namespace ID
* @param mixed $rs
*
* @throws Exception (description)
*/
public function __construct(dcCore &$core, $blog_id, $name, $rs = null)
{
if (preg_match(self::NS_NAME_SCHEMA, $name)) {
$this->ns = $name;
} else {
throw new Exception(sprintf(__('Invalid setting dcNamespace: %s'), $name));
}
$this->con = &$core->con;
$this->table = $core->prefix . 'setting';
$this->blog_id = &$blog_id;
$this->getSettings($rs);
}
private function getSettings($rs = null)
{
if ($rs == null) {
$strReq = 'SELECT blog_id, setting_id, setting_value, ' .
'setting_type, setting_label, setting_ns ' .
'FROM ' . $this->table . ' ' .
"WHERE (blog_id = '" . $this->con->escape($this->blog_id) . "' " .
'OR blog_id IS NULL) ' .
"AND setting_ns = '" . $this->con->escape($this->ns) . "' " .
'ORDER BY setting_id DESC ';
try {
$rs = $this->con->select($strReq);
} catch (Exception $e) {
trigger_error(__('Unable to retrieve settings:') . ' ' . $this->con->error(), E_USER_ERROR);
}
}
while ($rs->fetch()) {
if ($rs->f('setting_ns') != $this->ns) {
break;
}
$id = trim((string) $rs->f('setting_id'));
$value = $rs->f('setting_value');
$type = $rs->f('setting_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->blog_id ? 'local' : 'global';
$this->{$array . '_settings'}[$id] = [
'ns' => $this->ns,
'value' => $value,
'type' => $type,
'label' => (string) $rs->f('setting_label'),
'global' => $rs->blog_id == '',
];
}
$this->settings = $this->global_settings;
foreach ($this->local_settings as $id => $v) {
$this->settings[$id] = $v;
}
return true;
}
/**
* Returns true if a setting exist, else false
*
* @param string $id The identifier
* @param boolean $global The global
*
* @return boolean
*/
public function settingExists($id, $global = false)
{
$array = $global ? 'global' : 'local';
return isset($this->{$array . '_settings'}[$id]);
}
/**
* Returns setting value if exists.
*
* @param string $n Setting name
*
* @return mixed
*/
public function get($n)
{
if (isset($this->settings[$n]) && isset($this->settings[$n]['value'])) {
return $this->settings[$n]['value'];
}
}
/**
* Returns global setting value if exists.
*
* @param string $n Setting name
*
* @return mixed
*/
public function getGlobal($n)
{
if (isset($this->global_settings[$n]) && isset($this->global_settings[$n]['value'])) {
return $this->global_settings[$n]['value'];
}
}
/**
* Returns local setting value if exists.
*
* @param string $n Setting name
*
* @return mixed
*/
public function getLocal($n)
{
if (isset($this->local_settings[$n]) && isset($this->local_settings[$n]['value'])) {
return $this->local_settings[$n]['value'];
}
}
/**
* Magic __get method.
*
* @copydoc ::get
*
* @param string $n Setting name
*
* @return mixed
*/
public function __get($n)
{
return $this->get($n);
}
/**
* Sets a setting in $settings property. This sets the setting for script
* execution time only and if setting exists.
*
* @param string $n The setting name
* @param mixed $v The setting value
*/
public function set($n, $v)
{
if (isset($this->settings[$n])) {
$this->settings[$n]['value'] = $v;
}
}
/**
* Magic __set method.
*
* @copydoc ::set
*
* @param string $n The setting name
* @param mixed $v The setting value
*/
public function __set($n, $v)
{
$this->set($n, $v);
}
/**
* Creates or updates a setting.
*
* $type could be 'string', 'integer', 'float', 'boolean', 'array' or null. If $type is
* null and setting exists, it will keep current setting type.
*
* $value_change allow you to not change setting. Useful if you need to change
* a setting label or type and don't want to change its value.
*
* @param string $id The setting identifier
* @param mixed $value The setting value
* @param string $type The setting type
* @param string $label The setting label
* @param bool $value_change Change setting value or not
* @param bool $global Setting is global
*
* @throws Exception
*/
public function put($id, $value, $type = null, $label = null, $value_change = true, $global = false)
{
if (!preg_match(self::NS_ID_SCHEMA, $id)) {
throw new Exception(sprintf(__('%s is not a valid setting id'), $id));
}
# We don't want to change setting value
if (!$value_change) {
if (!$global && $this->settingExists($id, false)) {
$value = $this->local_settings[$id]['value'];
} elseif ($this->settingExists($id, true)) {
$value = $this->global_settings[$id]['value'];
}
}
# Setting type
if ($type == 'double') {
$type = 'float';
} elseif ($type === null) {
if (!$global && $this->settingExists($id, false)) {
$type = $this->local_settings[$id]['type'];
} elseif ($this->settingExists($id, true)) {
$type = $this->global_settings[$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->settingExists($id, false)) {
$label = $this->local_settings[$id]['label'];
} elseif ($this->settingExists($id, true)) {
$label = $this->global_settings[$id]['label'];
}
}
if ($type != 'array') {
settype($value, $type);
} else {
$value = json_encode($value);
}
$cur = $this->con->openCursor($this->table);
$cur->setting_value = ($type == 'boolean') ? (string) (int) $value : (string) $value;
$cur->setting_type = $type;
$cur->setting_label = $label;
#If we are local, compare to global value
if (!$global && $this->settingExists($id, true)) {
$g = $this->global_settings[$id];
$same_setting = ($g['ns'] == $this->ns && $g['value'] == $value && $g['type'] == $type && $g['label'] == $label);
# Drop setting if same value as global
if ($same_setting && $this->settingExists($id, false)) {
$this->drop($id);
} elseif ($same_setting) {
return;
}
}
if ($this->settingExists($id, $global) && $this->ns == $this->settings[$id]['ns']) {
if ($global) {
$where = 'WHERE blog_id IS NULL ';
} else {
$where = "WHERE blog_id = '" . $this->con->escape($this->blog_id) . "' ";
}
$cur->update($where . "AND setting_id = '" . $this->con->escape($id) . "' AND setting_ns = '" . $this->con->escape($this->ns) . "' ");
} else {
$cur->setting_id = $id;
$cur->blog_id = $global ? null : $this->blog_id;
$cur->setting_ns = $this->ns;
$cur->insert();
}
}
/**
* Rename an existing setting in a Namespace
*
* @param string $oldId The old setting identifier
* @param string $newId The new setting identifier
*
* @throws Exception
*
* @return bool
*/
public function rename($oldId, $newId)
{
if (!$this->ns) {
throw new Exception(__('No namespace specified'));
}
if (!array_key_exists($oldId, $this->settings) || array_key_exists($newId, $this->settings)) {
return false;
}
if (!preg_match(self::NS_ID_SCHEMA, $newId)) {
throw new Exception(sprintf(__('%s is not a valid setting id'), $newId));
}
// Rename the setting in the settings array
$this->settings[$newId] = $this->settings[$oldId];
unset($this->settings[$oldId]);
// Rename the setting in the database
$strReq = 'UPDATE ' . $this->table .
" SET setting_id = '" . $this->con->escape($newId) . "' " .
" WHERE setting_ns = '" . $this->con->escape($this->ns) . "' " .
" AND setting_id = '" . $this->con->escape($oldId) . "' ";
$this->con->execute($strReq);
return true;
}
/**
* Removes an existing setting in a Namespace.
*
* @param string $id The setting identifier
*
* @throws Exception
*/
public function drop($id)
{
if (!$this->ns) {
throw new Exception(__('No namespace specified'));
}
$strReq = 'DELETE FROM ' . $this->table . ' ';
if ($this->blog_id === null) {
$strReq .= 'WHERE blog_id IS NULL ';
} else {
$strReq .= "WHERE blog_id = '" . $this->con->escape($this->blog_id) . "' ";
}
$strReq .= "AND setting_id = '" . $this->con->escape($id) . "' ";
$strReq .= "AND setting_ns = '" . $this->con->escape($this->ns) . "' ";
$this->con->execute($strReq);
}
/**
* Removes every existing specific setting in a namespace
*
* @param string $id Setting ID
* @param boolean $global Remove global setting too
*
* @throws Exception
*/
public function dropEvery($id, $global = false)
{
if (!$this->ns) {
throw new Exception(__('No namespace specified'));
}
$strReq = 'DELETE FROM ' . $this->table . ' WHERE ';
if (!$global) {
$strReq .= 'blog_id IS NOT NULL AND ';
}
$strReq .= "setting_id = '" . $this->con->escape($id) . "' AND setting_ns = '" . $this->con->escape($this->ns) . "' ";
$this->con->execute($strReq);
}
/**
* Removes all existing settings in a Namespace.
*
* @param bool $force_global Force global pref drop
*
* @throws Exception
*/
public function dropAll($force_global = false)
{
if (!$this->ns) {
throw new Exception(__('No namespace specified'));
}
$strReq = 'DELETE FROM ' . $this->table . ' ';
if (($force_global) || ($this->blog_id === null)) {
$strReq .= 'WHERE blog_id IS NULL ';
$global = true;
} else {
$strReq .= "WHERE blog_id = '" . $this->con->escape($this->blog_id) . "' ";
$global = false;
}
$strReq .= "AND setting_ns = '" . $this->con->escape($this->ns) . "' ";
$this->con->execute($strReq);
$array = $global ? 'global' : 'local';
unset($this->{$array . '_settings'});
$this->{$array . '_settings'} = [];
$array = $global ? 'local' : 'global';
$this->settings = $this->{$array . '_settings'};
}
/**
* Dumps a namespace.
*
* @return string
*/
public function dumpNamespace()
{
return $this->ns;
}
/**
* Dumps settings.
*
* @return array
*/
public function dumpSettings()
{
return $this->settings;
}
/**
* Dumps local settings.
*
* @return array
*/
public function dumpLocalSettings()
{
return $this->local_settings;
}
/**
* Dumps global settings.
*
* @return array
*/
public function dumpGlobalSettings()
{
return $this->global_settings;
}
}