diff options
author | Thomas Steur <thomas.steur@gmail.com> | 2016-03-10 00:55:45 +0300 |
---|---|---|
committer | Thomas Steur <thomas.steur@gmail.com> | 2016-04-11 05:11:33 +0300 |
commit | b52ae4e7e488e0474d67c54578e1d6c1aa066bff (patch) | |
tree | f94b02f774cbc24faaa18f29ee1e19fef8b338af /core/Settings | |
parent | 6ba622a68a26792af8cc22131f488f7ff5189d2c (diff) |
refs #7983 let plugins add or remove fields to websites and better settings api
Diffstat (limited to 'core/Settings')
23 files changed, 1929 insertions, 795 deletions
diff --git a/core/Settings/FieldConfig.php b/core/Settings/FieldConfig.php new file mode 100644 index 0000000000..4c75fc1719 --- /dev/null +++ b/core/Settings/FieldConfig.php @@ -0,0 +1,208 @@ +<?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; + +/** + * Lets you configure a form field. + * + * @api + */ +class FieldConfig +{ + /** + * Shows a radio field + */ + const UI_CONTROL_RADIO = 'radio'; + + /** + * Shows a text field + */ + const UI_CONTROL_TEXT = 'text'; + + /** + * Shows a text area + */ + const UI_CONTROL_TEXTAREA = 'textarea'; + + /** + * Shows a checkbox + */ + const UI_CONTROL_CHECKBOX = 'checkbox'; + + /** + * Shows a password field + */ + const UI_CONTROL_PASSWORD = 'password'; + + /** + * Shows a select field where a user can select multiple values. The type "Array" is required for this ui control. + */ + const UI_CONTROL_MULTI_SELECT = 'multiselect'; + + /** + * Shows a select field + */ + const UI_CONTROL_SINGLE_SELECT = 'select'; + + /** + * Generates a hidden form field + */ + const UI_CONTROL_HIDDEN = 'hidden'; + + /** + * Expects an integer value + */ + const TYPE_INT = 'integer'; + + /** + * Expects a float value + */ + const TYPE_FLOAT = 'float'; + + /** + * Expects a string + */ + const TYPE_STRING = 'string'; + + /** + * Expects a boolean + */ + const TYPE_BOOL = 'boolean'; + + /** + * Expects an array containing multiple values + */ + const TYPE_ARRAY = 'array'; + + /** + * Describes what HTML element should be used to manipulate the setting through Piwik's UI. + * + * See {@link Piwik\Plugin\Settings} for a list of supported control types. + * + * @var string + */ + public $uiControl = null; + + /** + * Name-value mapping of HTML attributes that will be added HTML form control, eg, + * `array('size' => 3)`. Attributes will be escaped before outputting. + * + * @var array + */ + public $uiControlAttributes = array(); + + /** + * The list of all available values for this setting. If null, the setting can have any value. + * + * If supplied, this field should be an array mapping available values with their prettified + * display value. Eg, if set to `array('nb_visits' => 'Visits', 'nb_actions' => 'Actions')`, + * the UI will display **Visits** and **Actions**, and when the user selects one, Piwik will + * set the setting to **nb_visits** or **nb_actions** respectively. + * + * The setting value will be validated if this field is set. If the value is not one of the + * available values, an error will be triggered. + * + * _Note: If a custom validator is supplied (see {@link $validate}), the setting value will + * not be validated._ + * + * @var null|array + */ + public $availableValues = null; + + /** + * Text that will appear above this setting's section in the _Plugin Settings_ admin page. + * + * @var null|string + */ + public $introduction = null; + + /** + * Text that will appear directly underneath the setting title in the _Plugin Settings_ admin + * page. If set, should be a short description of the setting. + * + * @var null|string + */ + public $description = null; + + /** + * Text that will appear next to the setting's section in the _Plugin Settings_ admin page. If set, + * it should contain information about the setting that is more specific than a general description, + * such as the format of the setting value if it has a special format. + * + * Be sure to escape any user input as HTML can be used here. + * + * @var null|string + */ + public $inlineHelp = null; + + /** + * A closure that does some custom validation on the setting before the setting is persisted. + * + * The closure should take two arguments: the setting value and the {@link Setting} instance being + * validated. If the value is found to be invalid, the closure should throw an exception with + * a message that describes the error. + * + * **Example** + * + * $setting->validate = function ($value, Setting $setting) { + * if ($value > 60) { + * throw new \Exception('The time limit is not allowed to be greater than 60 minutes.'); + * } + * } + * + * @var null|\Closure + */ + public $validate = null; + + /** + * A closure that transforms the setting value. If supplied, this closure will be executed after + * the setting has been validated. + * + * _Note: If a transform is supplied, the setting's {@link $type} has no effect. This means the + * transformation function will be responsible for casting the setting value to the appropriate + * data type._ + * + * **Example** + * + * $setting->transform = function ($value, Setting $setting) { + * if ($value > 30) { + * $value = 30; + * } + * + * return (int) $value; + * } + * + * @var null|\Closure + */ + public $transform = null; + + /** + * This setting's display name, for example, `'Refresh Interval'`. + * + * Be sure to escape any user input as HTML can be used here. + * + * @var string + */ + public $title = ''; + + /** + * Here you can define conditions so that certain form fields will be only shown when a certain condition + * is true. This condition is supposed to be evaluated on the client side dynamically. This way you can hide + * for example some fields depending on another field. For example if SiteSearch is disabled, fields to enter + * site search keywords is not needed anymore and can be disabled. + * + * For example 'sitesearch', or 'sitesearch && !use_sitesearch_default' where 'sitesearch' and 'use_sitesearch_default' + * are both values of fields. + * + * @var string + */ + public $condition; + +} diff --git a/core/Settings/Manager.php b/core/Settings/Manager.php deleted file mode 100644 index e757e44d60..0000000000 --- a/core/Settings/Manager.php +++ /dev/null @@ -1,157 +0,0 @@ -<?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\Plugin\Manager as PluginManager; - -/** - * Settings manager. - * - */ -class Manager -{ - private static $settings = array(); - private static $numPluginsChecked = 0; - - /** - * Returns all available plugin settings, even settings for inactive plugins. A plugin has to specify a file named - * `Settings.php` containing a class named `Settings` that extends `Piwik\Plugin\Settings` in order to be - * considered as a plugin setting. Otherwise the settings for a plugin won't be available. - * - * @return \Piwik\Plugin\Settings[] An array containing array([pluginName] => [setting instance]). - */ - public static function getAllPluginSettings() - { - $numActivatedPlugins = PluginManager::getInstance()->getNumberOfActivatedPlugins(); - - if (static::$numPluginsChecked != $numActivatedPlugins) { - static::$numPluginsChecked = $numActivatedPlugins; - static::$settings = array(); - } - - if (empty(static::$settings)) { - $settings = PluginManager::getInstance()->findComponents('Settings', 'Piwik\\Plugin\\Settings'); - $byPluginName = array(); - - foreach ($settings as $setting) { - $byPluginName[$setting->getPluginName()] = $setting; - } - - static::$settings = $byPluginName; - } - - return static::$settings; - } - - private static function isActivatedPlugin($pluginName) - { - return PluginManager::getInstance()->isPluginActivated($pluginName); - } - - /** - * Removes all settings made for a specific plugin. Useful while uninstalling a plugin. - * - * @param string $pluginName - */ - public static function cleanupPluginSettings($pluginName) - { - $pluginManager = PluginManager::getInstance(); - - if (!$pluginManager->isPluginLoaded($pluginName)) { - return; - } - - $plugin = $pluginManager->loadPlugin($pluginName); - $settings = $plugin->findComponent('Settings', 'Piwik\\Plugin\\Settings'); - - if (!empty($settings)) { - $settings->removeAllPluginSettings(); - } - } - - /** - * Gets all plugins settings that have at least one settings a user is allowed to change. Only the settings for - * activated plugins are returned. - * - * @return \Piwik\Plugin\Settings[] An array containing array([pluginName] => [setting instance]). - */ - public static function getPluginSettingsForCurrentUser() - { - $settings = static::getAllPluginSettings(); - - $settingsForUser = array(); - foreach ($settings as $pluginName => $setting) { - if (!static::isActivatedPlugin($pluginName)) { - continue; - } - - $forUser = $setting->getSettingsForCurrentUser(); - if (!empty($forUser)) { - $settingsForUser[$pluginName] = $setting; - } - } - - return $settingsForUser; - } - - public static function hasSystemPluginSettingsForCurrentUser($pluginName) - { - $pluginNames = static::getPluginNamesHavingSystemSettings(); - - return in_array($pluginName, $pluginNames); - } - - /** - * Detects whether there are user settings for activated plugins available that the current user can change. - * - * @return bool - */ - public static function hasUserPluginsSettingsForCurrentUser() - { - $settings = static::getPluginSettingsForCurrentUser(); - - foreach ($settings as $setting) { - foreach ($setting->getSettingsForCurrentUser() as $set) { - if ($set instanceof UserSetting) { - return true; - } - } - } - - return false; - } - - public static function getPluginNamesHavingSystemSettings() - { - $settings = static::getPluginSettingsForCurrentUser(); - $plugins = array(); - - foreach ($settings as $pluginName => $setting) { - foreach ($setting->getSettingsForCurrentUser() as $set) { - if ($set instanceof SystemSetting) { - $plugins[] = $pluginName; - } - } - } - - return array_unique($plugins); - } - /** - * Detects whether there are system settings for activated plugins available that the current user can change. - * - * @return bool - */ - public static function hasSystemPluginsSettingsForCurrentUser() - { - $settings = static::getPluginNamesHavingSystemSettings(); - - return !empty($settings); - } -} diff --git a/core/Settings/Measurable/MeasurableProperty.php b/core/Settings/Measurable/MeasurableProperty.php new file mode 100644 index 0000000000..b49ee04698 --- /dev/null +++ b/core/Settings/Measurable/MeasurableProperty.php @@ -0,0 +1,89 @@ +<?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\Measurable; + +use Piwik\Container\StaticContainer; +use Piwik\Piwik; +use Piwik\Settings\Storage; +use Exception; + +/** + * Describes a Measurable property for a measurable type such as a website, a mobile app, .... + * + * The difference to {@link MeasurableSetting} is that these fields will be stored in the actual site table whereas + * MeasurableSetting will be stored in a site_settings table. For this reasons MeasurableProperty can be used only + * for some specific fields that already exist in site table such as "ecommerce", "sitesearch" etc. + * + * See {@link \Piwik\Settings\Setting}. + */ +class MeasurableProperty extends \Piwik\Settings\Setting +{ + /** + * @var int + */ + private $idSite = 0; + + private $allowedNames = array( + 'ecommerce', 'sitesearch', 'sitesearch_keyword_parameters', + 'sitesearch_category_parameters', + 'exclude_unknown_urls', 'excluded_ips', 'excluded_parameters', + 'excluded_user_agents', 'keep_url_fragment', 'urls' + ); + + /** + * Constructor. + * + * @param string $name The persisted name of the setting. + * @param mixed $defaultValue Default value for this setting if no value was specified. + * @param string $type Eg an array, int, ... see TYPE_* constants + * @param string $pluginName The name of the plugin the setting belongs to. + * @param int $idSite The idSite this property belongs to. + * @throws Exception + */ + public function __construct($name, $defaultValue, $type, $pluginName, $idSite) + { + if (!in_array($name, $this->allowedNames)) { + throw new Exception(sprintf('Name "%s" is not allowed to be used with a MeasurableProperty, use a MeasurableSetting instead.', $name)); + } + + parent::__construct($name, $defaultValue, $type, $pluginName); + + $this->idSite = $idSite; + + $storageFactory = StaticContainer::get('Piwik\Settings\Storage\Factory'); + $this->storage = $storageFactory->getSitesTable($idSite); + } + + /** + * Returns `true` if this setting can be displayed for the current user, `false` if otherwise. + * + * @return bool + */ + public function isWritableByCurrentUser() + { + if (isset($this->hasWritePermission)) { + return $this->hasWritePermission; + } + + // performance improvement, do not detect this in __construct otherwise likely rather "big" query to DB. + if ($this->hasSiteBeenCreated()) { + $this->hasWritePermission = Piwik::isUserHasAdminAccess($this->idSite); + } else { + $this->hasWritePermission = Piwik::hasUserSuperUserAccess(); + } + + return $this->hasWritePermission; + } + + private function hasSiteBeenCreated() + { + return !empty($this->idSite); + } +} diff --git a/core/Settings/Measurable/MeasurableSetting.php b/core/Settings/Measurable/MeasurableSetting.php new file mode 100644 index 0000000000..b03289e44e --- /dev/null +++ b/core/Settings/Measurable/MeasurableSetting.php @@ -0,0 +1,72 @@ +<?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\Measurable; + +use Piwik\Container\StaticContainer; +use Piwik\Piwik; +use Piwik\Settings\Storage; + +/** + * Describes a Measurable setting for a measurable type such as a website, a mobile app, ... + * + * See {@link \Piwik\Settings\Setting}. + */ +class MeasurableSetting extends \Piwik\Settings\Setting +{ + /** + * @var int + */ + private $idSite = 0; + + /** + * Constructor. + * + * @param string $name The persisted name of the setting. + * @param mixed $defaultValue Default value for this setting if no value was specified. + * @param string $type Eg an array, int, ... see TYPE_* constants + * @param string $pluginName The name of the plugin the setting belongs to + * @param int $idSite The idSite this setting belongs to. + */ + public function __construct($name, $defaultValue, $type, $pluginName, $idSite) + { + parent::__construct($name, $defaultValue, $type, $pluginName); + + $this->idSite = $idSite; + + $storageFactory = StaticContainer::get('Piwik\Settings\Storage\Factory'); + $this->storage = $storageFactory->getMeasurableSettingsStorage($idSite, $this->pluginName); + } + + /** + * Returns `true` if this setting can be displayed for the current user, `false` if otherwise. + * + * @return bool + */ + public function isWritableByCurrentUser() + { + if (isset($this->hasWritePermission)) { + return $this->hasWritePermission; + } + + // performance improvement, do not detect this in __construct otherwise likely rather "big" query to DB. + if ($this->hasSiteBeenCreated()) { + $this->hasWritePermission = Piwik::isUserHasAdminAccess($this->idSite); + } else { + $this->hasWritePermission = Piwik::hasUserSuperUserAccess(); + } + + return $this->hasWritePermission; + } + + private function hasSiteBeenCreated() + { + return !empty($this->idSite); + } +} diff --git a/core/Settings/Measurable/MeasurableSettings.php b/core/Settings/Measurable/MeasurableSettings.php new file mode 100644 index 0000000000..3d062f9943 --- /dev/null +++ b/core/Settings/Measurable/MeasurableSettings.php @@ -0,0 +1,141 @@ +<?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\Measurable; + +use Piwik\Db; +use Piwik\Piwik; +use Piwik\Settings\Settings; +use Piwik\Settings\Storage; +use Piwik\Site; +use Exception; + +/** + * Base class of all measurable settings providers. Plugins that define their own configuration settings + * can extend this class to easily make their measurable settings available to Piwik users. + * + * Descendants of this class should implement the {@link init()} method and call the + * {@link makeSetting()} method for each of the measurable's settings. + * + * For an example, see the {@link Piwik\Plugins\ExampleSettingsPlugin\MeasurableSettings} plugin. + * + * $settingsProvider = new Piwik\Plugin\SettingsProvider(); // get this instance via dependency injection + * $measurableSettings = $settingProvider->getMeasurableSettings($yourPluginName, $idsite, $idType = null); + * $measurableSettings->yourSetting->getValue(); + * + * @api + */ +abstract class MeasurableSettings extends Settings +{ + /** + * @var int + */ + protected $idSite; + + /** + * @var string + */ + protected $idMeasurableType; + + /** + * Constructor. + * @param int $idSite If creating settings for a new site that is not created yet, use idSite = 0 + * @param string|null $idMeasurableType If null, idType will be detected from idSite + * @throws Exception + */ + public function __construct($idSite, $idMeasurableType = null) + { + parent::__construct(); + + $this->idSite = (int) $idSite; + + if (!empty($idMeasurableType)) { + $this->idMeasurableType = $idMeasurableType; + } elseif (!empty($idSite)) { + $this->idMeasurableType = Site::getTypeFor($idSite); + } else { + throw new Exception('No idType specified for ' . get_class($this)); + } + + $this->init(); + } + + protected function hasMeasurableType($typeId) + { + return $typeId === $this->idMeasurableType; + } + + /** + * Creates a new measurable setting. + * + * Settings will be displayed in the UI depending on the order of `makeSetting` calls. This means you can define + * the order of the displayed settings by calling makeSetting first for more important settings. + * + * @param string $name The name of the setting that shall be created + * @param mixed $defaultValue The default value for this setting. Note the value will not be converted to the + * specified type. + * @param string $type The PHP internal type the value of this setting should have. + * Use one of FieldConfig::TYPE_* constancts + * @param \Closure $fieldConfigCallback A callback method to configure the field that shall be displayed in the + * UI to define the value for this setting + * @return MeasurableSetting Returns an instance of the created measurable setting. + * @throws Exception + */ + protected function makeSetting($name, $defaultValue, $type, $fieldConfigCallback) + { + $setting = new MeasurableSetting($name, $defaultValue, $type, $this->pluginName, $this->idSite); + $setting->setConfigureCallback($fieldConfigCallback); + + $this->addSetting($setting); + + return $setting; + } + + /** + * @internal + * @param $name + * @param $defaultValue + * @param $type + * @param $configureCallback + * @return MeasurableProperty + * @throws Exception + */ + protected function makeProperty($name, $defaultValue, $type, $configureCallback) + { + $setting = new MeasurableProperty($name, $defaultValue, $type, $this->pluginName, $this->idSite); + $setting->setConfigureCallback($configureCallback); + + $this->addSetting($setting); + + return $setting; + } + + /** + * Saves (persists) the current measurable setting values in the database. + * + * Will trigger an event to notify plugins that a value has been changed. + */ + public function save() + { + parent::save(); + + /** + * Triggered after a plugin settings have been updated. + * + * **Example** + * + * Piwik::addAction('MeasurableSettings.updated', function (MeasurableSettings $settings) { + * $value = $settings->someSetting->getValue(); + * // Do something with the new setting value + * }); + * + * @param Settings $settings The plugin settings object. + */ + Piwik::postEvent('MeasurableSettings.updated', array($this, $this->idSite)); + } +} diff --git a/core/Settings/SystemSetting.php b/core/Settings/Plugin/SystemSetting.php index 68e780a3e8..3c10a4c5f4 100644 --- a/core/Settings/SystemSetting.php +++ b/core/Settings/Plugin/SystemSetting.php @@ -7,48 +7,38 @@ * */ -namespace Piwik\Settings; +namespace Piwik\Settings\Plugin; use Piwik\Config; +use Piwik\Container\StaticContainer; use Piwik\Piwik; +use Piwik\Settings\Setting; +use Piwik\Settings\Storage; /** - * Describes a system wide setting. Only the Super User can change this type of setting and + * Describes a system wide setting. Only the Super User can change this type of setting by d efault and * the value of this setting will affect all users. * - * See {@link \Piwik\Plugin\Settings}. - * + * See {@link \Piwik\Settings\Setting}. * * @api */ class SystemSetting extends Setting { /** - * By default the value of the system setting is only readable by SuperUsers but someone the value should be - * readable by everyone. - * - * @var bool - * @since 2.4.0 - */ - public $readableByCurrentUser = false; - - /** - * @var bool - */ - private $writableByCurrentUser = false; - - /** * Constructor. * - * @param string $name The persisted name of the setting. - * @param string $title The display name of the setting. + * @param string $name The setting's persisted name. + * @param mixed $defaultValue Default value for this setting if no value was specified. + * @param string $type Eg an array, int, ... see TYPE_* constants + * @param string $pluginName The name of the plugin the system setting belongs to. */ - public function __construct($name, $title) + public function __construct($name, $defaultValue, $type, $pluginName) { - parent::__construct($name, $title); + parent::__construct($name, $defaultValue, $type, $pluginName); - $this->writableByCurrentUser = Piwik::hasUserSuperUserAccess(); - $this->readableByCurrentUser = $this->writableByCurrentUser; + $factory = StaticContainer::get('Piwik\Settings\Storage\Factory'); + $this->storage = $factory->getPluginStorage($this->pluginName, $userLogin = ''); } /** @@ -63,39 +53,19 @@ class SystemSetting extends Setting return false; } - return $this->writableByCurrentUser; - } + if (isset($this->hasWritePermission)) { + return $this->hasWritePermission; + } - /** - * 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->writableByCurrentUser = (bool) $isWritable; - } + // performance improvement, do not detect this in __construct otherwise likely rather "big" query to DB. + $this->hasWritePermission = Piwik::hasUserSuperUserAccess(); - /** - * Returns `true` if this setting can be displayed for the current user, `false` if otherwise. - * - * @return bool - */ - public function isReadableByCurrentUser() - { - return $this->readableByCurrentUser; + return $this->hasWritePermission; } /** - * Returns the display order. System settings are displayed before user settings. - * - * @return int + * @inheritdoc */ - public function getOrder() - { - return 30; - } - public function getValue() { $defaultValue = parent::getValue(); // we access value first to make sure permissions are checked diff --git a/core/Settings/Plugin/SystemSettings.php b/core/Settings/Plugin/SystemSettings.php new file mode 100644 index 0000000000..1b2fc413ff --- /dev/null +++ b/core/Settings/Plugin/SystemSettings.php @@ -0,0 +1,90 @@ +<?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\Plugin; + +use Piwik\Db; +use Piwik\Piwik; +use Piwik\Settings\Settings; +use Piwik\Settings\Storage; + +/** + * Base class of all system settings providers. Plugins that define their own configuration settings + * can extend this class to easily make their system settings available to Piwik users. + * + * Descendants of this class should implement the {@link init()} method and call the + * {@link makeSetting()} method to create a system setting for this plugin. + * + * For an example, see {@link Piwik\Plugins\ExampleSettingsPlugin\SystemSettings}. + * + * $systemSettings = new Piwik\Plugins\ExampleSettingsPlugin\SystemSettings(); // get instance via dependency injection + * $systemSettings->yourSetting->getValue(); + * + * @api + */ +abstract class SystemSettings extends Settings +{ + /** + * Constructor. + */ + public function __construct() + { + parent::__construct(); + + $this->init(); + } + + /** + * Creates a new system setting. + * + * Settings will be displayed in the UI depending on the order of `makeSetting` calls. This means you can define + * the order of the displayed settings by calling makeSetting first for more important settings. + * + * @param string $name The name of the setting that shall be created + * @param mixed $defaultValue The default value for this setting. Note the value will not be converted to the + * specified type. + * @param string $type The PHP internal type the value of this setting should have. + * Use one of FieldConfig::TYPE_* constancts + * @param \Closure $fieldConfigCallback A callback method to configure the field that shall be displayed in the + * UI to define the value for this setting + * @return SystemSetting Returns an instance of the created measurable setting. + */ + protected function makeSetting($name, $defaultValue, $type, $fieldConfigCallback) + { + $setting = new SystemSetting($name, $defaultValue, $type, $this->pluginName); + $setting->setConfigureCallback($fieldConfigCallback); + $this->addSetting($setting); + return $setting; + } + + /** + * Saves (persists) the current setting values in the database. + * + * Will trigger an event to notify plugins that a value has been changed. + */ + public function save() + { + parent::save(); + + /** + * Triggered after system settings have been updated. + * + * **Example** + * + * Piwik::addAction('SystemSettings.updated', function (SystemSettings $settings) { + * if ($settings->getPluginName() === 'PluginName') { + * $value = $settings->someSetting->getValue(); + * // Do something with the new setting value + * } + * }); + * + * @param Settings $settings The plugin settings object. + */ + Piwik::postEvent('SystemSettings.updated', array($this)); + } +} diff --git a/core/Settings/Plugin/UserSetting.php b/core/Settings/Plugin/UserSetting.php new file mode 100644 index 0000000000..0044eef78f --- /dev/null +++ b/core/Settings/Plugin/UserSetting.php @@ -0,0 +1,73 @@ +<?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\Plugin; + +use Piwik\Container\StaticContainer; +use Piwik\Db; +use Piwik\Piwik; +use Exception; +use Piwik\Settings\Setting; +use Piwik\Settings\Storage; + +/** + * Describes a per user setting. Each user will be able to change this setting for themselves, + * but not for other users. + * + * See {@link \Piwik\Settings\Setting}. + */ +class UserSetting extends Setting +{ + /** + * @var null|string + */ + private $userLogin = null; + + /** + * Constructor. + * + * @param string $name The setting's persisted name. + * @param mixed $defaultValue Default value for this setting if no value was specified. + * @param string $type Eg an array, int, ... see TYPE_* constants + * @param string $pluginName The name of the plugin the setting belongs to + * @param string $userLogin The name of the user the value should be set or get for + * @throws Exception + */ + public function __construct($name, $defaultValue, $type, $pluginName, $userLogin) + { + parent::__construct($name, $defaultValue, $type, $pluginName); + + if (empty($userLogin)) { + throw new Exception('No userLogin given to create setting ' . $name); + } + + $this->userLogin = $userLogin; + + $factory = StaticContainer::get('Piwik\Settings\Storage\Factory'); + $this->storage = $factory->getPluginStorage($this->pluginName, $this->userLogin); + } + + /** + * Returns `true` if this setting can be displayed for the current user, `false` if otherwise. + * + * @return bool + */ + public function isWritableByCurrentUser() + { + if (isset($this->hasWritePermission)) { + return $this->hasWritePermission; + } + + // performance improvement, do not detect this in __construct otherwise likely rather "big" query to DB. + $this->hasWritePermission = Piwik::isUserHasSomeViewAccess(); + + return $this->hasWritePermission; + } + +} diff --git a/core/Settings/Plugin/UserSettings.php b/core/Settings/Plugin/UserSettings.php new file mode 100644 index 0000000000..cda8438d73 --- /dev/null +++ b/core/Settings/Plugin/UserSettings.php @@ -0,0 +1,93 @@ +<?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\Plugin; + +use Piwik\Db; +use Piwik\Piwik; +use Piwik\Settings\Settings; +use Piwik\Settings\Storage; + +/** + * Base class of all plugin settings providers. Plugins that define their own configuration settings + * can extend this class to easily make their settings available to Piwik users. + * + * Descendants of this class should implement the {@link init()} method and call the + * {@link addSetting()} method for each of the plugin's settings. + * + * For an example, see {@link Piwik\Plugins\ExampleSettingsPlugin\UserSettings}. + * + * $userSettings = new Piwik\Plugins\ExampleSettingsPlugin\UserSettings(); // get instance via dependency injection + * $userSettings->yourSetting->getValue(); + * + * @api + */ +abstract class UserSettings extends Settings +{ + /** + * Constructor. + */ + public function __construct() + { + parent::__construct(); + + $this->init(); + } + + /** + * Creates a new user setting. + * + * Settings will be displayed in the UI depending on the order of `makeSetting` calls. This means you can define + * the order of the displayed settings by calling makeSetting first for more important settings. + * + * @param string $name The name of the setting that shall be created + * @param mixed $defaultValue The default value for this setting. Note the value will not be converted to the + * specified type. + * @param string $type The PHP internal type the value of this setting should have. + * Use one of FieldConfig::TYPE_* constancts + * @param \Closure $fieldConfigCallback A callback method to configure the field that shall be displayed in the + * UI to define the value for this setting + * @return UserSetting Returns an instance of the created measurable setting. + */ + protected function makeSetting($name, $defaultValue, $type, $configureCallback) + { + $userLogin = Piwik::getCurrentUserLogin(); + + $setting = new UserSetting($name, $defaultValue, $type, $this->pluginName, $userLogin); + $setting->setConfigureCallback($configureCallback); + + $this->addSetting($setting); + return $setting; + } + + /** + * Saves (persists) the current setting values in the database. + * + * Will trigger an event to notify plugins that a value has been changed. + */ + public function save() + { + parent::save(); + + /** + * Triggered after user settings have been updated. + * + * **Example** + * + * Piwik::addAction('UserSettings.updated', function (UserSettings $settings) { + * if ($settings->getPluginName() === 'PluginName') { + * $value = $settings->someSetting->getValue(); + * // Do something with the new setting value + * } + * }); + * + * @param Settings $settings The plugin settings object. + */ + Piwik::postEvent('UserSettings.updated', array($this)); + } +} diff --git a/core/Settings/Setting.php b/core/Settings/Setting.php index bf8947b196..efd99c3802 100644 --- a/core/Settings/Setting.php +++ b/core/Settings/Setting.php @@ -10,168 +10,84 @@ namespace Piwik\Settings; use Piwik\Piwik; -use Piwik\SettingsServer; +use Piwik\Settings\Storage\Storage; +use Exception; /** * Base setting type class. * * @api */ -abstract class Setting +class Setting { - /** - * Describes the setting's PHP data type. When saved, setting values will always be casted to this - * type. - * - * See {@link Piwik\Plugin\Settings} for a list of supported data types. - * - * @var string - */ - public $type = null; /** - * Describes what HTML element should be used to manipulate the setting through Piwik's UI. - * - * See {@link Piwik\Plugin\Settings} for a list of supported control types. - * + * The name of the setting * @var string */ - public $uiControlType = null; - - /** - * Name-value mapping of HTML attributes that will be added HTML form control, eg, - * `array('size' => 3)`. Attributes will be escaped before outputting. - * - * @var array - */ - public $uiControlAttributes = array(); - - /** - * The list of all available values for this setting. If null, the setting can have any value. - * - * If supplied, this field should be an array mapping available values with their prettified - * display value. Eg, if set to `array('nb_visits' => 'Visits', 'nb_actions' => 'Actions')`, - * the UI will display **Visits** and **Actions**, and when the user selects one, Piwik will - * set the setting to **nb_visits** or **nb_actions** respectively. - * - * The setting value will be validated if this field is set. If the value is not one of the - * available values, an error will be triggered. - * - * _Note: If a custom validator is supplied (see {@link $validate}), the setting value will - * not be validated._ - * - * @var null|array - */ - public $availableValues = null; + protected $name; /** - * Text that will appear above this setting's section in the _Plugin Settings_ admin page. - * - * @var null|string + * Null while not initialized, bool otherwise. + * @var null|bool */ - public $introduction = null; + protected $hasWritePermission = null; /** - * Text that will appear directly underneath the setting title in the _Plugin Settings_ admin - * page. If set, should be a short description of the setting. - * - * @var null|string + * @var Storage */ - public $description = null; + protected $storage; /** - * Text that will appear next to the setting's section in the _Plugin Settings_ admin page. If set, - * it should contain information about the setting that is more specific than a general description, - * such as the format of the setting value if it has a special format. - * - * @var null|string + * @var string */ - public $inlineHelp = null; + protected $pluginName; /** - * A closure that does some custom validation on the setting before the setting is persisted. - * - * The closure should take two arguments: the setting value and the {@link Setting} instance being - * validated. If the value is found to be invalid, the closure should throw an exception with - * a message that describes the error. - * - * **Example** - * - * $setting->validate = function ($value, Setting $setting) { - * if ($value > 60) { - * throw new \Exception('The time limit is not allowed to be greater than 60 minutes.'); - * } - * } - * - * @var null|\Closure + * @var FieldConfig */ - public $validate = null; + protected $config; /** - * A closure that transforms the setting value. If supplied, this closure will be executed after - * the setting has been validated. - * - * _Note: If a transform is supplied, the setting's {@link $type} has no effect. This means the - * transformation function will be responsible for casting the setting value to the appropriate - * data type._ - * - * **Example** - * - * $setting->transform = function ($value, Setting $setting) { - * if ($value > 30) { - * $value = 30; - * } - * - * return (int) $value; - * } - * - * @var null|\Closure + * @var \Closure|null */ - public $transform = null; + protected $configureCallback; /** - * Default value of this setting. - * - * The default value is not casted to the appropriate data type. This means _**you**_ have to make - * sure the value is of the correct type. - * * @var mixed */ - public $defaultValue = null; + protected $defaultValue; /** - * This setting's display name, for example, `'Refresh Interval'`. - * * @var string */ - public $title = ''; - - protected $key; - protected $name; - - /** - * @var StorageInterface - */ - private $storage; - protected $pluginName; + protected $type; /** * Constructor. * * @param string $name The setting's persisted name. Only alphanumeric characters are allowed, eg, * `'refreshInterval'`. - * @param string $title The setting's display name, eg, `'Refresh Interval'`. + * @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, $title) + public function __construct($name, $defaultValue, $type, $pluginName) { - $this->key = $name; - $this->name = $name; - $this->title = $title; + 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); } /** - * Returns the setting's persisted name, eg, `'refreshInterval'`. - * + * Get the name of the setting. * @return string */ public function getName() @@ -180,32 +96,47 @@ abstract class Setting } /** - * 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 + * Get the PHP type of the setting. + * @return string */ - public function isWritableByCurrentUser() + public function getType() { - return false; + return $this->type; } /** - * Returns `true` if this setting can be displayed for the current user, `false` if otherwise. - * - * @return bool + * @internal + * @ignore + * @param $callback */ - public function isReadableByCurrentUser() + public function setConfigureCallback($callback) { - return false; + $this->configureCallback = $callback; + $this->config = null; } /** - * Sets the object used to persist settings. - * - * @param StorageInterface $storage + * @return mixed + */ + public function getDefaultValue() + { + return $this->defaultValue; + } + + /** + * Sets/overwrites the current default value + * @param string $defaultValue */ - public function setStorage(StorageInterface $storage) + public function setDefaultValue($defaultValue) + { + $this->defaultValue = $defaultValue; + } + + /** + * @internal + * @param Storage $storage + */ + public function setStorage(Storage $storage) { $this->storage = $storage; } @@ -213,35 +144,52 @@ abstract class Setting /** * @internal * @ignore - * @return StorageInterface + * @return FieldConfig + * @throws Exception */ - public function getStorage() + public function configureField() { - return $this->storage; + 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; } /** - * Sets th name of the plugin the setting belongs to + * Set whether setting is writable or not. For example to hide setting from the UI set it to false. * - * @param string $pluginName + * @param bool $isWritable */ - public function setPluginName($pluginName) + public function setIsWritableByCurrentUser($isWritable) { - $this->pluginName = $pluginName; + $this->hasWritePermission = (bool) $isWritable; } /** - * Returns the previously persisted setting value. If no value was set, the default value - * is returned. + * 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 mixed - * @throws \Exception If the current user is not allowed to change the value of this setting. + * @return bool */ - public function getValue() + public function isWritableByCurrentUser() { - $this->checkHasEnoughReadPermission(); + return (bool) $this->hasWritePermission; + } - return $this->storage->getValue($this); + /** + * Saves (persists) the value for this setting in the database if a value has been actually set. + */ + public function save() + { + $this->storage->save(); } /** @@ -249,40 +197,85 @@ abstract class Setting * is returned. * * @return mixed - * @throws \Exception If the current user is not allowed to change the value of this setting. */ - public function removeValue() + public function getValue() { - $this->checkHasEnoughWritePermission(); - - return $this->storage->deleteValue($this); + 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 ($this->transform && $this->transform instanceof \Closure) { - $value = call_user_func($this->transform, $value, $this); - } elseif (isset($this->type)) { + if ($config->transform && $config->transform instanceof \Closure) { + $value = call_user_func($config->transform, $value, $this); + } + + if (isset($this->type)) { settype($value, $this->type); } - return $this->storage->setValue($this, $value); + $this->storage->setValue($this->name, $value); } private function validateValue($value) { - $this->checkHasEnoughWritePermission(); - - if ($this->validate && $this->validate instanceof \Closure) { - call_user_func($this->validate, $value, $this); + $config = $this->configureField(); + + 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); + } } } @@ -291,50 +284,49 @@ abstract class Setting */ private function checkHasEnoughWritePermission() { - // When the request is a Tracker request, allow plugins to write settings - if (SettingsServer::isTrackerApiRequest()) { - return; - } - if (!$this->isWritableByCurrentUser()) { - $errorMsg = Piwik::translate('CoreAdminHome_PluginSettingChangeNotAllowed', array($this->getName(), $this->pluginName)); + $errorMsg = Piwik::translate('CoreAdminHome_PluginSettingChangeNotAllowed', array($this->name, $this->pluginName)); throw new \Exception($errorMsg); } } - /** - * @throws \Exception - */ - private function checkHasEnoughReadPermission() + private function setUiControlIfNeeded(FieldConfig $field) { - // When the request is a Tracker request, allow plugins to read settings - if (SettingsServer::isTrackerApiRequest()) { - return; - } - - if (!$this->isReadableByCurrentUser()) { - $errorMsg = Piwik::translate('CoreAdminHome_PluginSettingReadNotAllowed', array($this->getName(), $this->pluginName)); - throw new \Exception($errorMsg); + 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; + } } } - /** - * Returns the unique string key used to store this setting. - * - * @return string - */ - public function getKey() + private function checkType(FieldConfig $field) { - return $this->key; - } + 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'); + } - /** - * Returns the display order. The lower the return value, the earlier the setting will be displayed. - * - * @return int - */ - public function getOrder() - { - return 100; + $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'); + } } + } diff --git a/core/Settings/Settings.php b/core/Settings/Settings.php new file mode 100644 index 0000000000..6c076f8cac --- /dev/null +++ b/core/Settings/Settings.php @@ -0,0 +1,107 @@ +<?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; + +/** + * Base class of all settings providers. + * + * @api + */ +abstract class Settings +{ + /** + * An array containing all available settings: Array ( [setting-name] => [setting] ) + * + * @var Setting[] + */ + private $settings = array(); + + protected $pluginName; + + public function __construct() + { + if (!isset($this->pluginName)) { + $classname = get_class($this); + $parts = explode('\\', $classname); + + if (count($parts) >= 3) { + $this->pluginName = $parts[2]; + } else { + throw new \Exception(sprintf('Plugin Settings must have a plugin name specified in %s, could not detect plugin name', $classname)); + } + } + } + + /** + * @ignore + */ + public function getPluginName() + { + return $this->pluginName; + } + + /** + * @ignore + * @return Setting + */ + public function getSetting($name) + { + if (array_key_exists($name, $this->settings)) { + return $this->settings[$name]; + } + } + + /** + * Implemented by descendants. This method should define plugin settings (via the + * {@link addSetting()}) method and set the introduction text (via the + * {@link setIntroduction()}). + */ + abstract protected function init(); + + /** + * Returns the settings that can be displayed for the current user. + * + * @return Setting[] + */ + public function getSettingsWritableByCurrentUser() + { + return array_filter($this->settings, function (Setting $setting) { + return $setting->isWritableByCurrentUser(); + }); + } + + /** + * Makes a new plugin setting available. + * + * @param Setting $setting + * @throws \Exception If there is a setting with the same name that already exists. + * If the name contains non-alphanumeric characters. + */ + protected function addSetting(Setting $setting) + { + $name = $setting->getName(); + + if (isset($this->settings[$name])) { + throw new \Exception(sprintf('A setting with name "%s" does already exist for plugin "%s"', $name, $this->pluginName)); + } + + $this->settings[$name] = $setting; + } + + /** + * Saves (persists) the current setting values in the database. + */ + public function save() + { + foreach ($this->settings as $setting) { + $setting->save(); + } + } + +} diff --git a/core/Settings/Storage.php b/core/Settings/Storage.php deleted file mode 100644 index 131c01b111..0000000000 --- a/core/Settings/Storage.php +++ /dev/null @@ -1,148 +0,0 @@ -<?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\Option; - -/** - * Base setting type class. - * - * @api - */ -class Storage implements StorageInterface -{ - - /** - * Array containing all plugin settings values: Array( [setting-key] => [setting-value] ). - * - * @var array - */ - protected $settingsValues = array(); - - // for lazy loading of setting values - private $settingValuesLoaded = false; - - private $pluginName; - - public function __construct($pluginName) - { - $this->pluginName = $pluginName; - } - - /** - * Saves (persists) the current setting values in the database. - */ - public function save() - { - $this->loadSettingsIfNotDoneYet(); - - Option::set($this->getOptionKey(), serialize($this->settingsValues)); - } - - /** - * Removes all settings for this plugin from the database. Useful when uninstalling - * a plugin. - */ - public function deleteAllValues() - { - $this->deleteSettingsFromStorage(); - - $this->settingsValues = array(); - $this->settingValuesLoaded = false; - } - - protected function deleteSettingsFromStorage() - { - Option::delete($this->getOptionKey()); - } - - /** - * Returns the current value for a setting. If no value is stored, the default value - * is be returned. - * - * @param Setting $setting - * @return mixed - * @throws \Exception If the setting does not exist or if the current user is not allowed to change the value - * of this setting. - */ - public function getValue(Setting $setting) - { - $this->loadSettingsIfNotDoneYet(); - - if (array_key_exists($setting->getKey(), $this->settingsValues)) { - return $this->settingsValues[$setting->getKey()]; - } - - return $setting->defaultValue; - } - - /** - * Sets (overwrites) the value of a setting in memory. To persist the change, {@link save()} must be - * called afterwards, otherwise the change has no effect. - * - * Before the setting is changed, the {@link Piwik\Settings\Setting::$validate} and - * {@link Piwik\Settings\Setting::$transform} closures will be invoked (if defined). If there is no validation - * filter, the setting value will be casted to the appropriate data type. - * - * @param Setting $setting - * @param string $value - * @throws \Exception If the setting does not exist or if the current user is not allowed to change the value - * of this setting. - */ - public function setValue(Setting $setting, $value) - { - $this->loadSettingsIfNotDoneYet(); - - $this->settingsValues[$setting->getKey()] = $value; - } - - /** - * Unsets a setting value in memory. To persist the change, {@link save()} must be - * called afterwards, otherwise the change has no effect. - * - * @param Setting $setting - */ - public function deleteValue(Setting $setting) - { - $this->loadSettingsIfNotDoneYet(); - - $key = $setting->getKey(); - - if (array_key_exists($key, $this->settingsValues)) { - unset($this->settingsValues[$key]); - } - } - - public function getOptionKey() - { - return 'Plugin_' . $this->pluginName . '_Settings'; - } - - private function loadSettingsIfNotDoneYet() - { - if ($this->settingValuesLoaded) { - return; - } - - $this->settingValuesLoaded = true; - $this->settingsValues = $this->loadSettings(); - } - - protected function loadSettings() - { - $values = Option::get($this->getOptionKey()); - - if (!empty($values)) { - return unserialize($values); - } - - return array(); - } -} diff --git a/core/Settings/Storage/Backend/BackendInterface.php b/core/Settings/Storage/Backend/BackendInterface.php new file mode 100644 index 0000000000..624493460e --- /dev/null +++ b/core/Settings/Storage/Backend/BackendInterface.php @@ -0,0 +1,47 @@ +<?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\Storage\Backend; + +/** + * Interface for a storage backend. Any new storage backend must implement this interface. + */ +interface BackendInterface +{ + + /** + * Get an id that identifies the current storage. Eg `Plugin_$pluginName_Settings` could be a storage id + * for plugin settings. It's kind of like a cache key and the value will be actually used for this by a cache + * decorator. + * + * @return string + */ + public function getStorageId(); + + /** + * Saves (persists) the current setting values in the database. Always all values that belong to a group of + * settings or backend needs to be passed. Usually existing values will be deleted and new values will be saved + * @param array $values An array of key value pairs where $settingName => $settingValue. + * Eg array('settingName1' > 'settingValue1') + */ + public function save($values); + + /** + * Deletes all saved settings. + * @return void + */ + public function delete(); + + /** + * Loads previously saved setting values and returns them (if some were saved) + * + * @return array An array of key value pairs where $settingName => $settingValue. + * Eg array('settingName1' > 'settingValue1') + */ + public function load(); +} diff --git a/core/Settings/Storage/Backend/Cache.php b/core/Settings/Storage/Backend/Cache.php new file mode 100644 index 0000000000..f3150d77ee --- /dev/null +++ b/core/Settings/Storage/Backend/Cache.php @@ -0,0 +1,78 @@ +<?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\Storage\Backend; + +use Piwik\Settings\Storage; +use Piwik\Tracker; +use Piwik\Cache as PiwikCache; + +/** + * Loads settings from tracker cache instead of database. If not yet present in tracker cache will cache it. + * + * Can be used as a decorator in combination with any other storage backend. + */ +class Cache implements BackendInterface +{ + /** + * @var BackendInterface + */ + private $backend; + + public function __construct(BackendInterface $backend) + { + $this->backend = $backend; + } + + /** + * Saves (persists) the current setting values in the database. + */ + public function save($values) + { + $this->backend->save($values); + self::clearCache(); + } + + public function getStorageId() + { + return $this->backend->getStorageId(); + } + + public function delete() + { + $this->backend->delete(); + self::clearCache(); + } + + public function load() + { + $cacheId = $this->getStorageId(); + $cache = self::buildCache(); + + if ($cache->contains($cacheId)) { + return $cache->fetch($cacheId); + } + + $settings = $this->backend->load(); + $cache->save($cacheId, $settings); + + return $settings; + } + + public static function clearCache() + { + Tracker\Cache::deleteTrackerCache(); + self::buildCache()->flushAll(); + } + + public static function buildCache() + { + return PiwikCache::getEagerCache(); + } +} diff --git a/core/Settings/Storage/Backend/MeasurableSettingsTable.php b/core/Settings/Storage/Backend/MeasurableSettingsTable.php new file mode 100644 index 0000000000..22452288c8 --- /dev/null +++ b/core/Settings/Storage/Backend/MeasurableSettingsTable.php @@ -0,0 +1,167 @@ +<?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\Storage\Backend; + +use Piwik\Common; +use Piwik\Db; +use Exception; + +/** + * Measurable settings backend. Stores all settings in a "site_setting" database table. + * + * If a value that needs to be stored is an array, will insert a new row for each value of this array. + */ +class MeasurableSettingsTable implements BackendInterface +{ + /** + * @var int + */ + private $idSite; + + /** + * @var string + */ + private $pluginName; + + /** + * @var Db\AdapterInterface + */ + private $db; + + public function __construct($idSite, $pluginName) + { + if (empty($pluginName)) { + throw new Exception('No plugin name given for MeasurableSettingsTable backend'); + } + + if (empty($idSite)) { + throw new Exception('No idSite given for MeasurableSettingsTable backend'); + } + + $this->idSite = (int) $idSite; + $this->pluginName = $pluginName; + } + + private function initDbIfNeeded() + { + if (!isset($this->db)) { + // we need to avoid db creation on instance creation, especially important in tracker mode + // the db might be never actually used when values are eg fetched from a cache + $this->db = Db::get(); + } + } + + /** + * @inheritdoc + */ + public function getStorageId() + { + return 'MeasurableSettings_' . $this->idSite . '_' . $this->pluginName; + } + + /** + * Saves (persists) the current setting values in the database. + */ + public function save($values) + { + $this->initDbIfNeeded(); + + $table = $this->getTableName(); + + $this->delete(); + + foreach ($values as $name => $value) { + if (!is_array($value)) { + $value = array($value); + } + + foreach ($value as $val) { + if (!isset($val)) { + continue; + } + + if (is_bool($val)) { + $val = (int) $val; + } + + $sql = "INSERT INTO $table (`idsite`, `plugin_name`, `setting_name`, `setting_value`) VALUES (?, ?, ?, ?)"; + $bind = array($this->idSite, $this->pluginName, $name, $val); + + $this->db->query($sql, $bind); + } + } + } + + public function load() + { + $this->initDbIfNeeded(); + + $table = $this->getTableName(); + + $sql = "SELECT `setting_name`, `setting_value` FROM " . $table . " WHERE idsite = ? and plugin_name = ?"; + $bind = array($this->idSite, $this->pluginName); + + $settings = $this->db->fetchAll($sql, $bind); + + $flat = array(); + foreach ($settings as $setting) { + $name = $setting['setting_name']; + + if (array_key_exists($name, $flat)) { + if (!is_array($flat[$name])) { + $flat[$name] = array($flat[$name]); + } + $flat[$name][] = $setting['setting_value']; + } else { + $flat[$name] = $setting['setting_value']; + } + } + + return $flat; + } + + private function getTableName() + { + return Common::prefixTable('site_setting'); + } + + public function delete() + { + $this->initDbIfNeeded(); + + $table = $this->getTableName(); + $sql = "DELETE FROM $table WHERE `idsite` = ? and plugin_name = ?"; + $bind = array($this->idSite, $this->pluginName); + + $this->db->query($sql, $bind); + } + + /** + * @internal + * @param int $idSite + * @throws \Exception + */ + public static function removeAllSettingsForSite($idSite) + { + $query = sprintf('DELETE FROM %s WHERE idsite = ?', Common::prefixTable('site_setting')); + Db::query($query, array($idSite)); + } + + /** + * @internal + * @param string $pluginName + * @throws \Exception + */ + public static function removeAllSettingsForPlugin($pluginName) + { + $query = sprintf('DELETE FROM %s WHERE plugin_name = ?', Common::prefixTable('site_setting')); + Db::query($query, array($pluginName)); + } +} diff --git a/core/Settings/Storage/Backend/Null.php b/core/Settings/Storage/Backend/Null.php new file mode 100644 index 0000000000..cc64c55654 --- /dev/null +++ b/core/Settings/Storage/Backend/Null.php @@ -0,0 +1,44 @@ +<?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\Storage\Backend; + +use Piwik\Settings\Storage; + +/** + * Static / temporary storage where a value shall never be persisted. Meaning it will use the default value + * for each request until configured differently. Useful for tests etc. + */ +class Null implements BackendInterface +{ + private $storageId; + + public function __construct($storageId) + { + $this->storageId = $storageId; + } + + public function load() + { + return array(); + } + + public function getStorageId() + { + return $this->storageId; + } + + public function delete() + { + } + + public function save($values) + { + } +} diff --git a/core/Settings/Storage/Backend/PluginSettingsTable.php b/core/Settings/Storage/Backend/PluginSettingsTable.php new file mode 100644 index 0000000000..87476ff8fb --- /dev/null +++ b/core/Settings/Storage/Backend/PluginSettingsTable.php @@ -0,0 +1,175 @@ +<?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\Storage\Backend; + +use Piwik\Common; +use Piwik\Db; +use Exception; + +/** + * Plugin settings backend. Stores all settings in a "plugin_setting" database table. + * + * If a value that needs to be stored is an array, will insert a new row for each value of this array. + */ +class PluginSettingsTable implements BackendInterface +{ + /** + * @var string + */ + private $pluginName; + + /** + * @var string + */ + private $userLogin; + + /** + * @var Db\AdapterInterface + */ + private $db; + + public function __construct($pluginName, $userLogin) + { + if (empty($pluginName)) { + throw new Exception('No plugin name given for PluginSettingsTable backend'); + } + + if ($userLogin === false || $userLogin === null) { + throw new Exception('Invalid user login name given for PluginSettingsTable backend'); + } + + $this->pluginName = $pluginName; + $this->userLogin = $userLogin; + } + + private function initDbIfNeeded() + { + if (!isset($this->db)) { + // we do not want to create a db connection on backend creation + $this->db = Db::get(); + } + } + + /** + * @inheritdoc + */ + public function getStorageId() + { + return 'PluginSettings_' . $this->pluginName . '_User_' . $this->userLogin; + } + + /** + * Saves (persists) the current setting values in the database. + */ + public function save($values) + { + $this->initDbIfNeeded(); + + $table = $this->getTableName(); + + $this->delete(); + + foreach ($values as $name => $value) { + + if (!is_array($value)) { + $value = array($value); + } + + foreach ($value as $val) { + if (!isset($val)) { + continue; + } + + if (is_bool($val)) { + $val = (int) $val; + } + + $sql = "INSERT INTO $table (`plugin_name`, `user_login`, `setting_name`, `setting_value`) VALUES (?, ?, ?, ?)"; + $bind = array($this->pluginName, $this->userLogin, $name, $val); + + $this->db->query($sql, $bind); + } + } + } + + public function load() + { + $this->initDbIfNeeded(); + + $sql = "SELECT `setting_name`, `setting_value` FROM " . $this->getTableName() . " WHERE plugin_name = ? and user_login = ?"; + $bind = array($this->pluginName, $this->userLogin); + + $settings = $this->db->fetchAll($sql, $bind); + + $flat = array(); + foreach ($settings as $setting) { + $name = $setting['setting_name']; + + if (array_key_exists($name, $flat)) { + if (!is_array($flat[$name])) { + $flat[$name] = array($flat[$name]); + } + $flat[$name][] = $setting['setting_value']; + } else { + $flat[$name] = $setting['setting_value']; + } + } + + return $flat; + } + + private function getTableName() + { + return Common::prefixTable('plugin_setting'); + } + + public function delete() + { + $this->initDbIfNeeded(); + + $table = $this->getTableName(); + $sql = "DELETE FROM $table WHERE `plugin_name` = ? and `user_login` = ?"; + $bind = array($this->pluginName, $this->userLogin); + + $this->db->query($sql, $bind); + } + + /** + * Unsets all settings for a user. The settings will be removed from the database. Used when + * a user is deleted. + * + * @internal + * @param string $userLogin + * @throws \Exception If the `$userLogin` is empty. Otherwise we would delete most plugin settings + */ + public static function removeAllUserSettingsForUser($userLogin) + { + if (empty($userLogin)) { + throw new Exception('No userLogin specified. Cannot remove all settings for this user'); + } + + $table = Common::prefixTable('plugin_setting'); + Db::get()->query(sprintf('DELETE FROM %s WHERE user_login = ?', $table), array($userLogin)); + } + + /** + * Unsets all settings for a plugin. The settings will be removed from the database. Used when + * a plugin is uninstalled. + * + * @internal + * @param string $pluginName + * @throws \Exception If the `$userLogin` is empty. + */ + public static function removeAllSettingsForPlugin($pluginName) + { + $table = Common::prefixTable('plugin_setting'); + Db::get()->query(sprintf('DELETE FROM %s WHERE plugin_name = ?', $table), array($pluginName)); + } +} diff --git a/core/Settings/Storage/Backend/SitesTable.php b/core/Settings/Storage/Backend/SitesTable.php new file mode 100644 index 0000000000..81de89b100 --- /dev/null +++ b/core/Settings/Storage/Backend/SitesTable.php @@ -0,0 +1,122 @@ +<?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\Storage\Backend; + +use Piwik\Plugins\SitesManager\Model; +use Piwik\Site; +use Exception; + +/** + * Backend for an existing site. Stores all settings in the "site" database table. + */ +class SitesTable implements BackendInterface +{ + /** + * @var int + */ + private $idSite; + + private $commaSeparatedArrayFields = array( + 'sitesearch_keyword_parameters', + 'sitesearch_category_parameters', + 'excluded_user_agents', + 'excluded_parameters', + 'excluded_ips' + ); + + // these fields are standard fields of a site and cannot be adjusted via a setting + private $allowedNames = array( + 'ecommerce', 'sitesearch', 'sitesearch_keyword_parameters', + 'sitesearch_category_parameters', 'exclude_unknown_urls', + 'excluded_ips', 'excluded_parameters', + 'excluded_user_agents', 'keep_url_fragment', 'urls' + ); + + public function __construct($idSite) + { + if (empty($idSite)) { + throw new Exception('No idSite given for Measurable backend'); + } + + $this->idSite = (int) $idSite; + } + + /** + * @inheritdoc + */ + public function getStorageId() + { + return 'SitesTable_' . $this->idSite; + } + + /** + * Saves (persists) the current setting values in the database. + */ + public function save($values) + { + $model = $this->getModel(); + + foreach ($values as $key => $value) { + if (!in_array($key, $this->allowedNames)) { + unset($values[$key]); + continue; + } + + if (is_array($value) && in_array($key, $this->commaSeparatedArrayFields)) { + $values[$key] = implode(',', $value); + } elseif (is_bool($value)) { + $values[$key] = (int) $value; + } + } + + if (!empty($values['urls'])) { + $urls = array_unique($values['urls']); + $values['main_url'] = array_shift($urls); + + $model->deleteSiteAliasUrls($this->idSite); + foreach ($urls as $url) { + $model->insertSiteUrl($this->idSite, $url); + } + } + + unset($values['urls']); + + $model->updateSite($values, $this->idSite); + Site::clearCacheForSite($this->idSite); + } + + public function load() + { + if (!empty($this->idSite)) { + $site = Site::getSite($this->idSite); + + $urls = $this->getModel(); + $site['urls'] = $urls->getSiteUrlsFromId($this->idSite); + + foreach ($this->commaSeparatedArrayFields as $field) { + if (!empty($site[$field]) && is_string($site[$field])) { + $site[$field] = explode(',', $site[$field]); + } + } + + return $site; + } + } + + private function getModel() + { + return new Model(); + } + + public function delete() + { + } + +} diff --git a/core/Settings/Storage/Factory.php b/core/Settings/Storage/Factory.php index 87d54ad3b1..5c1c6ca645 100644 --- a/core/Settings/Storage/Factory.php +++ b/core/Settings/Storage/Factory.php @@ -9,20 +9,113 @@ namespace Piwik\Settings\Storage; -use Piwik\Settings\Storage; +use Piwik\Settings\Storage\Backend\BackendInterface; use Piwik\SettingsServer; -use Piwik\Tracker\SettingsStorage; +/** + * Factory to create an instance of a storage. The storage can be created with different backends depending on the need. + * + * @package Piwik\Settings\Storage + */ class Factory { - public static function make($pluginName) + // cache prevents multiple loading of storage + private $cache = array(); + + /** + * Get a storage instance for plugin settings. + * + * The storage will hold values that belong to the given plugin name and user login. Be aware that instances + * for a specific plugin and login will be cached during one request for better performance. + * + * @param string $pluginName + * @param string $userLogin Use an empty string if settings should be not for a specific login + * @return Storage + */ + public function getPluginStorage($pluginName, $userLogin) + { + $id = $pluginName . '#' . $userLogin; + + if (!isset($this->cache[$id])) { + $backend = new Backend\PluginSettingsTable($pluginName, $userLogin); + $this->cache[$id] = $this->makeStorage($backend); + } + + return $this->cache[$id]; + } + + /** + * Get a storage instance for measurable settings. + * + * The storage will hold values that belong to the given idSite and plugin name. Be aware that a storage instance + * for a specific site and plugin will be cached during one request for better performance. + * + * @param int $idSite If idSite is empty it will use a backend that never actually persists any value. Pass + * $idSite = 0 to create a storage for a site that is about to be created. + * @param string $pluginName + * @return Storage + */ + public function getMeasurableSettingsStorage($idSite, $pluginName) + { + $id = 'measurableSettings' . (int) $idSite . '#' . $pluginName; + + if (empty($idSite)) { + return $this->getNonPersistentStorage($id . '#nonpersistent'); + } + + if (!isset($this->cache[$id])) { + $backend = new Backend\MeasurableSettingsTable($idSite, $pluginName); + $this->cache[$id] = $this->makeStorage($backend); + } + + return $this->cache[$id]; + } + + /** + * Get a storage instance for settings that will be saved in the "site" table. + * + * The storage will hold values that belong to the given idSite. Be aware that a storage instance for a specific + * site will be cached during one request for better performance. + * + * @param int $idSite If idSite is empty it will use a backend that never actually persists any value. Pass + * $idSite = 0 to create a storage for a site that is about to be created. + * + * @param int $idSite + * @return Storage + */ + public function getSitesTable($idSite) + { + $id = 'sitesTable#' . $idSite; + + if (empty($idSite)) { + return $this->getNonPersistentStorage($id . '#nonpersistent'); + } + + if (!isset($this->cache[$id])) { + $backend = new Backend\SitesTable($idSite); + $this->cache[$id] = $this->makeStorage($backend); + } + + return $this->cache[$id]; + } + + /** + * Get a storage with a backend that will never persist or load any value. + * + * @param string $key + * @return Storage + */ + public function getNonPersistentStorage($key) + { + return new Storage(new Backend\Null($key)); + } + + private function makeStorage(BackendInterface $backend) { if (SettingsServer::isTrackerApiRequest()) { - $storage = new SettingsStorage($pluginName); - } else { - $storage = new Storage($pluginName); + $backend = new Backend\Cache($backend); } - return $storage; + return new Storage($backend); } } diff --git a/core/Settings/Storage/StaticStorage.php b/core/Settings/Storage/StaticStorage.php deleted file mode 100644 index d8a43b6c9a..0000000000 --- a/core/Settings/Storage/StaticStorage.php +++ /dev/null @@ -1,34 +0,0 @@ -<?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\Storage; - -use Piwik\Settings\Storage; - -/** - * Static / temporary storage where a value will never be persisted meaning it will use the default value - * for each request until configured differently. Useful for tests. - * - * @api - */ -class StaticStorage extends Storage -{ - - protected function loadSettings() - { - return array(); - } - - /** - * Saves (persists) the current setting values in the database. - */ - public function save() - { - } -} diff --git a/core/Settings/Storage/Storage.php b/core/Settings/Storage/Storage.php new file mode 100644 index 0000000000..b24282068d --- /dev/null +++ b/core/Settings/Storage/Storage.php @@ -0,0 +1,117 @@ +<?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\Storage; + +use Piwik\Settings\Storage\Backend; + +/** + * A storage stores values for multiple settings. Storing multiple settings here saves having to do + * a "get" for each individual setting. A storage is usually stared between all individual setting instances + * within a plugin. + */ +class Storage +{ + /** + * Array containing all plugin settings values: Array( [setting-key] => [setting-value] ). + * + * @var array + */ + protected $settingsValues = array(); + + // for lazy loading of setting values + private $settingValuesLoaded = false; + + /** + * @var Backend\BackendInterface + */ + private $backend; + + /** + * Defines whether a value has changed since the settings were loaded or not. + * @var bool + */ + private $isDirty = false; + + public function __construct(Backend\BackendInterface $backend) + { + $this->backend = $backend; + } + + /** + * Get the currently used backend for this storage. + * @return Backend\BackendInterface + */ + public function getBackend() + { + return $this->backend; + } + + /** + * Saves (persists) the current setting values in the database if a value has actually changed. + */ + public function save() + { + if ($this->isDirty) { + $this->backend->save($this->settingsValues); + + $this->isDirty = false; + + Backend\Cache::clearCache(); + } + } + + /** + * Returns the current value for a setting. If no value is stored, the default value + * is be returned. + * + * @param string $key The name / key of a setting + * @param mixed $defaultValue Default value that will be used in case no value for this setting exists yet + * @param string $type The PHP internal type the value of the setting should have, see FieldConfig::TYPE_* + * constants. Only an actual value of the setting will be converted to the given type, the + * default value will not be converted. + * @return mixed + */ + public function getValue($key, $defaultValue, $type) + { + $this->loadSettingsIfNotDoneYet(); + + if (array_key_exists($key, $this->settingsValues)) { + settype($this->settingsValues[$key], $type); + return $this->settingsValues[$key]; + } + + return $defaultValue; + } + + /** + * Sets (overwrites) the value of a setting in memory. To persist the change across requests, {@link save()} must be + * called. + * + * @param string $key The name / key of a setting + * @param mixed $value The value that shall be set for the given setting. + */ + public function setValue($key, $value) + { + $this->loadSettingsIfNotDoneYet(); + + $this->isDirty = true; + $this->settingsValues[$key] = $value; + } + + private function loadSettingsIfNotDoneYet() + { + if ($this->settingValuesLoaded) { + return; + } + + $this->settingValuesLoaded = true; + $this->settingsValues = $this->backend->load(); + } +} diff --git a/core/Settings/StorageInterface.php b/core/Settings/StorageInterface.php deleted file mode 100644 index 6a4a76c5ac..0000000000 --- a/core/Settings/StorageInterface.php +++ /dev/null @@ -1,59 +0,0 @@ -<?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; - -/** - * Base type of all Setting storage implementations. - */ -interface StorageInterface -{ - /** - * Gets the current value for this setting. If no value is specified, the default value will be returned. - * - * @param Setting $setting - * - * @return mixed - * - * @throws \Exception In case the setting does not exist or if the current user is not allowed to change the value - * of this setting. - */ - public function getValue(Setting $setting); - - /** - * Removes the value for the given setting. Make sure to call `save()` afterwards, otherwise the removal has no - * effect. - * - * @param Setting $setting - */ - public function deleteValue(Setting $setting); - - /** - * Sets (overwrites) the value for the given setting. Make sure to call `save()` afterwards, otherwise the change - * has no effect. Before the value is saved a possibly define `validate` closure and `filter` closure will be - * called. Alternatively the value will be casted to the specfied setting type. - * - * @param Setting $setting - * @param string $value - * - * @throws \Exception In case the setting does not exist or if the current user is not allowed to change the value - * of this setting. - */ - public function setValue(Setting $setting, $value); - - /** - * Removes all settings for this plugin from the database. Useful when uninstalling - * a plugin. - */ - public function deleteAllValues(); - - /** - * Saves (persists) the current setting values in the database. - */ - public function save(); -} diff --git a/core/Settings/UserSetting.php b/core/Settings/UserSetting.php deleted file mode 100644 index aa3a08c400..0000000000 --- a/core/Settings/UserSetting.php +++ /dev/null @@ -1,146 +0,0 @@ -<?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\Common; -use Piwik\Piwik; - -/** - * Describes a per user setting. Each user will be able to change this setting for themselves, - * but not for other users. - * - * - * @api - */ -class UserSetting extends Setting -{ - private $userLogin = null; - - /** - * Null while not initialized, bool otherwise. - * @var null|bool - */ - private $hasReadAndWritePermission = null; - - /** - * Constructor. - * - * @param string $name The setting's persisted name. - * @param string $title The setting's display name. - * @param null|string $userLogin The user this setting applies to. Will default to the current user login. - */ - public function __construct($name, $title, $userLogin = null) - { - parent::__construct($name, $title); - - $this->setUserLogin($userLogin); - } - - /** - * Returns `true` if this setting can be displayed for the current user, `false` if otherwise. - * - * @return bool - */ - public function isReadableByCurrentUser() - { - return $this->isWritableByCurrentUser(); - } - - /** - * Returns `true` if this setting can be displayed for the current user, `false` if otherwise. - * - * @return bool - */ - public function isWritableByCurrentUser() - { - if (isset($this->hasReadAndWritePermission)) { - return $this->hasReadAndWritePermission; - } - - $this->hasReadAndWritePermission = Piwik::isUserHasSomeViewAccess(); - - return $this->hasReadAndWritePermission; - } - - /** - * Returns the display order. User settings are displayed after system settings. - * - * @return int - */ - public function getOrder() - { - return 60; - } - - private function buildUserSettingName($name, $userLogin = null) - { - if (empty($userLogin)) { - $userLogin = Piwik::getCurrentUserLogin(); - } - - // the asterisk tag is indeed important here and better than an underscore. Imagine a plugin has the settings - // "api_password" and "api". A user having the login "_password" could otherwise under circumstances change the - // setting for "api" although he is not allowed to. It is not so important at the moment because only alNum is - // currently allowed as a name this might change in the future. - $appendix = '#' . $userLogin . '#'; - - if (Common::stringEndsWith($name, $appendix)) { - return $name; - } - - return $name . $appendix; - } - - /** - * Sets the name of the user this setting will be set for. - * - * @param $userLogin - * @throws \Exception If the current user does not have permission to set the setting value - * of `$userLogin`. - */ - public function setUserLogin($userLogin) - { - if (!empty($userLogin) && !Piwik::hasUserSuperUserAccessOrIsTheUser($userLogin)) { - throw new \Exception('You do not have the permission to read the settings of a different user'); - } - - $this->userLogin = $userLogin; - $this->key = $this->buildUserSettingName($this->name, $userLogin); - } - - /** - * Unsets all settings for a user. The settings will be removed from the database. Used when - * a user is deleted. - * - * @param string $userLogin - * @throws \Exception If the `$userLogin` is empty. - */ - public static function removeAllUserSettingsForUser($userLogin) - { - if (empty($userLogin)) { - throw new \Exception('No userLogin specified'); - } - - $pluginsSettings = Manager::getAllPluginSettings(); - - foreach ($pluginsSettings as $pluginSettings) { - $settings = $pluginSettings->getSettings(); - - foreach ($settings as $setting) { - if ($setting instanceof UserSetting) { - $setting->setUserLogin($userLogin); - $setting->removeValue(); - } - } - - $pluginSettings->save(); - } - } -} |