PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / trunk
Matomo Analytics – Powerful, Privacy-First Insights for WordPress vtrunk
5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / core / Settings / Storage / Backend / PluginSettingsTable.php
matomo / app / core / Settings / Storage / Backend Last commit date
BackendInterface.php 1 year ago BaseSettingsTable.php 2 years ago Cache.php 2 years ago Config.php 2 years ago MeasurableSettingsTable.php 6 months ago NullBackend.php 2 years ago PluginSettingsTable.php 1 month ago SitesTable.php 1 month ago
PluginSettingsTable.php
330 lines
1 <?php
2
3 /**
4 * Matomo - free/libre analytics platform
5 *
6 * @link https://matomo.org
7 * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8 */
9 namespace Piwik\Settings\Storage\Backend;
10
11 use Piwik\Common;
12 use Piwik\Db;
13 use Exception;
14 /**
15 * Plugin settings backend. Stores all settings in a "plugin_setting" database table.
16 *
17 * If a value that needs to be stored is an array, will insert a new row for each value of this array.
18 */
19 class PluginSettingsTable extends \Piwik\Settings\Storage\Backend\BaseSettingsTable
20 {
21 /**
22 * @var string
23 */
24 private $pluginName;
25 /**
26 * @var string
27 */
28 private $userLogin;
29 public function __construct($pluginName, $userLogin)
30 {
31 parent::__construct();
32 if (empty($pluginName)) {
33 throw new Exception('No plugin name given for PluginSettingsTable backend');
34 }
35 if ($userLogin === \false || $userLogin === null) {
36 throw new Exception('Invalid user login name given for PluginSettingsTable backend');
37 }
38 $this->pluginName = $pluginName;
39 $this->userLogin = $userLogin;
40 }
41 /**
42 * @inheritdoc
43 */
44 public function getStorageId()
45 {
46 return 'PluginSettings_' . $this->pluginName . '_User_' . $this->userLogin;
47 }
48 /**
49 * Saves (persists) the current setting values in the database.
50 * @param array $values Key/value pairs of setting values to be written
51 */
52 public function save($values)
53 {
54 $this->initDbIfNeeded();
55 $valuesKeep = array();
56 foreach ($values as $name => $value) {
57 if (!isset($value)) {
58 continue;
59 }
60 if (is_array($value) || is_object($value)) {
61 $jsonEncoded = 1;
62 $value = json_encode($value);
63 } else {
64 $jsonEncoded = 0;
65 if (is_bool($value)) {
66 // we are currently not storing booleans as json as it could result in trouble with the UI and regress
67 // preselecting the correct value
68 $value = (int) $value;
69 }
70 }
71 $valuesKeep[] = array($this->pluginName, $this->userLogin, $name, $value, $jsonEncoded);
72 }
73 $columns = self::getColumns();
74 $table = $this->getTableName();
75 $lockKey = $this->getStorageId();
76 $this->lock->execute($lockKey, function () use($valuesKeep, $table, $columns) {
77 $this->delete();
78 // No values = nothing to save
79 if (!empty($valuesKeep)) {
80 Db\BatchInsert::tableInsertBatchSql($table, $columns, $valuesKeep);
81 }
82 });
83 }
84 /**
85 * Save exactly one setting key atomically for the current plugin/user context
86 *
87 * if the value is null, the key is deleted.
88 *
89 * @throws Exception when param $settingName is empty
90 */
91 /**
92 * @param mixed $value
93 */
94 public function saveValue(string $settingName, $value) : void
95 {
96 $this->initDbIfNeeded();
97 if (empty($settingName)) {
98 throw new Exception('No setting name given for PluginSettingsTable backend');
99 }
100 $table = $this->getTableName();
101 $lockKey = $this->getStorageId();
102 $columns = self::getColumns();
103 $this->lock->execute($lockKey, function () use($table, $columns, $settingName, $value) {
104 $this->deleteValueWithoutLock($settingName);
105 if (is_null($value)) {
106 return;
107 }
108 [$storedValue, $jsonEncoded] = $this->encodeValueForStorage($value);
109 Db\BatchInsert::tableInsertBatchSql($table, $columns, [[$this->pluginName, $this->userLogin, $settingName, $storedValue, $jsonEncoded]]);
110 });
111 }
112 /**
113 * @throws Exception when param $settingName is empty
114 */
115 /**
116 * @param mixed $defaultValue
117 * @return mixed
118 */
119 public function loadValue(string $settingName, $defaultValue = null)
120 {
121 $this->initDbIfNeeded();
122 if (empty($settingName)) {
123 throw new Exception('No setting name given for PluginSettingsTable backend');
124 }
125 $table = $this->getTableName();
126 $sql = "SELECT `setting_name`, `setting_value`, `json_encoded`\n FROM `{$table}`\n WHERE plugin_name = ? and user_login = ? and setting_name = ?";
127 $bind = [$this->pluginName, $this->userLogin, $settingName];
128 try {
129 $rows = $this->db->fetchAll($sql, $bind);
130 } catch (\Exception $e) {
131 if ($this->jsonEncodedMissingError($e)) {
132 $sql = "SELECT `setting_name`, `setting_value`\n FROM `{$table}`\n WHERE plugin_name = ? and user_login = ? and setting_name = ?";
133 $rows = $this->db->fetchAll($sql, $bind);
134 } else {
135 throw $e;
136 }
137 }
138 if (empty($rows)) {
139 return $defaultValue;
140 }
141 $flat = $this->flattenSettingsRows($rows);
142 return array_key_exists($settingName, $flat) ? $flat[$settingName] : $defaultValue;
143 }
144 /**
145 * @throws Exception when param $settingName is empty
146 */
147 public function deleteValue(string $settingName) : void
148 {
149 $this->initDbIfNeeded();
150 if (empty($settingName)) {
151 throw new Exception('No setting name given for PluginSettingsTable backend');
152 }
153 $lockKey = $this->getStorageId();
154 $this->lock->execute($lockKey, function () use($settingName) {
155 $this->deleteValueWithoutLock($settingName);
156 });
157 }
158 private function jsonEncodedMissingError(Exception $e)
159 {
160 return strpos($e->getMessage(), 'json_encoded') !== \false;
161 }
162 public function load()
163 {
164 $this->initDbIfNeeded();
165 $sql = "SELECT `setting_name`, `setting_value`, `json_encoded` FROM `" . $this->getTableName() . "` WHERE plugin_name = ? and user_login = ?";
166 $bind = array($this->pluginName, $this->userLogin);
167 try {
168 $settings = $this->db->fetchAll($sql, $bind);
169 } catch (\Exception $e) {
170 // we catch an exception since json_encoded might not be present before matomo is updated to 3.5.0+ but the updater
171 // may run this query
172 if ($this->jsonEncodedMissingError($e)) {
173 $sql = "SELECT `setting_name`, `setting_value` FROM `" . $this->getTableName() . "` WHERE plugin_name = ? and user_login = ?";
174 $settings = $this->db->fetchAll($sql, $bind);
175 } else {
176 throw $e;
177 }
178 }
179 return $this->flattenSettingsRows($settings);
180 }
181 /**
182 * Returns selected setting names for all users in one plugin
183 *
184 * @param string[] $settingNames
185 * @return array<string, array<string, mixed>>
186 */
187 public function loadValuesForUsers(array $settingNames) : array
188 {
189 $this->initDbIfNeeded();
190 if (empty($settingNames)) {
191 return [];
192 }
193 $table = $this->getTableName();
194 $placeholders = Common::getSqlStringFieldsArray($settingNames);
195 $sql = "SELECT user_login, setting_name, setting_value, json_encoded\n FROM `{$table}`\n WHERE plugin_name = ?\n AND setting_name IN ({$placeholders})\n AND user_login <> ''";
196 $bind = array_merge([$this->pluginName], $settingNames);
197 try {
198 $rows = $this->db->fetchAll($sql, $bind);
199 } catch (\Exception $e) {
200 if ($this->jsonEncodedMissingError($e)) {
201 $sql = "SELECT user_login, setting_name, setting_value\n FROM `{$table}`\n WHERE plugin_name = ?\n AND setting_name IN ({$placeholders})\n AND user_login <> ''";
202 $rows = $this->db->fetchAll($sql, $bind);
203 } else {
204 throw $e;
205 }
206 }
207 $valuesByUser = [];
208 foreach ($rows as $row) {
209 $jsonEncoded = isset($row['json_encoded']) ? (bool) $row['json_encoded'] : \false;
210 $value = $this->decodeValueFromStorage($row['setting_value'], $jsonEncoded);
211 $valuesByUser[$row['user_login']][$row['setting_name']] = $value;
212 }
213 return $valuesByUser;
214 }
215 protected function getTableName()
216 {
217 return Common::prefixTable('plugin_setting');
218 }
219 public function delete()
220 {
221 $this->initDbIfNeeded();
222 $table = $this->getTableName();
223 $sql = "DELETE FROM `{$table}` WHERE `plugin_name` = ? and `user_login` = ?";
224 $bind = array($this->pluginName, $this->userLogin);
225 $this->db->query($sql, $bind);
226 }
227 private function deleteValueWithoutLock(string $settingName) : void
228 {
229 $this->initDbIfNeeded();
230 $table = $this->getTableName();
231 $sql = "DELETE FROM `{$table}`\n WHERE `plugin_name` = ? and `user_login` = ? and `setting_name` = ?";
232 $bind = [$this->pluginName, $this->userLogin, $settingName];
233 $this->db->query($sql, $bind);
234 }
235 /**
236 * Unsets all settings for a user. The settings will be removed from the database. Used when
237 * a user is deleted.
238 *
239 * @internal
240 * @param string $userLogin
241 * @throws \Exception If the `$userLogin` is empty. Otherwise we would delete most plugin settings
242 */
243 public static function removeAllUserSettingsForUser($userLogin)
244 {
245 if (empty($userLogin)) {
246 throw new Exception('No userLogin specified. Cannot remove all settings for this user');
247 }
248 try {
249 $table = Common::prefixTable('plugin_setting');
250 Db::get()->query(sprintf('DELETE FROM `%s` WHERE user_login = ?', $table), array($userLogin));
251 } catch (Exception $e) {
252 if ($e->getCode() != 42) {
253 // ignore table not found error, which might occur when updating from an older version of Piwik
254 throw $e;
255 }
256 }
257 }
258 /**
259 * Unsets all settings for a plugin. The settings will be removed from the database. Used when
260 * a plugin is uninstalled.
261 *
262 * @internal
263 * @param string $pluginName
264 * @throws \Exception If the `$userLogin` is empty.
265 */
266 public static function removeAllSettingsForPlugin($pluginName)
267 {
268 try {
269 $table = Common::prefixTable('plugin_setting');
270 Db::get()->query(sprintf('DELETE FROM `%s` WHERE plugin_name = ?', $table), array($pluginName));
271 } catch (Exception $e) {
272 if ($e->getCode() != 42) {
273 // ignore table not found error, which might occur when updating from an older version of Piwik
274 throw $e;
275 }
276 }
277 }
278 /**
279 * @return string[]
280 */
281 private static function getColumns() : array
282 {
283 return ['plugin_name', 'user_login', 'setting_name', 'setting_value', 'json_encoded'];
284 }
285 /**
286 * @param mixed $value
287 * @return array{0: mixed, 1: int}
288 */
289 private function encodeValueForStorage($value) : array
290 {
291 if (is_array($value) || is_object($value)) {
292 return [json_encode($value), 1];
293 }
294 if (is_bool($value)) {
295 return [(int) $value, 0];
296 }
297 return [$value, 0];
298 }
299 /**
300 * @param mixed $value
301 * @return mixed
302 */
303 private function decodeValueFromStorage($value, bool $jsonEncoded)
304 {
305 if ($jsonEncoded) {
306 return json_decode($value, \true);
307 }
308 return $value;
309 }
310 /**
311 * @return array<string, mixed>
312 */
313 private function flattenSettingsRows(array $settings) : array
314 {
315 $flat = [];
316 foreach ($settings as $setting) {
317 $name = $setting['setting_name'];
318 $jsonEncoded = isset($setting['json_encoded']) ? (bool) $setting['json_encoded'] : \false;
319 $value = $this->decodeValueFromStorage($setting['setting_value'], $jsonEncoded);
320 if (array_key_exists($name, $flat) && !$jsonEncoded) {
321 $flat[$name] = (array) $flat[$name];
322 $flat[$name][] = $value;
323 } else {
324 $flat[$name] = $value;
325 }
326 }
327 return $flat;
328 }
329 }
330