Merge pull request #11413 from seamuslee001/CRM-21534-29
[civicrm-core.git] / Civi / Core / SettingsBag.php
CommitLineData
3a84c0ab
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
3b8eef99 6 | Copyright CiviCRM LLC (c) 2004-2017 |
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 *
73 * @var array|NULL
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;
8c74acc3 92 $this->values = array();
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
TO
130
131 $this->values = array();
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
142 if ($isUpgradeMode && empty($this->contactId) && \CRM_Core_DAO::checkFieldExists('civicrm_domain', 'config_backend', FALSE)) {
143 $config_backend = \CRM_Core_DAO::singleValueQuery('SELECT config_backend FROM civicrm_domain WHERE id = %1',
144 array(1 => array($this->domainId, 'Positive')));
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(
183 array($this->defaults, $this->values, $this->mandatory)
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')
5dbaf8de
TO
284 ->where('domain_id = #id', array(
285 'id' => $this->domainId,
286 ));
3a84c0ab 287 if ($this->contactId === NULL) {
5dbaf8de 288 $select->where('is_domain = 1');
3a84c0ab
TO
289 }
290 else {
5dbaf8de
TO
291 $select->where('contact_id = #id', array(
292 'id' => $this->contactId,
293 ));
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) {
309 $combined = array();
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) {
055f6c27
TO
329 if (\CRM_Core_BAO_Setting::isUpgradeFromPreFourOneAlpha1()) {
330 // civicrm_setting table is not going to be present.
331 return;
332 }
333
3a84c0ab
TO
334 $fields = array();
335 $fieldsToSet = \CRM_Core_BAO_Setting::validateSettingsInput(array($name => $value), $fields);
336 //We haven't traditionally validated inputs to setItem, so this breaks things.
337 //foreach ($fieldsToSet as $settingField => &$settingValue) {
338 // self::validateSetting($settingValue, $fields['values'][$settingField]);
339 //}
055f6c27
TO
340
341 $metadata = $fields['values'][$name];
342
343 $dao = new \CRM_Core_DAO_Setting();
344 $dao->name = $name;
345 $dao->domain_id = $this->domainId;
346 if ($this->contactId) {
347 $dao->contact_id = $this->contactId;
348 $dao->is_domain = 0;
349 }
350 else {
351 $dao->is_domain = 1;
352 }
353 $dao->find(TRUE);
055f6c27 354
03c5ceba 355 // string comparison with 0 always return true, so to be ensure the type use ===
356 // ref - https://stackoverflow.com/questions/8671942/php-string-comparasion-to-0-integer-returns-true
357 if (isset($metadata['on_change']) && !($value === 0 && ($dao->value === NULL || unserialize($dao->value) == 0))) {
055f6c27
TO
358 foreach ($metadata['on_change'] as $callback) {
359 call_user_func(
360 \Civi\Core\Resolver::singleton()->get($callback),
361 unserialize($dao->value),
362 $value,
363 $metadata,
364 $this->domainId
365 );
366 }
367 }
368
bf322c68 369 if (!is_array($value) && \CRM_Utils_System::isNull($value)) {
055f6c27
TO
370 $dao->value = 'null';
371 }
372 else {
373 $dao->value = serialize($value);
374 }
375
8ff759c4
C
376 if (!isset(\Civi::$statics[__CLASS__]['upgradeMode'])) {
377 \Civi::$statics[__CLASS__]['upgradeMode'] = \CRM_Core_Config::isUpgradeMode();
378 }
379 if (\Civi::$statics[__CLASS__]['upgradeMode'] && \CRM_Core_DAO::checkFieldExists('civicrm_setting', 'group_name')) {
380 $dao->group_name = 'placeholder';
381 }
382
9ece35cc 383 $dao->created_date = \CRM_Utils_Time::getTime('YmdHis');
055f6c27
TO
384
385 $session = \CRM_Core_Session::singleton();
386 if (\CRM_Contact_BAO_Contact_Utils::isContactId($session->get('userID'))) {
387 $dao->created_id = $session->get('userID');
388 }
389
867a532b
TO
390 if ($dao->id) {
391 $dao->save();
392 }
393 else {
394 // Cannot use $dao->save(); in upgrade mode (eg WP + Civi 4.4=>4.7), the DAO will refuse
395 // to save the field `group_name`, which is required in older schema.
396 \CRM_Core_DAO::executeQuery(\CRM_Utils_SQL_Insert::dao($dao)->toSQL());
397 }
055f6c27 398 $dao->free();
3a84c0ab
TO
399 }
400
401}