Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Steur <thomas.steur@gmail.com>2016-03-10 00:55:45 +0300
committerThomas Steur <thomas.steur@gmail.com>2016-04-11 05:11:33 +0300
commitb52ae4e7e488e0474d67c54578e1d6c1aa066bff (patch)
treef94b02f774cbc24faaa18f29ee1e19fef8b338af /core/Settings
parent6ba622a68a26792af8cc22131f488f7ff5189d2c (diff)
refs #7983 let plugins add or remove fields to websites and better settings api
Diffstat (limited to 'core/Settings')
-rw-r--r--core/Settings/FieldConfig.php208
-rw-r--r--core/Settings/Manager.php157
-rw-r--r--core/Settings/Measurable/MeasurableProperty.php89
-rw-r--r--core/Settings/Measurable/MeasurableSetting.php72
-rw-r--r--core/Settings/Measurable/MeasurableSettings.php141
-rw-r--r--core/Settings/Plugin/SystemSetting.php (renamed from core/Settings/SystemSetting.php)72
-rw-r--r--core/Settings/Plugin/SystemSettings.php90
-rw-r--r--core/Settings/Plugin/UserSetting.php73
-rw-r--r--core/Settings/Plugin/UserSettings.php93
-rw-r--r--core/Settings/Setting.php378
-rw-r--r--core/Settings/Settings.php107
-rw-r--r--core/Settings/Storage.php148
-rw-r--r--core/Settings/Storage/Backend/BackendInterface.php47
-rw-r--r--core/Settings/Storage/Backend/Cache.php78
-rw-r--r--core/Settings/Storage/Backend/MeasurableSettingsTable.php167
-rw-r--r--core/Settings/Storage/Backend/Null.php44
-rw-r--r--core/Settings/Storage/Backend/PluginSettingsTable.php175
-rw-r--r--core/Settings/Storage/Backend/SitesTable.php122
-rw-r--r--core/Settings/Storage/Factory.php107
-rw-r--r--core/Settings/Storage/StaticStorage.php34
-rw-r--r--core/Settings/Storage/Storage.php117
-rw-r--r--core/Settings/StorageInterface.php59
-rw-r--r--core/Settings/UserSetting.php146
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();
- }
- }
-}