CRM-16373 - Migrate config_backend to civicrm_setting
[civicrm-core.git] / Civi / Core / SettingsBag.php
CommitLineData
3a84c0ab
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2015 |
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
9fd040ae
TO
83 protected $filteredValues;
84
3a84c0ab
TO
85 /**
86 * @param int $domainId
87 * The domain for which we want settings.
88 * @param int|NULL $contactId
89 * The contact for which we want settings. Use NULL for domain settings.
5dbaf8de
TO
90 */
91 public function __construct($domainId, $contactId) {
92 $this->domainId = $domainId;
93 $this->contactId = $contactId;
8c74acc3 94 $this->values = array();
5dbaf8de
TO
95 $this->filteredValues = array();
96 $this->combined = NULL;
97 }
98
99 /**
100 * Set/replace the default values.
101 *
3a84c0ab
TO
102 * @param array $defaults
103 * Array(string $settingName => mixed $value).
5dbaf8de
TO
104 * @return $this
105 */
106 public function loadDefaults($defaults) {
107 $this->defaults = $defaults;
108 $this->filteredValues = array();
109 $this->combined = NULL;
110 return $this;
111 }
112
113 /**
114 * Set/replace the mandatory values.
115 *
3a84c0ab
TO
116 * @param array $mandatory
117 * Array(string $settingName => mixed $value).
5dbaf8de 118 * @return $this
3a84c0ab 119 */
5dbaf8de 120 public function loadMandatory($mandatory) {
3a84c0ab 121 $this->mandatory = $mandatory;
5dbaf8de 122 $this->filteredValues = array();
3a84c0ab 123 $this->combined = NULL;
5dbaf8de 124 return $this;
3a84c0ab
TO
125 }
126
127 /**
5dbaf8de 128 * Load all explicit settings that apply to this domain or contact.
3a84c0ab
TO
129 *
130 * @return $this
131 */
5dbaf8de 132 public function loadValues() {
f806379b 133 // Note: Don't use DAO child classes. They require fields() which require
5dbaf8de 134 // translations -- which are keyed off settings!
f806379b
TO
135
136 $this->values = array();
3a84c0ab 137 $this->combined = NULL;
f806379b
TO
138
139 // Ordinarily, we just load values from `civicrm_setting`. But upgrades require care.
140 // In v4.0 and earlier, all values were stored in `civicrm_domain.config_backend`.
141 // In v4.1-v4.6, values were split between `civicrm_domain` and `civicrm_setting`.
142 // In v4.7+, all values are stored in `civicrm_setting`.
143 // Whenever a value is available in civicrm_setting, it will take precedence.
144
145 $isUpgradeMode = \CRM_Core_Config::isUpgradeMode();
146
147 if ($isUpgradeMode && empty($this->contactId) && \CRM_Core_DAO::checkFieldExists('civicrm_domain', 'config_backend', FALSE)) {
148 $config_backend = \CRM_Core_DAO::singleValueQuery('SELECT config_backend FROM civicrm_domain WHERE id = %1',
149 array(1 => array($this->domainId, 'Positive')));
150 $oldSettings = \CRM_Upgrade_Incremental_php_FourSeven::convertBackendToSettings($this->domainId, $config_backend);
151 \CRM_Utils_Array::extend($this->values, $oldSettings);
152 }
153
154 // Normal case. Aside: Short-circuit prevents unnecessary query.
155 if (!$isUpgradeMode || \CRM_Core_DAO::checkTableExists('civicrm_setting')) {
156 $dao = \CRM_Core_DAO::executeQuery($this->createQuery()->toSQL());
157 while ($dao->fetch()) {
158 $this->values[$dao->name] = ($dao->value !== NULL) ? unserialize($dao->value) : NULL;
159 }
160 }
161
3a84c0ab
TO
162 return $this;
163 }
164
165 /**
166 * Add a batch of settings. Save them.
167 *
168 * @param array $settings
169 * Array(string $settingName => mixed $settingValue).
170 * @return $this
171 */
172 public function add(array $settings) {
173 foreach ($settings as $key => $value) {
174 $this->set($key, $value);
175 }
176 return $this;
177 }
178
179 /**
180 * Get a list of all effective settings.
181 *
182 * @return array
183 * Array(string $settingName => mixed $settingValue).
184 */
185 public function all() {
186 if ($this->combined === NULL) {
187 $this->combined = $this->combine(
188 array($this->defaults, $this->values, $this->mandatory)
189 );
190 }
191 return $this->combined;
192 }
193
194 /**
195 * Determine the effective value.
196 *
197 * @param string $key
198 * @return mixed
199 */
200 public function get($key) {
201 $all = $this->all();
202 return isset($all[$key]) ? $all[$key] : NULL;
203 }
204
1a6ba7d4
TO
205 /**
206 * Determine the default value of a setting.
207 *
208 * @param string $key
209 * The simple name of the setting.
210 * @return mixed|NULL
211 */
212 public function getDefault($key) {
213 return isset($this->defaults[$key]) ? $this->defaults[$key] : NULL;
214 }
215
3a84c0ab
TO
216 /**
217 * Determine the explicitly designated value, regardless of
218 * any default or mandatory values.
219 *
220 * @param string $key
1a6ba7d4
TO
221 * The simple name of the setting.
222 * @return mixed|NULL
3a84c0ab
TO
223 */
224 public function getExplicit($key) {
225 return (isset($this->values[$key]) ? $this->values[$key] : NULL);
226 }
227
1a6ba7d4
TO
228 /**
229 * Determine the mandatory value of a setting.
230 *
231 * @param string $key
232 * The simple name of the setting.
233 * @return mixed|NULL
234 */
235 public function getMandatory($key) {
236 return isset($this->mandatory[$key]) ? $this->mandatory[$key] : NULL;
237 }
238
3a84c0ab
TO
239 /**
240 * Determine if the entity has explicitly designated a value.
241 *
242 * Note that get() may still return other values based on
243 * mandatory values or defaults.
244 *
245 * @param string $key
1a6ba7d4 246 * The simple name of the setting.
3a84c0ab
TO
247 * @return bool
248 */
249 public function hasExplict($key) {
250 // NULL means no designated value.
251 return isset($this->values[$key]);
252 }
253
254 /**
255 * Removes any explicit settings. This restores the default.
256 *
257 * @param string $key
1a6ba7d4 258 * The simple name of the setting.
3a84c0ab
TO
259 * @return $this
260 */
261 public function revert($key) {
262 // It might be better to DELETE (to avoid long-term leaks),
263 // but setting NULL is simpler for now.
264 return $this->set($key, NULL);
265 }
266
267 /**
268 * Add a single setting. Save it.
269 *
270 * @param string $key
1a6ba7d4 271 * The simple name of the setting.
3a84c0ab 272 * @param mixed $value
1a6ba7d4 273 * The new, explicit value of the setting.
3a84c0ab
TO
274 * @return $this
275 */
276 public function set($key, $value) {
277 $this->setDb($key, $value);
278 $this->values[$key] = $value;
9fd040ae 279 unset($this->filteredValues[$key]);
3a84c0ab
TO
280 $this->combined = NULL;
281 return $this;
282 }
283
284 /**
5dbaf8de 285 * @return \CRM_Utils_SQL_Select
3a84c0ab 286 */
5dbaf8de
TO
287 protected function createQuery() {
288 $select = \CRM_Utils_SQL_Select::from('civicrm_setting')
f806379b 289 ->select('id, name, value, domain_id, contact_id, is_domain, component_id, created_date, created_id')
5dbaf8de
TO
290 ->where('domain_id = #id', array(
291 'id' => $this->domainId,
292 ));
3a84c0ab 293 if ($this->contactId === NULL) {
5dbaf8de 294 $select->where('is_domain = 1');
3a84c0ab
TO
295 }
296 else {
5dbaf8de
TO
297 $select->where('contact_id = #id', array(
298 'id' => $this->contactId,
299 ));
300 $select->where('is_domain = 0');
3a84c0ab 301 }
5dbaf8de 302 return $select;
3a84c0ab
TO
303 }
304
305 /**
306 * Combine a series of arrays, excluding any
307 * null values. Later values override earlier
308 * values.
309 *
1a6ba7d4
TO
310 * @param array $arrays
311 * List of arrays to combine.
3a84c0ab
TO
312 * @return array
313 */
314 protected function combine($arrays) {
315 $combined = array();
316 foreach ($arrays as $array) {
317 foreach ($array as $k => $v) {
318 if ($v !== NULL) {
319 $combined[$k] = $v;
320 }
321 }
322 }
323 return $combined;
324 }
325
326 /**
055f6c27
TO
327 * Update the DB record for this setting.
328 *
1a6ba7d4
TO
329 * @param string $name
330 * The simple name of the setting.
331 * @param mixed $value
332 * The new value of the setting.
3a84c0ab
TO
333 */
334 protected function setDb($name, $value) {
055f6c27
TO
335 if (\CRM_Core_BAO_Setting::isUpgradeFromPreFourOneAlpha1()) {
336 // civicrm_setting table is not going to be present.
337 return;
338 }
339
3a84c0ab
TO
340 $fields = array();
341 $fieldsToSet = \CRM_Core_BAO_Setting::validateSettingsInput(array($name => $value), $fields);
342 //We haven't traditionally validated inputs to setItem, so this breaks things.
343 //foreach ($fieldsToSet as $settingField => &$settingValue) {
344 // self::validateSetting($settingValue, $fields['values'][$settingField]);
345 //}
055f6c27
TO
346
347 $metadata = $fields['values'][$name];
348
349 $dao = new \CRM_Core_DAO_Setting();
350 $dao->name = $name;
351 $dao->domain_id = $this->domainId;
352 if ($this->contactId) {
353 $dao->contact_id = $this->contactId;
354 $dao->is_domain = 0;
355 }
356 else {
357 $dao->is_domain = 1;
358 }
359 $dao->find(TRUE);
055f6c27
TO
360
361 if (isset($metadata['on_change'])) {
362 foreach ($metadata['on_change'] as $callback) {
363 call_user_func(
364 \Civi\Core\Resolver::singleton()->get($callback),
365 unserialize($dao->value),
366 $value,
367 $metadata,
368 $this->domainId
369 );
370 }
371 }
372
373 if (\CRM_Utils_System::isNull($value)) {
374 $dao->value = 'null';
375 }
376 else {
377 $dao->value = serialize($value);
378 }
379
380 $dao->created_date = \CRM_Utils_Time::getTime('Ymdhis');
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
387 $dao->save();
388 $dao->free();
3a84c0ab
TO
389 }
390
391}