Merge pull request #14249 from yashodha/959_dev
[civicrm-core.git] / Civi / Core / SettingsBag.php
CommitLineData
3a84c0ab
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
fee14197 4 | CiviCRM version 5 |
3a84c0ab 5 +--------------------------------------------------------------------+
6b83d5bd 6 | Copyright CiviCRM LLC (c) 2004-2019 |
3a84c0ab
TO
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28namespace Civi\Core;
29
30/**
31 * Class SettingsBag
32 * @package Civi\Core
33 *
34 * Read and write settings for a given domain (or contact).
35 *
36 * If the target entity does not already have a value for the setting, then
37 * the defaults will be used. If mandatory values are provided, they will
38 * override any defaults or custom settings.
39 *
40 * It's expected that the SettingsBag will have O(50-250) settings -- and that
41 * we'll load the full bag on many page requests. Consequently, we don't
42 * want the full metadata (help text and version history and HTML widgets)
43 * for all 250 settings, but we do need the default values.
44 *
45 * This class is not usually instantiated directly. Instead, use SettingsManager
46 * or Civi::settings().
47 *
48 * @see \Civi::settings()
49 * @see SettingsManagerTest
50 */
51class SettingsBag {
52
53 protected $domainId;
54
55 protected $contactId;
56
57 /**
58 * @var array
59 * Array(string $settingName => mixed $value).
60 */
61 protected $defaults;
62
63 /**
64 * @var array
65 * Array(string $settingName => mixed $value).
66 */
67 protected $mandatory;
68
69 /**
70 * The result of combining default values, mandatory
71 * values, and user values.
72 *
cc101011 73 * @var array|null
3a84c0ab
TO
74 * Array(string $settingName => mixed $value).
75 */
76 protected $combined;
77
78 /**
79 * @var array
80 */
81 protected $values;
82
83 /**
84 * @param int $domainId
85 * The domain for which we want settings.
86 * @param int|NULL $contactId
87 * The contact for which we want settings. Use NULL for domain settings.
5dbaf8de
TO
88 */
89 public function __construct($domainId, $contactId) {
90 $this->domainId = $domainId;
91 $this->contactId = $contactId;
c64f69d9 92 $this->values = [];
5dbaf8de
TO
93 $this->combined = NULL;
94 }
95
96 /**
97 * Set/replace the default values.
98 *
3a84c0ab
TO
99 * @param array $defaults
100 * Array(string $settingName => mixed $value).
4b350175 101 * @return SettingsBag
5dbaf8de
TO
102 */
103 public function loadDefaults($defaults) {
104 $this->defaults = $defaults;
5dbaf8de
TO
105 $this->combined = NULL;
106 return $this;
107 }
108
109 /**
110 * Set/replace the mandatory values.
111 *
3a84c0ab
TO
112 * @param array $mandatory
113 * Array(string $settingName => mixed $value).
4b350175 114 * @return SettingsBag
3a84c0ab 115 */
5dbaf8de 116 public function loadMandatory($mandatory) {
3a84c0ab
TO
117 $this->mandatory = $mandatory;
118 $this->combined = NULL;
5dbaf8de 119 return $this;
3a84c0ab
TO
120 }
121
122 /**
5dbaf8de 123 * Load all explicit settings that apply to this domain or contact.
3a84c0ab 124 *
4b350175 125 * @return SettingsBag
3a84c0ab 126 */
5dbaf8de 127 public function loadValues() {
f806379b 128 // Note: Don't use DAO child classes. They require fields() which require
5dbaf8de 129 // translations -- which are keyed off settings!
f806379b 130
c64f69d9 131 $this->values = [];
3a84c0ab 132 $this->combined = NULL;
f806379b
TO
133
134 // Ordinarily, we just load values from `civicrm_setting`. But upgrades require care.
135 // In v4.0 and earlier, all values were stored in `civicrm_domain.config_backend`.
136 // In v4.1-v4.6, values were split between `civicrm_domain` and `civicrm_setting`.
137 // In v4.7+, all values are stored in `civicrm_setting`.
138 // Whenever a value is available in civicrm_setting, it will take precedence.
139
140 $isUpgradeMode = \CRM_Core_Config::isUpgradeMode();
141
eed7e803 142 if ($isUpgradeMode && empty($this->contactId) && \CRM_Core_BAO_SchemaHandler::checkIfFieldExists('civicrm_domain', 'config_backend', FALSE)) {
f806379b 143 $config_backend = \CRM_Core_DAO::singleValueQuery('SELECT config_backend FROM civicrm_domain WHERE id = %1',
c64f69d9 144 [1 => [$this->domainId, 'Positive']]);
f806379b
TO
145 $oldSettings = \CRM_Upgrade_Incremental_php_FourSeven::convertBackendToSettings($this->domainId, $config_backend);
146 \CRM_Utils_Array::extend($this->values, $oldSettings);
147 }
148
149 // Normal case. Aside: Short-circuit prevents unnecessary query.
150 if (!$isUpgradeMode || \CRM_Core_DAO::checkTableExists('civicrm_setting')) {
151 $dao = \CRM_Core_DAO::executeQuery($this->createQuery()->toSQL());
152 while ($dao->fetch()) {
153 $this->values[$dao->name] = ($dao->value !== NULL) ? unserialize($dao->value) : NULL;
154 }
155 }
156
3a84c0ab
TO
157 return $this;
158 }
159
160 /**
161 * Add a batch of settings. Save them.
162 *
163 * @param array $settings
164 * Array(string $settingName => mixed $settingValue).
4b350175 165 * @return SettingsBag
3a84c0ab
TO
166 */
167 public function add(array $settings) {
168 foreach ($settings as $key => $value) {
169 $this->set($key, $value);
170 }
171 return $this;
172 }
173
174 /**
175 * Get a list of all effective settings.
176 *
177 * @return array
178 * Array(string $settingName => mixed $settingValue).
179 */
180 public function all() {
181 if ($this->combined === NULL) {
182 $this->combined = $this->combine(
c64f69d9 183 [$this->defaults, $this->values, $this->mandatory]
3a84c0ab
TO
184 );
185 }
186 return $this->combined;
187 }
188
189 /**
190 * Determine the effective value.
191 *
192 * @param string $key
193 * @return mixed
194 */
195 public function get($key) {
196 $all = $this->all();
197 return isset($all[$key]) ? $all[$key] : NULL;
198 }
199
1a6ba7d4
TO
200 /**
201 * Determine the default value of a setting.
202 *
203 * @param string $key
204 * The simple name of the setting.
205 * @return mixed|NULL
206 */
207 public function getDefault($key) {
208 return isset($this->defaults[$key]) ? $this->defaults[$key] : NULL;
209 }
210
3a84c0ab
TO
211 /**
212 * Determine the explicitly designated value, regardless of
213 * any default or mandatory values.
214 *
215 * @param string $key
1a6ba7d4
TO
216 * The simple name of the setting.
217 * @return mixed|NULL
3a84c0ab
TO
218 */
219 public function getExplicit($key) {
220 return (isset($this->values[$key]) ? $this->values[$key] : NULL);
221 }
222
1a6ba7d4
TO
223 /**
224 * Determine the mandatory value of a setting.
225 *
226 * @param string $key
227 * The simple name of the setting.
228 * @return mixed|NULL
229 */
230 public function getMandatory($key) {
231 return isset($this->mandatory[$key]) ? $this->mandatory[$key] : NULL;
232 }
233
3a84c0ab
TO
234 /**
235 * Determine if the entity has explicitly designated a value.
236 *
237 * Note that get() may still return other values based on
238 * mandatory values or defaults.
239 *
240 * @param string $key
1a6ba7d4 241 * The simple name of the setting.
3a84c0ab
TO
242 * @return bool
243 */
244 public function hasExplict($key) {
245 // NULL means no designated value.
246 return isset($this->values[$key]);
247 }
248
249 /**
250 * Removes any explicit settings. This restores the default.
251 *
252 * @param string $key
1a6ba7d4 253 * The simple name of the setting.
4b350175 254 * @return SettingsBag
3a84c0ab
TO
255 */
256 public function revert($key) {
257 // It might be better to DELETE (to avoid long-term leaks),
258 // but setting NULL is simpler for now.
259 return $this->set($key, NULL);
260 }
261
262 /**
263 * Add a single setting. Save it.
264 *
265 * @param string $key
1a6ba7d4 266 * The simple name of the setting.
3a84c0ab 267 * @param mixed $value
1a6ba7d4 268 * The new, explicit value of the setting.
4b350175 269 * @return SettingsBag
3a84c0ab
TO
270 */
271 public function set($key, $value) {
272 $this->setDb($key, $value);
273 $this->values[$key] = $value;
274 $this->combined = NULL;
275 return $this;
276 }
277
278 /**
5dbaf8de 279 * @return \CRM_Utils_SQL_Select
3a84c0ab 280 */
5dbaf8de
TO
281 protected function createQuery() {
282 $select = \CRM_Utils_SQL_Select::from('civicrm_setting')
f806379b 283 ->select('id, name, value, domain_id, contact_id, is_domain, component_id, created_date, created_id')
c64f69d9 284 ->where('domain_id = #id', [
5dbaf8de 285 'id' => $this->domainId,
c64f69d9 286 ]);
3a84c0ab 287 if ($this->contactId === NULL) {
5dbaf8de 288 $select->where('is_domain = 1');
3a84c0ab
TO
289 }
290 else {
c64f69d9 291 $select->where('contact_id = #id', [
5dbaf8de 292 'id' => $this->contactId,
c64f69d9 293 ]);
5dbaf8de 294 $select->where('is_domain = 0');
3a84c0ab 295 }
5dbaf8de 296 return $select;
3a84c0ab
TO
297 }
298
299 /**
300 * Combine a series of arrays, excluding any
301 * null values. Later values override earlier
302 * values.
303 *
1a6ba7d4
TO
304 * @param array $arrays
305 * List of arrays to combine.
3a84c0ab
TO
306 * @return array
307 */
308 protected function combine($arrays) {
c64f69d9 309 $combined = [];
3a84c0ab
TO
310 foreach ($arrays as $array) {
311 foreach ($array as $k => $v) {
312 if ($v !== NULL) {
313 $combined[$k] = $v;
314 }
315 }
316 }
317 return $combined;
318 }
319
320 /**
055f6c27
TO
321 * Update the DB record for this setting.
322 *
1a6ba7d4
TO
323 * @param string $name
324 * The simple name of the setting.
325 * @param mixed $value
326 * The new value of the setting.
3a84c0ab
TO
327 */
328 protected function setDb($name, $value) {
c64f69d9
CW
329 $fields = [];
330 $fieldsToSet = \CRM_Core_BAO_Setting::validateSettingsInput([$name => $value], $fields);
3a84c0ab
TO
331 //We haven't traditionally validated inputs to setItem, so this breaks things.
332 //foreach ($fieldsToSet as $settingField => &$settingValue) {
333 // self::validateSetting($settingValue, $fields['values'][$settingField]);
334 //}
055f6c27
TO
335
336 $metadata = $fields['values'][$name];
337
338 $dao = new \CRM_Core_DAO_Setting();
339 $dao->name = $name;
340 $dao->domain_id = $this->domainId;
341 if ($this->contactId) {
342 $dao->contact_id = $this->contactId;
343 $dao->is_domain = 0;
344 }
345 else {
346 $dao->is_domain = 1;
347 }
348 $dao->find(TRUE);
055f6c27 349
38e5457e
TO
350 // Call 'on_change' listeners. It would be nice to only fire when there's
351 // a genuine change in the data. However, PHP developers have mixed
352 // expectations about whether 0, '0', '', NULL, and FALSE represent the same
353 // value, so there's no universal way to determine if a change is genuine.
354 if (isset($metadata['on_change'])) {
055f6c27
TO
355 foreach ($metadata['on_change'] as $callback) {
356 call_user_func(
357 \Civi\Core\Resolver::singleton()->get($callback),
358 unserialize($dao->value),
359 $value,
360 $metadata,
361 $this->domainId
362 );
363 }
364 }
365
bf322c68 366 if (!is_array($value) && \CRM_Utils_System::isNull($value)) {
055f6c27
TO
367 $dao->value = 'null';
368 }
369 else {
370 $dao->value = serialize($value);
371 }
372
8ff759c4
C
373 if (!isset(\Civi::$statics[__CLASS__]['upgradeMode'])) {
374 \Civi::$statics[__CLASS__]['upgradeMode'] = \CRM_Core_Config::isUpgradeMode();
375 }
eed7e803 376 if (\Civi::$statics[__CLASS__]['upgradeMode'] && \CRM_Core_BAO_SchemaHandler::checkIfFieldExists('civicrm_setting', 'group_name')) {
8ff759c4
C
377 $dao->group_name = 'placeholder';
378 }
379
9ece35cc 380 $dao->created_date = \CRM_Utils_Time::getTime('YmdHis');
055f6c27
TO
381
382 $session = \CRM_Core_Session::singleton();
383 if (\CRM_Contact_BAO_Contact_Utils::isContactId($session->get('userID'))) {
384 $dao->created_id = $session->get('userID');
385 }
386
867a532b
TO
387 if ($dao->id) {
388 $dao->save();
389 }
390 else {
391 // Cannot use $dao->save(); in upgrade mode (eg WP + Civi 4.4=>4.7), the DAO will refuse
392 // to save the field `group_name`, which is required in older schema.
393 \CRM_Core_DAO::executeQuery(\CRM_Utils_SQL_Insert::dao($dao)->toSQL());
394 }
3a84c0ab
TO
395 }
396
397}