343 lines
9.7 KiB
PHP
343 lines
9.7 KiB
PHP
<?php
|
|
/**
|
|
* Piwik - free/libre analytics platform
|
|
*
|
|
* @link http://piwik.org
|
|
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
|
*
|
|
*/
|
|
|
|
namespace Piwik\Settings;
|
|
|
|
use Piwik\Piwik;
|
|
use Piwik\Settings\Storage\Storage;
|
|
use Exception;
|
|
use Piwik\Validators\BaseValidator;
|
|
|
|
/**
|
|
* Base setting type class.
|
|
*
|
|
* @api
|
|
*/
|
|
class Setting
|
|
{
|
|
|
|
/**
|
|
* The name of the setting
|
|
* @var string
|
|
*/
|
|
protected $name;
|
|
|
|
/**
|
|
* Null while not initialized, bool otherwise.
|
|
* @var null|bool
|
|
*/
|
|
protected $hasWritePermission = null;
|
|
|
|
/**
|
|
* @var Storage
|
|
*/
|
|
protected $storage;
|
|
|
|
/**
|
|
* @var string
|
|
*/
|
|
protected $pluginName;
|
|
|
|
/**
|
|
* @var FieldConfig
|
|
*/
|
|
protected $config;
|
|
|
|
/**
|
|
* @var \Closure|null
|
|
*/
|
|
protected $configureCallback;
|
|
|
|
/**
|
|
* @var mixed
|
|
*/
|
|
protected $defaultValue;
|
|
|
|
/**
|
|
* @var string
|
|
*/
|
|
protected $type;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param string $name The setting's persisted name. Only alphanumeric characters are allowed, eg,
|
|
* `'refreshInterval'`.
|
|
* @param mixed $defaultValue Default value for this setting if no value was specified.
|
|
* @param string $type Eg an array, int, ... see SettingConfig::TYPE_* constants
|
|
* @param string $pluginName The name of the plugin the setting belongs to
|
|
* @throws Exception
|
|
*/
|
|
public function __construct($name, $defaultValue, $type, $pluginName)
|
|
{
|
|
if (!ctype_alnum(str_replace('_', '', $name))) {
|
|
$msg = sprintf('The setting name "%s" in plugin "%s" is invalid. Only underscores, alpha and numerical characters are allowed', $name, $pluginName);
|
|
throw new Exception($msg);
|
|
}
|
|
|
|
$this->name = $name;
|
|
$this->type = $type;
|
|
$this->pluginName = $pluginName;
|
|
$this->setDefaultValue($defaultValue);
|
|
}
|
|
|
|
/**
|
|
* Get the name of the setting.
|
|
* @return string
|
|
*/
|
|
public function getName()
|
|
{
|
|
return $this->name;
|
|
}
|
|
|
|
/**
|
|
* Get the PHP type of the setting.
|
|
* @return string
|
|
*/
|
|
public function getType()
|
|
{
|
|
return $this->type;
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
* @ignore
|
|
* @param $callback
|
|
*/
|
|
public function setConfigureCallback($callback)
|
|
{
|
|
$this->configureCallback = $callback;
|
|
$this->config = null;
|
|
}
|
|
|
|
/**
|
|
* @return mixed
|
|
*/
|
|
public function getDefaultValue()
|
|
{
|
|
return $this->defaultValue;
|
|
}
|
|
|
|
/**
|
|
* Sets/overwrites the current default value
|
|
* @param string $defaultValue
|
|
*/
|
|
public function setDefaultValue($defaultValue)
|
|
{
|
|
$this->defaultValue = $defaultValue;
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
* @param Storage $storage
|
|
*/
|
|
public function setStorage(Storage $storage)
|
|
{
|
|
$this->storage = $storage;
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
* @ignore
|
|
* @return FieldConfig
|
|
* @throws Exception
|
|
*/
|
|
public function configureField()
|
|
{
|
|
if (!$this->config) {
|
|
$this->config = new FieldConfig();
|
|
|
|
if ($this->configureCallback) {
|
|
call_user_func($this->configureCallback, $this->config);
|
|
}
|
|
|
|
$this->setUiControlIfNeeded($this->config);
|
|
$this->checkType($this->config);
|
|
}
|
|
|
|
return $this->config;
|
|
}
|
|
|
|
/**
|
|
* Set whether setting is writable or not. For example to hide setting from the UI set it to false.
|
|
*
|
|
* @param bool $isWritable
|
|
*/
|
|
public function setIsWritableByCurrentUser($isWritable)
|
|
{
|
|
$this->hasWritePermission = (bool) $isWritable;
|
|
}
|
|
|
|
/**
|
|
* Returns `true` if this setting is writable for the current user, `false` if otherwise. In case it returns
|
|
* writable for the current user it will be visible in the Plugin settings UI.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function isWritableByCurrentUser()
|
|
{
|
|
return (bool) $this->hasWritePermission;
|
|
}
|
|
|
|
/**
|
|
* Saves (persists) the value for this setting in the database if a value has been actually set.
|
|
*/
|
|
public function save()
|
|
{
|
|
$this->storage->save();
|
|
}
|
|
|
|
/**
|
|
* Returns the previously persisted setting value. If no value was set, the default value
|
|
* is returned.
|
|
*
|
|
* @return mixed
|
|
*/
|
|
public function getValue()
|
|
{
|
|
return $this->storage->getValue($this->name, $this->defaultValue, $this->type);
|
|
}
|
|
|
|
/**
|
|
* Sets and persists this setting's value overwriting any existing value.
|
|
*
|
|
* Before a value is actually set it will be made sure the current user is allowed to change the value. The value
|
|
* will be first validated either via a system built-in validate method or via a set {@link FieldConfig::$validate}
|
|
* custom method. Afterwards the value will be transformed via a possibly specified {@link FieldConfig::$transform}
|
|
* method. Before storing the actual value, the value will be converted to the actually specified {@link $type}.
|
|
*
|
|
* @param mixed $value
|
|
* @throws \Exception If the current user is not allowed to change the value of this setting.
|
|
*/
|
|
public function setValue($value)
|
|
{
|
|
$this->checkHasEnoughWritePermission();
|
|
|
|
$config = $this->configureField();
|
|
|
|
$this->validateValue($value);
|
|
|
|
if ($config->transform && $config->transform instanceof \Closure) {
|
|
$value = call_user_func($config->transform, $value, $this);
|
|
}
|
|
|
|
if (isset($this->type) && !is_null($value)) {
|
|
settype($value, $this->type);
|
|
}
|
|
|
|
$this->storage->setValue($this->name, $value);
|
|
}
|
|
|
|
private function validateValue($value)
|
|
{
|
|
$config = $this->configureField();
|
|
|
|
if (!empty($config->validators)) {
|
|
BaseValidator::check($config->title, $value, $config->validators);
|
|
}
|
|
|
|
if ($config->validate && $config->validate instanceof \Closure) {
|
|
call_user_func($config->validate, $value, $this);
|
|
} elseif (is_array($config->availableValues)) {
|
|
if (is_bool($value) && $value) {
|
|
$value = '1';
|
|
} elseif (is_bool($value)) {
|
|
$value = '0';
|
|
}
|
|
|
|
// TODO move error message creation to a subclass, eg in MeasurableSettings we do not want to mention plugin name
|
|
$errorMsg = Piwik::translate('CoreAdminHome_PluginSettingsValueNotAllowed',
|
|
array(strip_tags($config->title), $this->pluginName));
|
|
|
|
if (is_array($value) && $this->type === FieldConfig::TYPE_ARRAY) {
|
|
foreach ($value as $val) {
|
|
if (!array_key_exists($val, $config->availableValues)) {
|
|
throw new \Exception($errorMsg);
|
|
}
|
|
}
|
|
} else {
|
|
if (!array_key_exists($value, $config->availableValues)) {
|
|
throw new \Exception($errorMsg);
|
|
}
|
|
}
|
|
} elseif ($this->type === FieldConfig::TYPE_INT || $this->type === FieldConfig::TYPE_FLOAT) {
|
|
|
|
if (!is_numeric($value)) {
|
|
$errorMsg = Piwik::translate('CoreAdminHome_PluginSettingsValueNotAllowed',
|
|
array(strip_tags($config->title), $this->pluginName));
|
|
throw new \Exception($errorMsg);
|
|
}
|
|
|
|
} elseif ($this->type === FieldConfig::TYPE_BOOL) {
|
|
|
|
if (!in_array($value, array(true, false, '0', '1', 0, 1), true)) {
|
|
$errorMsg = Piwik::translate('CoreAdminHome_PluginSettingsValueNotAllowed',
|
|
array(strip_tags($config->title), $this->pluginName));
|
|
throw new \Exception($errorMsg);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @throws \Exception
|
|
*/
|
|
private function checkHasEnoughWritePermission()
|
|
{
|
|
if (!$this->isWritableByCurrentUser()) {
|
|
$errorMsg = Piwik::translate('CoreAdminHome_PluginSettingChangeNotAllowed', array($this->name, $this->pluginName));
|
|
throw new \Exception($errorMsg);
|
|
}
|
|
}
|
|
|
|
private function setUiControlIfNeeded(FieldConfig $field)
|
|
{
|
|
if (!isset($field->uiControl)) {
|
|
$defaultControlTypes = array(
|
|
FieldConfig::TYPE_INT => FieldConfig::UI_CONTROL_TEXT,
|
|
FieldConfig::TYPE_FLOAT => FieldConfig::UI_CONTROL_TEXT,
|
|
FieldConfig::TYPE_STRING => FieldConfig::UI_CONTROL_TEXT,
|
|
FieldConfig::TYPE_BOOL => FieldConfig::UI_CONTROL_CHECKBOX,
|
|
FieldConfig::TYPE_ARRAY => FieldConfig::UI_CONTROL_MULTI_SELECT,
|
|
);
|
|
|
|
if (isset($defaultControlTypes[$this->type])) {
|
|
$field->uiControl = $defaultControlTypes[$this->type];
|
|
} else {
|
|
$field->uiControl = FieldConfig::UI_CONTROL_TEXT;
|
|
}
|
|
}
|
|
}
|
|
|
|
private function checkType(FieldConfig $field)
|
|
{
|
|
if ($field->uiControl === FieldConfig::UI_CONTROL_MULTI_SELECT &&
|
|
$this->type !== FieldConfig::TYPE_ARRAY) {
|
|
throw new Exception('Type must be an array when using a multi select');
|
|
}
|
|
|
|
if ($field->uiControl === FieldConfig::UI_CONTROL_MULTI_TUPLE &&
|
|
$this->type !== FieldConfig::TYPE_ARRAY) {
|
|
throw new Exception('Type must be an array when using a multi pair');
|
|
}
|
|
|
|
$types = array(
|
|
FieldConfig::TYPE_INT,
|
|
FieldConfig::TYPE_FLOAT,
|
|
FieldConfig::TYPE_STRING,
|
|
FieldConfig::TYPE_BOOL,
|
|
FieldConfig::TYPE_ARRAY
|
|
);
|
|
|
|
if (!in_array($this->type, $types)) {
|
|
throw new Exception('Type does not exist');
|
|
}
|
|
}
|
|
|
|
}
|