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
path: root/core
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
parent6ba622a68a26792af8cc22131f488f7ff5189d2c (diff)
refs #7983 let plugins add or remove fields to websites and better settings api
Diffstat (limited to 'core')
-rw-r--r--core/API/DataTableManipulator/ReportTotalsCalculator.php4
-rw-r--r--core/API/DataTablePostProcessor.php4
-rw-r--r--core/DataTable/Filter/PivotByDimension.php4
-rw-r--r--core/DataTable/Renderer/Xml.php5
-rw-r--r--core/Db/Schema/Mysql.php12
-rw-r--r--core/Http/ControllerResolver.php10
-rw-r--r--core/Measurable/Measurable.php13
-rw-r--r--core/Measurable/MeasurableSetting.php70
-rw-r--r--core/Measurable/MeasurableSettings.php103
-rw-r--r--core/Measurable/Settings/Storage.php104
-rw-r--r--core/Measurable/Type.php3
-rw-r--r--core/Measurable/Type/TypeManager.php10
-rw-r--r--core/Menu/MenuAbstract.php8
-rw-r--r--core/Notification/Manager.php4
-rw-r--r--core/Plugin.php4
-rw-r--r--core/Plugin/API.php10
-rw-r--r--core/Plugin/Controller.php4
-rw-r--r--core/Plugin/Manager.php6
-rw-r--r--core/Plugin/Menu.php2
-rw-r--r--core/Plugin/Report.php4
-rw-r--r--core/Plugin/ReportsProvider.php (renamed from core/Plugin/Reports.php)2
-rw-r--r--core/Plugin/Settings.php308
-rw-r--r--core/Plugin/SettingsProvider.php215
-rw-r--r--core/Plugin/ViewDataTable.php4
-rw-r--r--core/Plugin/Visualization.php4
-rw-r--r--core/Plugin/WidgetsProvider.php (renamed from core/Plugin/Widgets.php)2
-rw-r--r--core/Scheduler/TaskLoader.php2
-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
-rw-r--r--core/Site.php11
-rw-r--r--core/Tracker/SettingsStorage.php58
-rw-r--r--core/Updates/3.0.0-b1.php102
-rw-r--r--core/ViewDataTable/Config.php4
-rw-r--r--core/ViewDataTable/Factory.php4
-rw-r--r--core/Widget/WidgetsList.php4
56 files changed, 2336 insertions, 1492 deletions
diff --git a/core/API/DataTableManipulator/ReportTotalsCalculator.php b/core/API/DataTableManipulator/ReportTotalsCalculator.php
index 7906077b1e..e1e467d0c1 100644
--- a/core/API/DataTableManipulator/ReportTotalsCalculator.php
+++ b/core/API/DataTableManipulator/ReportTotalsCalculator.php
@@ -13,7 +13,7 @@ use Piwik\DataTable;
use Piwik\Metrics;
use Piwik\Period;
use Piwik\Plugin\Report;
-use Piwik\Plugin\Reports;
+use Piwik\Plugin\ReportsProvider;
/**
* This class is responsible for setting the metadata property 'totals' on each dataTable if the report
@@ -212,7 +212,7 @@ class ReportTotalsCalculator extends DataTableManipulator
private function findFirstLevelReport()
{
- $reports = new Reports();
+ $reports = new ReportsProvider();
foreach ($reports->getAllReports() as $report) {
$actionToLoadSubtables = $report->getActionToLoadSubTables();
if ($actionToLoadSubtables == $this->apiMethod
diff --git a/core/API/DataTablePostProcessor.php b/core/API/DataTablePostProcessor.php
index c6424ea793..c2fdd1569c 100644
--- a/core/API/DataTablePostProcessor.php
+++ b/core/API/DataTablePostProcessor.php
@@ -19,7 +19,7 @@ use Piwik\DataTable\Filter\PivotByDimension;
use Piwik\Metrics\Formatter;
use Piwik\Plugin\ProcessedMetric;
use Piwik\Plugin\Report;
-use Piwik\Plugin\Reports;
+use Piwik\Plugin\ReportsProvider;
/**
* Processes DataTables that should be served through Piwik's APIs. This processing handles
@@ -72,7 +72,7 @@ class DataTablePostProcessor
$this->apiMethod = $apiMethod;
$this->setRequest($request);
- $this->report = Reports::factory($apiModule, $apiMethod);
+ $this->report = ReportsProvider::factory($apiModule, $apiMethod);
$this->apiInconsistencies = new Inconsistencies();
$this->setFormatter(new Formatter());
}
diff --git a/core/DataTable/Filter/PivotByDimension.php b/core/DataTable/Filter/PivotByDimension.php
index bbc72f46d9..f668afb0b9 100644
--- a/core/DataTable/Filter/PivotByDimension.php
+++ b/core/DataTable/Filter/PivotByDimension.php
@@ -21,7 +21,7 @@ use Piwik\Period;
use Piwik\Piwik;
use Piwik\Plugin\Report;
use Piwik\Plugin\Segment;
-use Piwik\Plugin\Reports;
+use Piwik\Plugin\ReportsProvider;
use Piwik\Site;
/**
@@ -329,7 +329,7 @@ class PivotByDimension extends BaseFilter
{
list($module, $method) = explode('.', $report);
- $this->thisReport = Reports::factory($module, $method);
+ $this->thisReport = ReportsProvider::factory($module, $method);
if (empty($this->thisReport)) {
throw new Exception("Unable to find report '$report'.");
}
diff --git a/core/DataTable/Renderer/Xml.php b/core/DataTable/Renderer/Xml.php
index b01f5596a2..31e56a4fde 100644
--- a/core/DataTable/Renderer/Xml.php
+++ b/core/DataTable/Renderer/Xml.php
@@ -176,9 +176,9 @@ class Xml extends Renderer
}
// render the array item
- if (is_array($value)) {
+ if (is_array($value) || $value instanceof \stdClass) {
$result .= $prefixLines . $prefix . "\n";
- $result .= $this->renderArray($value, $prefixLines . "\t");
+ $result .= $this->renderArray((array) $value, $prefixLines . "\t");
$result .= $prefixLines . $suffix . "\n";
} elseif ($value instanceof DataTable
|| $value instanceof Map
@@ -198,6 +198,7 @@ class Xml extends Renderer
}
} else {
$xmlValue = self::formatValueXml($value);
+
if (strlen($xmlValue) != 0) {
$result .= $prefixLines . $prefix . $xmlValue . $suffix . "\n";
} else {
diff --git a/core/Db/Schema/Mysql.php b/core/Db/Schema/Mysql.php
index 07cb1490d0..61532c47ef 100644
--- a/core/Db/Schema/Mysql.php
+++ b/core/Db/Schema/Mysql.php
@@ -76,11 +76,21 @@ class Mysql implements SchemaInterface
) ENGINE=$engine DEFAULT CHARSET=utf8
",
+ 'plugin_setting' => "CREATE TABLE {$prefixTables}plugin_setting (
+ `plugin_name` VARCHAR(60) NOT NULL,
+ `setting_name` VARCHAR(255) NOT NULL,
+ `setting_value` LONGTEXT NOT NULL,
+ `user_login` VARCHAR(100) NOT NULL DEFAULT '',
+ INDEX(plugin_name, user_login)
+ ) ENGINE=$engine DEFAULT CHARSET=utf8
+ ",
+
'site_setting' => "CREATE TABLE {$prefixTables}site_setting (
idsite INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `plugin_name` VARCHAR(60) NOT NULL,
`setting_name` VARCHAR(255) NOT NULL,
`setting_value` LONGTEXT NOT NULL,
- PRIMARY KEY(idsite, setting_name)
+ INDEX(idsite, plugin_name)
) ENGINE=$engine DEFAULT CHARSET=utf8
",
diff --git a/core/Http/ControllerResolver.php b/core/Http/ControllerResolver.php
index e0fe7d98e3..b9e71b0f11 100644
--- a/core/Http/ControllerResolver.php
+++ b/core/Http/ControllerResolver.php
@@ -12,9 +12,9 @@ use DI\FactoryInterface;
use Exception;
use Piwik\Plugin;
use Piwik\Plugin\Controller;
-use Piwik\Plugin\Reports;
+use Piwik\Plugin\ReportsProvider;
use Piwik\Session;
-use Piwik\Plugin\Widgets;
+use Piwik\Plugin\WidgetsProvider;
/**
* Resolves the controller that will handle the request.
@@ -29,11 +29,11 @@ class ControllerResolver
private $abstractFactory;
/**
- * @var Widgets
+ * @var WidgetsProvider
*/
private $widgets;
- public function __construct(FactoryInterface $abstractFactory, Widgets $widgets)
+ public function __construct(FactoryInterface $abstractFactory, WidgetsProvider $widgets)
{
$this->abstractFactory = $abstractFactory;
$this->widgets = $widgets;
@@ -100,7 +100,7 @@ class ControllerResolver
private function createReportController($module, $action, array &$parameters)
{
- $report = Reports::factory($module, $action);
+ $report = ReportsProvider::factory($module, $action);
if (!$report) {
return null;
diff --git a/core/Measurable/Measurable.php b/core/Measurable/Measurable.php
index d80c1f0323..6223a6931f 100644
--- a/core/Measurable/Measurable.php
+++ b/core/Measurable/Measurable.php
@@ -9,7 +9,6 @@
namespace Piwik\Measurable;
-use Exception;
use Piwik\Site;
/**
@@ -17,16 +16,4 @@ use Piwik\Site;
*/
class Measurable extends Site
{
-
- public function getSettingValue($name)
- {
- $settings = new MeasurableSettings($this->id, $this->getType());
- $setting = $settings->getSetting($name);
-
- if (!empty($setting)) {
- return $setting->getValue(); // Calling `getValue` makes sure we respect read permission of this setting
- }
-
- throw new Exception(sprintf('Setting %s does not exist', $name));
- }
}
diff --git a/core/Measurable/MeasurableSetting.php b/core/Measurable/MeasurableSetting.php
deleted file mode 100644
index 91e0970442..0000000000
--- a/core/Measurable/MeasurableSetting.php
+++ /dev/null
@@ -1,70 +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\Measurable;
-
-use Piwik\Piwik;
-
-/**
- * Describes a Type setting for a website, mobile app, ...
- *
- * See {@link \Piwik\Plugin\Settings}.
- */
-class MeasurableSetting extends \Piwik\Settings\Setting
-{
- /**
- * By default the value of the type setting is only readable by users having at least view access to one site
- *
- * @var bool
- * @since 2.14.0
- */
- public $readableByCurrentUser = false;
-
- /**
- * By default the value of the type setting is only writable by users having at least admin access to one site
- * @var bool
- * @internal
- */
- public $writableByCurrentUser = false;
-
- /**
- * Constructor.
- *
- * @param string $name The persisted name of the setting.
- * @param string $title The display name of the setting.
- */
- public function __construct($name, $title)
- {
- parent::__construct($name, $title);
-
- $this->writableByCurrentUser = Piwik::isUserHasSomeAdminAccess();
- $this->readableByCurrentUser = Piwik::isUserHasSomeViewAccess();
- }
-
- /**
- * 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 $this->writableByCurrentUser;
- }
-
- /**
- * Returns `true` if this setting can be displayed for the current user, `false` if otherwise.
- *
- * @return bool
- */
- public function isReadableByCurrentUser()
- {
- return $this->readableByCurrentUser;
- }
-}
diff --git a/core/Measurable/MeasurableSettings.php b/core/Measurable/MeasurableSettings.php
deleted file mode 100644
index 7d627e0a2c..0000000000
--- a/core/Measurable/MeasurableSettings.php
+++ /dev/null
@@ -1,103 +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\Measurable;
-
-use Piwik\Db;
-use Piwik\Piwik;
-use Piwik\Plugin\Settings;
-use Piwik\Measurable\Settings\Storage;
-use Piwik\Settings\Setting;
-use Piwik\Measurable\Type\TypeManager;
-
-class MeasurableSettings extends Settings
-{
-
- /**
- * @var int
- */
- private $idSite = null;
-
- /**
- * @var string
- */
- private $idType = null;
-
- /**
- * @param int $idSite The id of a site. If you want to get settings for a not yet created site just pass an empty value ("0")
- * @param string $idType If no typeId is given, the type of the site will be used.
- *
- * @throws \Exception
- */
- public function __construct($idSite, $idType)
- {
- $this->idSite = $idSite;
- $this->idType = $idType;
- $this->storage = new Storage(Db::get(), $this->idSite);
- $this->pluginName = 'MeasurableSettings';
-
- $this->init();
- }
-
- protected function init()
- {
- $typeManager = new TypeManager();
- $type = $typeManager->getType($this->idType);
- $type->configureMeasurableSettings($this);
-
- /**
- * This event is posted when generating settings for a Measurable (website). You can add any Measurable settings
- * that you wish to be shown in the Measurable manager (websites manager). If you need to add settings only for
- * eg MobileApp measurables you can use eg `$type->getId() === Piwik\Plugins\MobileAppMeasurable\Type::ID` and
- * add only settings if the condition is true.
- *
- * @since Piwik 2.14.0
- * @deprecated will be removed in Piwik 3.0.0
- *
- * @param MeasurableSettings $this
- * @param \Piwik\Measurable\Type $type
- * @param int $idSite
- */
- Piwik::postEvent('Measurable.initMeasurableSettings', array($this, $type, $this->idSite));
- }
-
- public function addSetting(Setting $setting)
- {
- if ($this->idSite && $setting instanceof MeasurableSetting) {
- $setting->writableByCurrentUser = Piwik::isUserHasAdminAccess($this->idSite);
- }
-
- parent::addSetting($setting);
- }
-
- public function save()
- {
- Piwik::checkUserHasAdminAccess($this->idSite);
-
- $typeManager = new TypeManager();
- $type = $typeManager->getType($this->idType);
-
- /**
- * Triggered just before Measurable settings are about to be saved. You can use this event for example
- * to validate not only one setting but multiple ssetting. For example whether username
- * and password matches.
- *
- * @since Piwik 2.14.0
- * @deprecated will be removed in Piwik 3.0.0
- *
- * @param MeasurableSettings $this
- * @param \Piwik\Measurable\Type $type
- * @param int $idSite
- */
- Piwik::postEvent('Measurable.beforeSaveSettings', array($this, $type, $this->idSite));
-
- $this->storage->save();
- }
-
-}
-
diff --git a/core/Measurable/Settings/Storage.php b/core/Measurable/Settings/Storage.php
deleted file mode 100644
index df9748af5e..0000000000
--- a/core/Measurable/Settings/Storage.php
+++ /dev/null
@@ -1,104 +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\Measurable\Settings;
-
-use Piwik\Db;
-use Piwik\Common;
-use Piwik\Settings\Setting;
-
-/**
- * Storage for site settings
- */
-class Storage extends \Piwik\Settings\Storage
-{
- private $idSite = null;
-
- /**
- * @var Db
- */
- private $db = null;
-
- private $toBeDeleted = array();
-
- public function __construct(Db\AdapterInterface $db, $idSite)
- {
- $this->db = $db;
- $this->idSite = $idSite;
- }
-
- protected function deleteSettingsFromStorage()
- {
- $table = $this->getTableName();
- $sql = "DELETE FROM $table WHERE `idsite` = ?";
- $bind = array($this->idSite);
-
- $this->db->query($sql, $bind);
- }
-
- public function deleteValue(Setting $setting)
- {
- $this->toBeDeleted[$setting->getName()] = true;
- parent::deleteValue($setting);
- }
-
- public function setValue(Setting $setting, $value)
- {
- $this->toBeDeleted[$setting->getName()] = false; // prevent from deleting this setting, we will create/update it
- parent::setValue($setting, $value);
- }
-
- /**
- * Saves (persists) the current setting values in the database.
- */
- public function save()
- {
- $table = $this->getTableName();
-
- foreach ($this->toBeDeleted as $name => $delete) {
- if ($delete) {
- $sql = "DELETE FROM $table WHERE `idsite` = ? and `setting_name` = ?";
- $bind = array($this->idSite, $name);
-
- $this->db->query($sql, $bind);
- }
- }
-
- $this->toBeDeleted = array();
-
- foreach ($this->settingsValues as $name => $value) {
- $value = serialize($value);
-
- $sql = "INSERT INTO $table (`idsite`, `setting_name`, `setting_value`) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE `setting_value` = ?";
- $bind = array($this->idSite, $name, $value, $value);
-
- $this->db->query($sql, $bind);
- }
- }
-
- protected function loadSettings()
- {
- $sql = "SELECT `setting_name`, `setting_value` FROM " . $this->getTableName() . " WHERE idsite = ?";
- $bind = array($this->idSite);
-
- $settings =$this->db->fetchAll($sql, $bind);
-
- $flat = array();
- foreach ($settings as $setting) {
- $flat[$setting['setting_name']] = unserialize($setting['setting_value']);
- }
-
- return $flat;
- }
-
- private function getTableName()
- {
- return Common::prefixTable('site_setting');
- }
-}
diff --git a/core/Measurable/Type.php b/core/Measurable/Type.php
index e9457a660f..5d24f04409 100644
--- a/core/Measurable/Type.php
+++ b/core/Measurable/Type.php
@@ -55,8 +55,5 @@ class Type
return $this->howToSetupUrl;
}
- public function configureMeasurableSettings(MeasurableSettings $settings)
- {
- }
}
diff --git a/core/Measurable/Type/TypeManager.php b/core/Measurable/Type/TypeManager.php
index af40d9c624..a514abbbdc 100644
--- a/core/Measurable/Type/TypeManager.php
+++ b/core/Measurable/Type/TypeManager.php
@@ -8,6 +8,7 @@
*/
namespace Piwik\Measurable\Type;
+use Piwik\Container\StaticContainer;
use Piwik\Plugin\Manager as PluginManager;
use Piwik\Measurable\Type;
@@ -18,7 +19,14 @@ class TypeManager
*/
public function getAllTypes()
{
- return PluginManager::getInstance()->findComponents('Type', '\\Piwik\\Measurable\\Type');
+ $components = PluginManager::getInstance()->findComponents('Type', '\\Piwik\\Measurable\\Type');
+
+ $instances = array();
+ foreach ($components as $component) {
+ $instances[] = StaticContainer::get($component);
+ }
+
+ return $instances;
}
/**
diff --git a/core/Menu/MenuAbstract.php b/core/Menu/MenuAbstract.php
index 777d97b523..25e6ce0f53 100644
--- a/core/Menu/MenuAbstract.php
+++ b/core/Menu/MenuAbstract.php
@@ -8,6 +8,7 @@
*/
namespace Piwik\Menu;
+use Piwik\Container\StaticContainer;
use Piwik\Plugins\SitesManager\API;
use Piwik\Singleton;
use Piwik\Plugin\Manager as PluginManager;
@@ -71,7 +72,12 @@ abstract class MenuAbstract extends Singleton
return self::$menus;
}
- self::$menus = PluginManager::getInstance()->findComponents('Menu', 'Piwik\\Plugin\\Menu');
+ $components = PluginManager::getInstance()->findComponents('Menu', 'Piwik\\Plugin\\Menu');
+
+ self::$menus = array();
+ foreach ($components as $component) {
+ self::$menus[] = StaticContainer::get($component);
+ }
return self::$menus;
}
diff --git a/core/Notification/Manager.php b/core/Notification/Manager.php
index bdf1f130dd..4ae3acde42 100644
--- a/core/Notification/Manager.php
+++ b/core/Notification/Manager.php
@@ -129,6 +129,10 @@ class Manager
private static function removeOldestNotificationsIfThereAreTooMany()
{
+ if (!self::isSessionEnabled()) {
+ return;
+ }
+
$maxNotificationsInSession = 30;
$session = static::getSession();
diff --git a/core/Plugin.php b/core/Plugin.php
index 852e09522e..91d5a8a8ad 100644
--- a/core/Plugin.php
+++ b/core/Plugin.php
@@ -324,7 +324,7 @@ class Plugin
* given subclass. If the requested file exists but does not extend this class
* a warning will be shown to advice a developer to extend this certain class.
*
- * @return \stdClass|null Null if the requested component does not exist or an instance of the found
+ * @return string|null Null if the requested component does not exist or an instance of the found
* component.
*/
public function findComponent($componentName, $expectedSubclass)
@@ -369,7 +369,7 @@ class Plugin
$this->cache->save($cacheId, $classname);
}
- return StaticContainer::get($classname);
+ return $classname;
}
public function findMultipleComponents($directoryWithinPlugin, $expectedSubclass)
diff --git a/core/Plugin/API.php b/core/Plugin/API.php
index c54e10a82b..0f48816af3 100644
--- a/core/Plugin/API.php
+++ b/core/Plugin/API.php
@@ -86,6 +86,16 @@ abstract class API
}
/**
+ * Used in tests only
+ * @ignore
+ * @deprecated
+ */
+ public static function unsetAllInstances()
+ {
+ self::$instances = array();
+ }
+
+ /**
* Sets the singleton instance. For testing purposes.
* @ignore
* @deprecated
diff --git a/core/Plugin/Controller.php b/core/Plugin/Controller.php
index 74691e577c..ad4addf85c 100644
--- a/core/Plugin/Controller.php
+++ b/core/Plugin/Controller.php
@@ -33,7 +33,7 @@ use Piwik\Piwik;
use Piwik\Plugins\CoreAdminHome\CustomLogo;
use Piwik\Plugins\CoreVisualizations\Visualizations\JqplotGraph\Evolution;
use Piwik\Plugins\LanguagesManager\LanguagesManager;
-use Piwik\Plugin\Reports;
+use Piwik\Plugin\ReportsProvider;
use Piwik\SettingsPiwik;
use Piwik\Site;
use Piwik\Url;
@@ -314,7 +314,7 @@ abstract class Controller
protected function renderReport($apiAction, $controllerAction = false)
{
if (empty($controllerAction) && is_string($apiAction)) {
- $report = Reports::factory($this->pluginName, $apiAction);
+ $report = ReportsProvider::factory($this->pluginName, $apiAction);
if (!empty($report)) {
$apiAction = $report;
diff --git a/core/Plugin/Manager.php b/core/Plugin/Manager.php
index 2b552f48e0..b490451747 100644
--- a/core/Plugin/Manager.php
+++ b/core/Plugin/Manager.php
@@ -12,8 +12,11 @@ namespace Piwik\Plugin;
use Piwik\Application\Kernel\PluginList;
use Piwik\Cache;
use Piwik\Columns\Dimension;
+use Piwik\Common;
use Piwik\Config as PiwikConfig;
use Piwik\Config;
+use Piwik\Db;
+use Piwik\Settings\Storage as SettingsStorage;
use Piwik\Container\StaticContainer;
use Piwik\EventDispatcher;
use Piwik\Filesystem;
@@ -398,7 +401,8 @@ class Manager
}
$this->loadAllPluginsAndGetTheirInfo();
- \Piwik\Settings\Manager::cleanupPluginSettings($pluginName);
+ SettingsStorage\Backend\PluginSettingsTable::removeAllSettingsForPlugin($pluginName);
+ SettingsStorage\Backend\MeasurableSettingsTable::removeAllSettingsForPlugin($pluginName);
$this->executePluginDeactivate($pluginName);
$this->executePluginUninstall($pluginName);
diff --git a/core/Plugin/Menu.php b/core/Plugin/Menu.php
index 384c3a199a..99275a5aeb 100644
--- a/core/Plugin/Menu.php
+++ b/core/Plugin/Menu.php
@@ -236,7 +236,7 @@ class Menu
}
$reportAction = lcfirst(substr($action, 4));
- if (Reports::factory($module, $reportAction)) {
+ if (ReportsProvider::factory($module, $reportAction)) {
return;
}
diff --git a/core/Plugin/Report.php b/core/Plugin/Report.php
index c98d38e849..38e4857498 100644
--- a/core/Plugin/Report.php
+++ b/core/Plugin/Report.php
@@ -20,7 +20,7 @@ use Piwik\Cache as PiwikCache;
use Piwik\Piwik;
use Piwik\Plugins\CoreVisualizations\Visualizations\HtmlTable;
use Piwik\Plugins\CoreVisualizations\Visualizations\JqplotGraph\Evolution;
-use Piwik\Plugin\Reports;
+use Piwik\Plugin\ReportsProvider;
use Piwik\ViewDataTable\Factory as ViewDataTableFactory;
use Exception;
use Piwik\Widget\WidgetsList;
@@ -699,7 +699,7 @@ class Report
list($subtableReportModule, $subtableReportAction) = $this->getSubtableApiMethod();
- $subtableReport = Reports::factory($subtableReportModule, $subtableReportAction);
+ $subtableReport = ReportsProvider::factory($subtableReportModule, $subtableReportAction);
if (empty($subtableReport)) {
return null;
}
diff --git a/core/Plugin/Reports.php b/core/Plugin/ReportsProvider.php
index 56d69e5453..021ca891fe 100644
--- a/core/Plugin/Reports.php
+++ b/core/Plugin/ReportsProvider.php
@@ -16,7 +16,7 @@ use Piwik\Cache as PiwikCache;
/**
* Get reports that are defined by plugins.
*/
-class Reports
+class ReportsProvider
{
/**
diff --git a/core/Plugin/Settings.php b/core/Plugin/Settings.php
deleted file mode 100644
index c26581e4b1..0000000000
--- a/core/Plugin/Settings.php
+++ /dev/null
@@ -1,308 +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\Plugin;
-
-use Piwik\Piwik;
-use Piwik\Settings\Setting;
-use Piwik\Settings\Storage;
-use Piwik\Settings\StorageInterface;
-use Piwik\Tracker\SettingsStorage;
-
-/**
- * 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 the {@link Piwik\Plugins\ExampleSettingsPlugin\ExampleSettingsPlugin} plugin.
- *
- * @api
- */
-abstract class Settings
-{
- const TYPE_INT = 'integer';
- const TYPE_FLOAT = 'float';
- const TYPE_STRING = 'string';
- const TYPE_BOOL = 'boolean';
- const TYPE_ARRAY = 'array';
-
- const CONTROL_RADIO = 'radio';
- const CONTROL_TEXT = 'text';
- const CONTROL_TEXTAREA = 'textarea';
- const CONTROL_CHECKBOX = 'checkbox';
- const CONTROL_PASSWORD = 'password';
- const CONTROL_MULTI_SELECT = 'multiselect';
- const CONTROL_SINGLE_SELECT = 'select';
-
- /**
- * An array containing all available settings: Array ( [setting-name] => [setting] )
- *
- * @var Settings[]
- */
- private $settings = array();
-
- private $introduction;
- protected $pluginName;
-
- /**
- * @var StorageInterface
- */
- protected $storage;
-
- /**
- * Constructor.
- */
- public function __construct($pluginName = null)
- {
- if (!empty($pluginName)) {
- $this->pluginName = $pluginName;
- } else {
- $classname = get_class($this);
- $parts = explode('\\', $classname);
-
- if (3 <= count($parts)) {
- $this->pluginName = $parts[2];
- }
- }
-
- $this->storage = Storage\Factory::make($this->pluginName);
-
- $this->init();
- }
-
- /**
- * @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();
-
- /**
- * Sets the text used to introduce this plugin's settings in the _Plugin Settings_ page.
- *
- * @param string $introduction
- */
- protected function setIntroduction($introduction)
- {
- $this->introduction = $introduction;
- }
-
- /**
- * Returns the introduction text for this plugin's settings.
- *
- * @return string
- */
- public function getIntroduction()
- {
- return $this->introduction;
- }
-
- /**
- * Returns the settings that can be displayed for the current user.
- *
- * @return Setting[]
- */
- public function getSettingsForCurrentUser()
- {
- $settings = array_filter($this->getSettings(), function (Setting $setting) {
- return $setting->isWritableByCurrentUser();
- });
-
- $settings2 = $settings;
-
- uasort($settings, function ($setting1, $setting2) use ($settings2) {
-
- /** @var Setting $setting1 */ /** @var Setting $setting2 */
- if ($setting1->getOrder() == $setting2->getOrder()) {
- // preserve order for settings having same order
- foreach ($settings2 as $setting) {
- if ($setting1 === $setting) {
- return -1;
- }
- if ($setting2 === $setting) {
- return 1;
- }
- }
-
- return 0;
- }
-
- return $setting1->getOrder() > $setting2->getOrder() ? -1 : 1;
- });
-
- return $settings;
- }
-
- /**
- * Returns all available settings. This will include settings that are not available
- * to the current user (such as settings available only to the Super User).
- *
- * @return Setting[]
- */
- public function getSettings()
- {
- return $this->settings;
- }
-
- /**
- * 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 (!ctype_alnum(str_replace('_', '', $name))) {
- $msg = sprintf('The setting name "%s" in plugin "%s" is not valid. Only underscores, alpha and numerical characters are allowed', $setting->getName(), $this->pluginName);
- throw new \Exception($msg);
- }
-
- if (array_key_exists($name, $this->settings)) {
- throw new \Exception(sprintf('A setting with name "%s" does already exist for plugin "%s"', $setting->getName(), $this->pluginName));
- }
-
- $this->setDefaultTypeAndFieldIfNeeded($setting);
- $this->addValidatorIfNeeded($setting);
-
- $setting->setStorage($this->storage);
- $setting->setPluginName($this->pluginName);
-
- $this->settings[$name] = $setting;
- }
-
- /**
- * Saves (persists) the current setting values in the database.
- */
- public function save()
- {
- $this->storage->save();
-
- SettingsStorage::clearCache();
-
- /**
- * Triggered after a plugin settings have been updated.
- *
- * **Example**
- *
- * Piwik::addAction('Settings.MyPlugin.settingsUpdated', function (Settings $settings) {
- * $value = $settings->someSetting->getValue();
- * // Do something with the new setting value
- * });
- *
- * @param Settings $settings The plugin settings object.
- */
- Piwik::postEvent(sprintf('Settings.%s.settingsUpdated', $this->pluginName), array($this));
- }
-
- /**
- * Removes all settings for this plugin from the database. Useful when uninstalling
- * a plugin.
- */
- public function removeAllPluginSettings()
- {
- Piwik::checkUserHasSuperUserAccess();
-
- $this->storage->deleteAllValues();
-
- SettingsStorage::clearCache();
- }
-
- private function getDefaultType($controlType)
- {
- $defaultTypes = array(
- static::CONTROL_TEXT => static::TYPE_STRING,
- static::CONTROL_TEXTAREA => static::TYPE_STRING,
- static::CONTROL_PASSWORD => static::TYPE_STRING,
- static::CONTROL_CHECKBOX => static::TYPE_BOOL,
- static::CONTROL_MULTI_SELECT => static::TYPE_ARRAY,
- static::CONTROL_RADIO => static::TYPE_STRING,
- static::CONTROL_SINGLE_SELECT => static::TYPE_STRING,
- );
-
- return $defaultTypes[$controlType];
- }
-
- private function getDefaultCONTROL($type)
- {
- $defaultControlTypes = array(
- static::TYPE_INT => static::CONTROL_TEXT,
- static::TYPE_FLOAT => static::CONTROL_TEXT,
- static::TYPE_STRING => static::CONTROL_TEXT,
- static::TYPE_BOOL => static::CONTROL_CHECKBOX,
- static::TYPE_ARRAY => static::CONTROL_MULTI_SELECT,
- );
-
- return $defaultControlTypes[$type];
- }
-
- private function setDefaultTypeAndFieldIfNeeded(Setting $setting)
- {
- $hasControl = !is_null($setting->uiControlType);
- $hasType = !is_null($setting->type);
-
- if ($hasControl && !$hasType) {
- $setting->type = $this->getDefaultType($setting->uiControlType);
- } elseif ($hasType && !$hasControl) {
- $setting->uiControlType = $this->getDefaultCONTROL($setting->type);
- } elseif (!$hasControl && !$hasType) {
- $setting->type = static::TYPE_STRING;
- $setting->uiControlType = static::CONTROL_TEXT;
- }
- }
-
- private function addValidatorIfNeeded(Setting $setting)
- {
- if (!is_null($setting->validate) || is_null($setting->availableValues)) {
- return;
- }
-
- $pluginName = $this->pluginName;
-
- $setting->validate = function ($value) use ($setting, $pluginName) {
-
- $errorMsg = Piwik::translate('CoreAdminHome_PluginSettingsValueNotAllowed',
- array($setting->title, $pluginName));
-
- if (is_array($value) && $setting->type == Settings::TYPE_ARRAY) {
- foreach ($value as $val) {
- if (!array_key_exists($val, $setting->availableValues)) {
- throw new \Exception($errorMsg);
- }
- }
- } else {
- if (!array_key_exists($value, $setting->availableValues)) {
- throw new \Exception($errorMsg);
- }
- }
- };
- }
-}
diff --git a/core/Plugin/SettingsProvider.php b/core/Plugin/SettingsProvider.php
new file mode 100644
index 0000000000..5cc05bd32c
--- /dev/null
+++ b/core/Plugin/SettingsProvider.php
@@ -0,0 +1,215 @@
+<?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\Plugin;
+
+use Piwik\CacheId;
+use Piwik\Container\StaticContainer;
+use Piwik\Plugin;
+use Piwik\Cache as PiwikCache;
+use Piwik\Settings\Measurable\MeasurableSettings;
+use \Piwik\Settings\Plugin\UserSettings;
+use \Piwik\Settings\Plugin\SystemSettings;
+
+/**
+ * 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 the {@link Piwik\Plugins\ExampleSettingsPlugin\ExampleSettingsPlugin} plugin.
+ */
+class SettingsProvider
+{
+ /**
+ * @var Plugin\Manager
+ */
+ private $pluginManager;
+
+ public function __construct(Plugin\Manager $pluginManager)
+ {
+ $this->pluginManager = $pluginManager;
+ }
+
+ /**
+ *
+ * Get user settings implemented by a specific plugin (if implemented by this plugin).
+ * @param string $pluginName
+ * @return SystemSettings|null
+ */
+ public function getSystemSettings($pluginName)
+ {
+ $plugin = $this->getLoadedAndActivated($pluginName);
+
+ if ($plugin) {
+ $settings = $plugin->findComponent('SystemSettings', 'Piwik\\Settings\\Plugin\\SystemSettings');
+
+ if ($settings) {
+ return StaticContainer::get($settings);
+ }
+ }
+ }
+
+ /**
+ * Get user settings implemented by a specific plugin (if implemented by this plugin).
+ * @param string $pluginName
+ * @return UserSettings|null
+ */
+ public function getUserSettings($pluginName)
+ {
+ $plugin = $this->getLoadedAndActivated($pluginName);
+
+ if ($plugin) {
+ $settings = $plugin->findComponent('UserSettings', 'Piwik\\Settings\\Plugin\\UserSettings');
+
+ if ($settings) {
+ return StaticContainer::get($settings);
+ }
+ }
+ }
+
+ /**
+ * Returns all available system settings. A plugin has to specify a file named `SystemSettings.php` containing a
+ * class named `SystemSettings` that extends `Piwik\Settings\Plugin\SystemSettings` in order to be considered as
+ * a system setting. Otherwise the settings for a plugin won't be available.
+ *
+ * @return SystemSettings[] An array containing array([pluginName] => [setting instance]).
+ */
+ public function getAllSystemSettings()
+ {
+ $cacheId = CacheId::languageAware('AllSystemSettings');
+ $cache = PiwikCache::getTransientCache();
+
+ if (!$cache->contains($cacheId)) {
+ $pluginNames = $this->pluginManager->getActivatedPlugins();
+ $byPluginName = array();
+
+ foreach ($pluginNames as $plugin) {
+ $component = $this->getSystemSettings($plugin);
+
+ if (!empty($component)) {
+ $byPluginName[$plugin] = $component;
+ }
+ }
+
+ $cache->save($cacheId, $byPluginName);
+ }
+
+ return $cache->fetch($cacheId);
+ }
+
+ /**
+ * Returns all available user settings. A plugin has to specify a file named `UserSettings.php` containing a class
+ * named `UserSettings` that extends `Piwik\Settings\Plugin\UserSettings` in order to be considered as a plugin
+ * setting. Otherwise the settings for a plugin won't be available.
+ *
+ * @return UserSettings[] An array containing array([pluginName] => [setting instance]).
+ */
+ public function getAllUserSettings()
+ {
+ $cacheId = CacheId::languageAware('AllUserSettings');
+ $cache = PiwikCache::getTransientCache();
+
+ if (!$cache->contains($cacheId)) {
+ $pluginNames = $this->pluginManager->getActivatedPlugins();
+ $byPluginName = array();
+
+ foreach ($pluginNames as $plugin) {
+ $component = $this->getUserSettings($plugin);
+
+ if (!empty($component)) {
+ $byPluginName[$plugin] = $component;
+ }
+ }
+
+ $cache->save($cacheId, $byPluginName);
+ }
+
+ return $cache->fetch($cacheId);
+ }
+
+ /**
+ * @api
+ *
+ * Get measurable settings for a specific plugin.
+ *
+ * @param string $pluginName The name of a plugin.
+ * @param int $idSite The ID of a site. If a site is about to be created pass idSite = 0.
+ * @param string|null $idType If null, idType will be detected automatically if the site already exists. Only
+ * needed to set a value when idSite = 0 (this is the case when a site is about)
+ * to be created.
+ *
+ * @return MeasurableSettings|null Returns null if no MeasurableSettings implemented by this plugin or when plugin
+ * is not loaded and activated. Returns an instance of the settings otherwise.
+ */
+ public function getMeasurableSettings($pluginName, $idSite, $idType = null)
+ {
+ $plugin = $this->getLoadedAndActivated($pluginName);
+
+ if ($plugin) {
+ $component = $plugin->findComponent('MeasurableSettings', 'Piwik\\Settings\\Measurable\\MeasurableSettings');
+
+ if ($component) {
+ return StaticContainer::getContainer()->make($component, array(
+ 'idSite' => $idSite,
+ 'idMeasurableType' => $idType
+ ));
+ }
+ }
+ }
+
+ /**
+ * @api
+ *
+ * Get all available measurable settings implemented by loaded and activated plugins.
+ *
+ * @param int $idSite The ID of a site. If a site is about to be created pass idSite = 0.
+ * @param string|null $idMeasurableType If null, idType will be detected automatically if the site already exists.
+ * Only needed to set a value when idSite = 0 (this is the case when a site
+ * is about) to be created.
+ *
+ * @return MeasurableSettings[]
+ */
+ public function getAllMeasurableSettings($idSite, $idMeasurableType = null)
+ {
+ $pluginNames = $this->pluginManager->getActivatedPlugins();
+ $byPluginName = array();
+
+ foreach ($pluginNames as $plugin) {
+ $component = $this->getMeasurableSettings($plugin, $idSite, $idMeasurableType);
+
+ if (!empty($component)) {
+ $byPluginName[$plugin] = $component;
+ }
+ }
+
+ return $byPluginName;
+ }
+
+ private function getLoadedAndActivated($pluginName)
+ {
+ if (!$this->pluginManager->isPluginLoaded($pluginName)) {
+ return;
+ }
+
+ try {
+ if (!$this->pluginManager->isPluginActivated($pluginName)) {
+ return;
+ }
+
+ $plugin = $this->pluginManager->getLoadedPlugin($pluginName);
+ } catch (\Exception $e) {
+ // we are not allowed to use possible settings from this plugin, plugin is not active
+ return;
+ }
+
+ return $plugin;
+ }
+
+}
diff --git a/core/Plugin/ViewDataTable.php b/core/Plugin/ViewDataTable.php
index 5ca7e2e9ba..3b7bcf4ff8 100644
--- a/core/Plugin/ViewDataTable.php
+++ b/core/Plugin/ViewDataTable.php
@@ -13,7 +13,7 @@ use Piwik\Common;
use Piwik\DataTable;
use Piwik\Period;
use Piwik\Piwik;
-use Piwik\Plugin\Reports;
+use Piwik\Plugin\ReportsProvider;
use Piwik\View;
use Piwik\View\ViewInterface;
use Piwik\ViewDataTable\Config as VizConfig;
@@ -192,7 +192,7 @@ abstract class ViewDataTable implements ViewInterface
$this->requestConfig->apiMethodToRequestDataTable = $apiMethodToRequestDataTable;
- $report = Reports::factory($this->requestConfig->getApiModuleToRequest(), $this->requestConfig->getApiMethodToRequest());
+ $report = ReportsProvider::factory($this->requestConfig->getApiModuleToRequest(), $this->requestConfig->getApiMethodToRequest());
if (!empty($report)) {
/** @var Report $report */
diff --git a/core/Plugin/Visualization.php b/core/Plugin/Visualization.php
index da2c60c18e..e19da81d9a 100644
--- a/core/Plugin/Visualization.php
+++ b/core/Plugin/Visualization.php
@@ -23,7 +23,7 @@ use Piwik\Period;
use Piwik\Piwik;
use Piwik\Plugins\API\API as ApiApi;
use Piwik\Plugins\PrivacyManager\PrivacyManager;
-use Piwik\Plugin\Reports;
+use Piwik\Plugin\ReportsProvider;
use Piwik\View;
use Piwik\ViewDataTable\Manager as ViewDataTableManager;
use Piwik\Plugin\Manager as PluginManager;
@@ -169,7 +169,7 @@ class Visualization extends ViewDataTable
parent::__construct($controllerAction, $apiMethodToRequestDataTable, $params);
- $this->report = Reports::factory($this->requestConfig->getApiModuleToRequest(), $this->requestConfig->getApiMethodToRequest());
+ $this->report = ReportsProvider::factory($this->requestConfig->getApiModuleToRequest(), $this->requestConfig->getApiMethodToRequest());
}
public function render()
diff --git a/core/Plugin/Widgets.php b/core/Plugin/WidgetsProvider.php
index e67f2bce61..8fe015411b 100644
--- a/core/Plugin/Widgets.php
+++ b/core/Plugin/WidgetsProvider.php
@@ -19,7 +19,7 @@ use Piwik\Widget\WidgetContainerConfig;
/**
* Get widgets that are defined by plugins.
*/
-class Widgets
+class WidgetsProvider
{
/**
* @var Plugin\Manager
diff --git a/core/Scheduler/TaskLoader.php b/core/Scheduler/TaskLoader.php
index 60b9e328b6..95c3394cfb 100644
--- a/core/Scheduler/TaskLoader.php
+++ b/core/Scheduler/TaskLoader.php
@@ -8,6 +8,7 @@
namespace Piwik\Scheduler;
+use Piwik\Container\StaticContainer;
use Piwik\Plugin\Manager as PluginManager;
use Piwik\Plugin\Tasks;
@@ -27,6 +28,7 @@ class TaskLoader
$pluginTasks = PluginManager::getInstance()->findComponents('Tasks', 'Piwik\Plugin\Tasks');
foreach ($pluginTasks as $pluginTask) {
+ $pluginTask = StaticContainer::get($pluginTask);
$pluginTask->schedule();
foreach ($pluginTask->getScheduledTasks() as $task) {
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();
- }
- }
-}
diff --git a/core/Site.php b/core/Site.php
index 3bc535fc2d..a45a4a5e2e 100644
--- a/core/Site.php
+++ b/core/Site.php
@@ -419,6 +419,17 @@ class Site
}
/**
+ * Clears the site data cache.
+ *
+ * See also {@link setSites()} and {@link setSitesFromArray()}.
+ */
+ public static function clearCacheForSite($idSite)
+ {
+ $idSite = (int)$idSite;
+ unset(self::$infoSites[$idSite]);
+ }
+
+ /**
* Utility function. Returns the value of the specified field for the
* site with the specified ID.
*
diff --git a/core/Tracker/SettingsStorage.php b/core/Tracker/SettingsStorage.php
deleted file mode 100644
index a69e03553f..0000000000
--- a/core/Tracker/SettingsStorage.php
+++ /dev/null
@@ -1,58 +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\Tracker;
-
-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.
- */
-class SettingsStorage extends Storage
-{
- protected function loadSettings()
- {
- $cacheId = $this->getOptionKey();
- $cache = $this->getCache();
-
- if ($cache->contains($cacheId)) {
- $settings = $cache->fetch($cacheId);
- } else {
- $settings = parent::loadSettings();
-
- $cache->save($cacheId, $settings);
- }
-
- return $settings;
- }
-
- public function save()
- {
- parent::save();
- self::clearCache();
- }
-
- private function getCache()
- {
- return self::buildCache($this->getOptionKey());
- }
-
- public static function clearCache()
- {
- Cache::deleteTrackerCache();
- self::buildCache()->flushAll();
- }
-
- private static function buildCache()
- {
- return PiwikCache::getEagerCache();
- }
-}
diff --git a/core/Updates/3.0.0-b1.php b/core/Updates/3.0.0-b1.php
index 301bc44a1b..b7fbd2d027 100644
--- a/core/Updates/3.0.0-b1.php
+++ b/core/Updates/3.0.0-b1.php
@@ -11,6 +11,7 @@ namespace Piwik\Updates;
use Piwik\Common;
use Piwik\Db;
+use Piwik\DbHelper;
use Piwik\Updater;
use Piwik\Updates;
use Piwik\Plugins\Dashboard;
@@ -30,7 +31,11 @@ class Updates_3_0_0_b1 extends Updates
$allGoals = $db->fetchAll(sprintf("SELECT DISTINCT idgoal FROM %s", Common::prefixTable('goal')));
$allDashboards = $db->fetchAll(sprintf("SELECT * FROM %s", Common::prefixTable('user_dashboard')));
- return $this->getDashboardMigrationSqls($allDashboards, $allGoals);
+ $queries = $this->getDashboardMigrationSqls($allDashboards, $allGoals);
+ $queries = $this->getPluginSettingsMigrationQueries($queries, $db);
+ $queries = $this->getSiteSettingsMigrationQueries($queries);
+
+ return $queries;
}
public function doUpdate(Updater $updater)
@@ -38,11 +43,104 @@ class Updates_3_0_0_b1 extends Updates
$updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater));
}
+ /**
+ * @param $queries
+ * @param Db $db
+ * @return mixed
+ */
+ private function getPluginSettingsMigrationQueries($queries, $db)
+ {
+ $pluginSettingsTableName = $this->getPluginSettingsTableName();
+ $dbSettings = new Db\Settings();
+ $engine = $dbSettings->getEngine();
+
+ $pluginSettingsTable = "CREATE TABLE $pluginSettingsTableName (
+ `plugin_name` VARCHAR(60) NOT NULL,
+ `setting_name` VARCHAR(255) NOT NULL,
+ `setting_value` LONGTEXT NOT NULL,
+ `user_login` VARCHAR(100) NOT NULL DEFAULT '',
+ INDEX(plugin_name, user_login)
+ ) ENGINE=$engine DEFAULT CHARSET=utf8
+ ";
+ $queries[$pluginSettingsTable] = 1050;
+
+ $optionTable = Common::prefixTable('option');
+ $query = 'SELECT `option_name`, `option_value` FROM `' . $optionTable . '` WHERE `option_name` LIKE "Plugin_%_Settings"';
+ $options = Db::get()->fetchAll($query);
+
+ foreach ($options as $option) {
+ $name = $option['option_name'];
+ $pluginName = str_replace(array('Plugin_', '_Settings'), '', $name);
+ $values = @unserialize($option['option_value']);
+
+ if (empty($values)) {
+ continue;
+ }
+
+ foreach ($values as $settingName => $settingValue) {
+ if (!is_array($settingValue)) {
+ $settingValue = array($settingValue);
+ }
+
+ foreach ($settingValue as $val) {
+ $queries[$this->createPluginSettingQuery($pluginName, $settingName, $val)] = 1062;
+ }
+ }
+ }
+
+ $queries[$query = sprintf('DELETE FROM `%s` WHERE `option_name` like "Plugin_%%_Settings"', $optionTable)] = false;
+
+ return $queries;
+ }
+
+ /**
+ * @param $queries
+ * @param Db $db
+ * @return mixed
+ */
+ private function getSiteSettingsMigrationQueries($queries)
+ {
+ $table = Common::prefixTable('site_setting');
+
+ $pluginSettingsTable = "ALTER TABLE $table ADD COLUMN `plugin_name` VARCHAR(60) NOT NULL AFTER `idsite`";
+ $queries[$pluginSettingsTable] = 1060;
+ $queries["ALTER TABLE $table DROP PRIMARY KEY, ADD INDEX(idsite, plugin_name);"] = false;
+
+ // we cannot migrate existing settings as we do not know the related plugin name, but this feature
+ // (measurablesettings) was not really used anyway. If a migration is somewhere really needed it has to be
+ // handled in the plugin
+ $queries[sprintf('DELETE FROM `%s`', $table)] = false;
+
+ return $queries;
+ }
+
+ private function createPluginSettingQuery($pluginName, $settingName, $settingValue)
+ {
+ $table = $this->getPluginSettingsTableName();
+
+ $login = '';
+ if (preg_match('/^.+#(.+)#$/', $settingName, $matches)) {
+ $login = $matches[1];
+ $settingName = str_replace('#' . $login . '#', '', $settingName);
+ }
+
+ $db = Db::get();
+
+ $query = sprintf("INSERT INTO %s (plugin_name, setting_name, setting_value, user_login) VALUES ", $table);
+ $query .= sprintf("(%s, %s, %s, %s)", $db->quote($pluginName), $db->quote($settingName), $db->quote($settingValue), $db->quote($login));
+
+ return $query;
+ }
+
+ private function getPluginSettingsTableName()
+ {
+ return Common::prefixTable('plugin_setting');
+ }
+
private function getDashboardMigrationSqls($allDashboards, $allGoals)
{
$sqls = array();
-
// update dashboard to use new widgets
$oldWidgets = array(
array (
diff --git a/core/ViewDataTable/Config.php b/core/ViewDataTable/Config.php
index f275aebe58..14a6e13ed5 100644
--- a/core/ViewDataTable/Config.php
+++ b/core/ViewDataTable/Config.php
@@ -15,7 +15,7 @@ use Piwik\DataTable;
use Piwik\DataTable\Filter\PivotByDimension;
use Piwik\Metrics;
use Piwik\Plugins\API\API;
-use Piwik\Plugin\Reports;
+use Piwik\Plugin\ReportsProvider;
/**
* Contains base display properties for {@link Piwik\Plugin\ViewDataTable}s. Manipulating these
@@ -717,7 +717,7 @@ class Config
private function setShouldShowPivotBySubtable()
{
- $report = Reports::factory($this->controllerName, $this->controllerAction);
+ $report = ReportsProvider::factory($this->controllerName, $this->controllerAction);
if (empty($report)) {
$this->show_pivot_by_subtable = false;
diff --git a/core/ViewDataTable/Factory.php b/core/ViewDataTable/Factory.php
index 543f1280cf..846de225c2 100644
--- a/core/ViewDataTable/Factory.php
+++ b/core/ViewDataTable/Factory.php
@@ -12,7 +12,7 @@ use Piwik\Common;
use Piwik\Piwik;
use Piwik\Plugin\Report;
use Piwik\Plugins\CoreVisualizations\Visualizations\HtmlTable;
-use Piwik\Plugin\Reports;
+use Piwik\Plugin\ReportsProvider;
/**
* Provides a means of creating {@link Piwik\Plugin\ViewDataTable} instances by ID.
@@ -168,7 +168,7 @@ class Factory
private static function getReport($apiAction)
{
list($module, $action) = explode('.', $apiAction);
- $report = Reports::factory($module, $action);
+ $report = ReportsProvider::factory($module, $action);
return $report;
}
diff --git a/core/Widget/WidgetsList.php b/core/Widget/WidgetsList.php
index dfeb004277..14c626b0a9 100644
--- a/core/Widget/WidgetsList.php
+++ b/core/Widget/WidgetsList.php
@@ -175,7 +175,7 @@ class WidgetsList
{
$list = new static;
- $widgets = StaticContainer::get('Piwik\Plugin\Widgets');
+ $widgets = StaticContainer::get('Piwik\Plugin\WidgetsProvider');
$widgetContainerConfigs = $widgets->getWidgetContainerConfigs();
foreach ($widgetContainerConfigs as $config) {
@@ -191,7 +191,7 @@ class WidgetsList
}
}
- $reports = StaticContainer::get('Piwik\Plugin\Reports');
+ $reports = StaticContainer::get('Piwik\Plugin\ReportsProvider');
$reports = $reports->getAllReports();
foreach ($reports as $report) {
if ($report->isEnabled()) {