PDF rausgenommen

This commit is contained in:
aschwarz
2023-01-23 11:03:31 +01:00
parent 82d562a322
commit a6523903eb
28078 changed files with 4247552 additions and 2 deletions

View File

@ -0,0 +1,262 @@
<?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\Validators\BaseValidator;
/**
* Lets you configure a form field.
*
* @api
*/
class FieldConfig
{
/**
* Shows a radio field. To use this field assign it to the `$uiControl` property.
*/
const UI_CONTROL_RADIO = 'radio';
/**
* Shows a text field. To use this field assign it to the `$uiControl` property.
*/
const UI_CONTROL_TEXT = 'text';
/**
* Shows an email text field. To use this field assign it to the `$uiControl` property.
*/
const UI_CONTROL_EMAIL = 'email';
/**
* Shows a URL text field. To use this field assign it to the `$uiControl` property.
*/
const UI_CONTROL_URL = 'url';
/**
* Shows a text area. To use this field assign it to the `$uiControl` property.
*/
const UI_CONTROL_TEXTAREA = 'textarea';
/**
* Shows a checkbox. To use this field assign it to the `$uiControl` property.
*/
const UI_CONTROL_CHECKBOX = 'checkbox';
/**
* Shows a password field. To use this field assign it to the `$uiControl` property.
*/
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. To use this field assign it to the `$uiControl` property.
*/
const UI_CONTROL_MULTI_SELECT = 'multiselect';
/**
* Shows a select field. To use this field assign it to the `$uiControl` property.
*/
const UI_CONTROL_SINGLE_SELECT = 'select';
/**
* Shows an expandable select field which is useful when each selectable value belongs to a group.
* To use this field assign it to the `$uiControl` property.
*/
const UI_CONTROL_SINGLE_EXPANDABLE_SELECT = 'expandable-select';
/**
* Lets a user configure an array of form fields.
*/
const UI_CONTROL_FIELD_ARRAY = 'field-array';
/**
* Lets a user configure two form fields next to each other, and add multiple entries of those two pairs.
*/
const UI_CONTROL_MULTI_TUPLE = 'multituple';
/**
* Generates a hidden form field. To use this field assign it to the `$uiControl` property.
*/
const UI_CONTROL_HIDDEN = 'hidden';
/**
* Expects an integer value. Is usually used when creating a setting.
*/
const TYPE_INT = 'integer';
/**
* Expects a float value. Is usually used when creating a setting.
*/
const TYPE_FLOAT = 'float';
/**
* Expects a string. Is usually used when creating a setting.
*/
const TYPE_STRING = 'string';
/**
* Expects a boolean. Is usually used when creating a setting.
*/
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;
/**
* Defines a custom template file for a UI control. This file should render a UI control and expose the value in a
* "formField.value" angular model. For an example see "plugins/CorePluginsAdmin/angularjs/form-field/field-text.html"
*
* @var string
*/
public $customUiControlTemplateFile = '';
/**
* 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;
/**
* Here you can add one or multiple instances of `Piwik\Validators\BaseValidator` to avoid having to
* write the same validators over and over again in {@link $validate}.
*
* Examples
* Want to require a value to be set?
* $fieldConfig->validators[] = new Piwik\Validators\NotEmpty();
*
* Want to require an email?
* $fieldConfig->validators[] = new Piwik\Validators\NotEmpty();
* $fieldConfig->validators[] = new Piwik\Validators\Email();
*
* The core comes with a set of various validators that can be used.
*
* @var BaseValidator[]
*/
public $validators = [];
}

View File

@ -0,0 +1,79 @@
<?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\FieldConfig;
/**
* Lets you configure a field for a field array.
*
* Usage:
*
* $field->uiControl = FieldConfig::UI_CONTROL_FIELD_ARRAY;
* $arrayField = new FieldConfig\ArrayField('Index', FieldConfig::UI_CONTROL_TEXT);
* $field->uiControlAttributes['field'] = $field->toArray();
*
* @api
*/
class ArrayField
{
/**
* 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;
/**
* Defines a custom template file for a UI control. This file should render a UI control and expose the value in a
* "formField.value" angular model. For an example see "plugins/CorePluginsAdmin/angularjs/form-field/field-text.html"
*
* @var string
*/
public $customUiControlTemplateFile = '';
/**
* 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 = '';
/**
* 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.
*
* @var null|array
*/
public $availableValues = null;
public function __construct($title, $uiControl = 'text')
{
$this->title = $title;
$this->uiControl = $uiControl;
}
public function toArray()
{
return array(
'title' => $this->title,
'uiControl' => $this->uiControl,
'templateFile' => $this->customUiControlTemplateFile,
'availableValues' => $this->availableValues,
);
}
}

View File

@ -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\FieldConfig;
/**
* Lets you configure a multi pair field.
*
* Usage:
*
* $field->uiControl = FieldConfig::UI_CONTROL_MULTI_PAIR;
* $field1 = new FieldConfig\MultiPair('Index', 'index', FieldConfig::UI_CONTROL_TEXT);
* $field2 = new FieldConfig\MultiPair('Value', 'value', FieldConfig::UI_CONTROL_TEXT);
* $field->uiControlAttributes['field1'] = $field1->toArray();
* $field->uiControlAttributes['field2'] = $field2->toArray();
*
* @api
*/
class MultiPair
{
/**
* The name of the key the index should have eg "dimension" will make an index array(array('dimension' => '...'))
* @var string
*/
public $key = '';
/**
* 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;
/**
* Defines a custom template file for a UI control. This file should render a UI control and expose the value in a
* "formField.value" angular model. For an example see "plugins/CorePluginsAdmin/angularjs/form-field/field-text.html"
*
* @var string
*/
public $customUiControlTemplateFile = '';
/**
* 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 = '';
/**
* 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.
*
* @var null|array
*/
public $availableValues = null;
public function __construct($title, $key, $uiControl = 'text')
{
$this->title = $title;
$this->key = $key;
$this->uiControl = $uiControl;
}
public function toArray()
{
return array(
'key' => $this->key,
'title' => $this->title,
'uiControl' => $this->uiControl,
'templateFile' => $this->customUiControlTemplateFile,
'availableValues' => $this->availableValues,
);
}
}

View File

@ -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', 'group'
);
/**
* 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);
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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\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 by d efault and
* the value of this setting will affect all users.
*
* See {@link \Piwik\Settings\Setting}.
*
* @api
*/
class SystemConfigSetting extends Setting
{
protected $configSection = 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 system setting belongs to.
*/
public function __construct($name, $defaultValue, $type, $pluginName, $configSectionName)
{
parent::__construct($name, $defaultValue, $type, $pluginName);
$factory = StaticContainer::get('Piwik\Settings\Storage\Factory');
$this->configSection = $configSectionName;
$this->storage = $factory->getConfigStorage($configSectionName);
}
/**
* Returns `true` if this setting is writable for the current user, `false` if otherwise. In case it returns
* writable for the current user it will be visible in the Plugin settings UI.
*
* @return bool
*/
public function isWritableByCurrentUser()
{
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::hasUserSuperUserAccess();
return $this->hasWritePermission;
}
/**
* Returns the config section the setting is for
*
* @return string
*/
public function getConfigSectionName()
{
return $this->configSection;
}
}

View File

@ -0,0 +1,98 @@
<?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\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 by default and
* the value of this setting will affect all users.
*
* See {@link \Piwik\Settings\Setting}.
*
* @api
*/
class SystemSetting extends Setting
{
/**
* 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 system setting belongs to.
*/
public function __construct($name, $defaultValue, $type, $pluginName)
{
parent::__construct($name, $defaultValue, $type, $pluginName);
$factory = StaticContainer::get('Piwik\Settings\Storage\Factory');
$this->storage = $factory->getPluginStorage($this->pluginName, $userLogin = '');
}
/**
* Returns `true` if this setting is writable for the current user, `false` if otherwise. In case it returns
* writable for the current user it will be visible in the Plugin settings UI.
*
* @return bool
*/
public function isWritableByCurrentUser()
{
if ($this->hasConfigValue()) {
return false;
}
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::hasUserSuperUserAccess();
return $this->hasWritePermission;
}
/**
* @inheritdoc
*/
public function getValue()
{
$defaultValue = parent::getValue(); // we access value first to make sure permissions are checked
$configValue = $this->getValueFromConfig();
if (isset($configValue)) {
$defaultValue = $configValue;
settype($defaultValue, $this->type);
}
return $defaultValue;
}
private function hasConfigValue()
{
$value = $this->getValueFromConfig();
return isset($value);
}
private function getValueFromConfig()
{
$config = Config::getInstance()->{$this->pluginName};
if (!empty($config) && array_key_exists($this->name, $config)) {
return $config[$this->name];
}
}
}

View File

@ -0,0 +1,110 @@
<?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;
}
/**
* This is only meant for some core features used by some core plugins that are shipped with Piwik
* @internal
* @ignore
* @param string $configSectionName
* @param $name
* @param $defaultValue
* @param $type
* @param $fieldConfigCallback
* @return SystemSetting
* @throws \Exception
*/
protected function makeSettingManagedInConfigOnly($configSectionName, $name, $defaultValue, $type, $fieldConfigCallback)
{
$setting = new SystemConfigSetting($name, $defaultValue, $type, $this->pluginName, $configSectionName);
$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));
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

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

View File

@ -0,0 +1,123 @@
<?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;
/**
* By default the plugin name is shown in the UI when managing plugin settings. However, you can overwrite
* the displayed title by specifying a title.
* @var string
*/
protected $title = '';
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));
}
}
}
public function getTitle()
{
if (!empty($this->title)) {
return $this->title;
}
return $this->pluginName;
}
/**
* @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();
});
}
/**
* Adds a new setting to the settings container.
*
* @param Setting $setting
* @throws \Exception If there is a setting with the same name that already exists.
* If the name contains non-alphanumeric characters.
*/
public 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();
}
}
}

View File

@ -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();
}

View File

@ -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();
}
}

View File

@ -0,0 +1,76 @@
<?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;
/**
* Backend for an existing site. Stores all settings in the "site" database table.
*/
class Config implements BackendInterface
{
private $section;
public function __construct($section)
{
if (empty($section)) {
throw new \Exception('No section given for config section backend');
}
$this->section = $section;
}
/**
* @inheritdoc
*/
public function getStorageId()
{
return 'Config_' . $this->section;
}
private function getConfig()
{
return \Piwik\Config::getInstance();
}
/**
* Saves (persists) the current setting values in the database.
*/
public function save($values)
{
$section = $this->load();
foreach ($values as $key => $value) {
$section[$key] = $value;
}
$config = $this->getConfig();
$config->{$this->section} = $section;
$config->forceSave();
}
public function load()
{
$config = $this->getConfig();
$section = $config->{$this->section};
$values = array();
// remove reference
foreach ($section as $key => $value) {
$values[$key] = $value;
}
return $values;
}
public function delete()
{
$this->getConfig()->{$this->section} = array();
}
}

View File

@ -0,0 +1,203 @@
<?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;
use Piwik\Version;
/**
* 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 (!isset($value)) {
continue;
}
if (is_array($value) || is_object($value)) {
$jsonEncoded = 1;
$value = json_encode($value);
} else {
$jsonEncoded = 0;
if (is_bool($value)) {
// we are currently not storing booleans as json as it could result in trouble with the UI and regress
// preselecting the correct value
$value = (int) $value;
}
}
$sql = "INSERT INTO $table (`idsite`, `plugin_name`, `setting_name`, `setting_value`, `json_encoded`) VALUES (?, ?, ?, ?, ?)";
$bind = array($this->idSite, $this->pluginName, $name, $value, $jsonEncoded);
$this->db->query($sql, $bind);
}
}
private function jsonEncodedMissingError(Exception $e)
{
return strpos($e->getMessage(), 'json_encoded') !== false;
}
public function load()
{
$this->initDbIfNeeded();
$table = $this->getTableName();
$sql = "SELECT `setting_name`, `setting_value`, `json_encoded` FROM " . $table . " WHERE idsite = ? and plugin_name = ?";
$bind = array($this->idSite, $this->pluginName);
try {
$settings = $this->db->fetchAll($sql, $bind);
} catch (\Exception $e) {
// we catch an exception since json_encoded might not be present before matomo is updated to 3.5.0+ but the updater
// may run this query
if ($this->jsonEncodedMissingError($e)) {
$sql = "SELECT `setting_name`, `setting_value` FROM " . $table . " WHERE idsite = ? and plugin_name = ?";
$settings = $this->db->fetchAll($sql, $bind);
} else {
throw $e;
}
}
$flat = array();
foreach ($settings as $setting) {
$name = $setting['setting_name'];
if (!empty($setting['json_encoded'])) {
$flat[$name] = json_decode($setting['setting_value'], true);
} elseif (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)
{
try {
$query = sprintf('DELETE FROM %s WHERE idsite = ?', Common::prefixTable('site_setting'));
Db::query($query, array($idSite));
} catch (Exception $e) {
if ($e->getCode() != 42) {
// ignore table not found error, which might occur when updating from an older version of Piwik
throw $e;
}
}
}
/**
* @internal
* @param string $pluginName
* @throws \Exception
*/
public static function removeAllSettingsForPlugin($pluginName)
{
try {
$query = sprintf('DELETE FROM %s WHERE plugin_name = ?', Common::prefixTable('site_setting'));
Db::query($query, array($pluginName));
} catch (Exception $e) {
if ($e->getCode() != 42) {
// ignore table not found error, which might occur when updating from an older version of Piwik
throw $e;
}
}
}
}

View File

@ -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 NullBackend 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)
{
}
}

View File

@ -0,0 +1,209 @@
<?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;
use Piwik\Version;
/**
* 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 (!isset($value)) {
continue;
}
if (is_array($value) || is_object($value)) {
$jsonEncoded = 1;
$value = json_encode($value);
} else {
$jsonEncoded = 0;
if (is_bool($value)) {
// we are currently not storing booleans as json as it could result in trouble with the UI and regress
// preselecting the correct value
$value = (int) $value;
}
}
$sql = "INSERT INTO $table (`plugin_name`, `user_login`, `setting_name`, `setting_value`, `json_encoded`) VALUES (?, ?, ?, ?, ?)";
$bind = array($this->pluginName, $this->userLogin, $name, $value, $jsonEncoded);
$this->db->query($sql, $bind);
}
}
private function jsonEncodedMissingError(Exception $e)
{
return strpos($e->getMessage(), 'json_encoded') !== false;
}
public function load()
{
$this->initDbIfNeeded();
$sql = "SELECT `setting_name`, `setting_value`, `json_encoded` FROM " . $this->getTableName() . " WHERE plugin_name = ? and user_login = ?";
$bind = array($this->pluginName, $this->userLogin);
try {
$settings = $this->db->fetchAll($sql, $bind);
} catch (\Exception $e) {
// we catch an exception since json_encoded might not be present before matomo is updated to 3.5.0+ but the updater
// may run this query
if ($this->jsonEncodedMissingError($e)) {
$sql = "SELECT `setting_name`, `setting_value` FROM " . $this->getTableName() . " WHERE plugin_name = ? and user_login = ?";
$settings = $this->db->fetchAll($sql, $bind);
} else {
throw $e;
}
}
$flat = array();
foreach ($settings as $setting) {
$name = $setting['setting_name'];
if (!empty($setting['json_encoded'])) {
$flat[$name] = json_decode($setting['setting_value'], true);
} elseif (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');
}
try {
$table = Common::prefixTable('plugin_setting');
Db::get()->query(sprintf('DELETE FROM %s WHERE user_login = ?', $table), array($userLogin));
} catch (Exception $e) {
if ($e->getCode() != 42) {
// ignore table not found error, which might occur when updating from an older version of Piwik
throw $e;
}
}
}
/**
* 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)
{
try {
$table = Common::prefixTable('plugin_setting');
Db::get()->query(sprintf('DELETE FROM %s WHERE plugin_name = ?', $table), array($pluginName));
} catch (Exception $e) {
if ($e->getCode() != 42) {
// ignore table not found error, which might occur when updating from an older version of Piwik
throw $e;
}
}
}
}

View File

@ -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()
{
}
}

View File

@ -0,0 +1,142 @@
<?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\BackendInterface;
use Piwik\SettingsServer;
/**
* 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
{
// 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];
}
/**
* @param string $section
* @return mixed
*/
public function getConfigStorage($section)
{
$id = 'config' . $section;
if (!isset($this->cache[$id])) {
$backend = new Backend\Config($section);
$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\NullBackend($key));
}
/**
* Makes a new storage object based on a custom backend interface.
*
* @param BackendInterface $backend
* @return Storage
*/
public function makeStorage(BackendInterface $backend)
{
if (SettingsServer::isTrackerApiRequest()) {
$backend = new Backend\Cache($backend);
}
return new Storage($backend);
}
}

View File

@ -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();
}
}