3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
18 * Read and write settings for a given domain (or contact).
20 * If the target entity does not already have a value for the setting, then
21 * the defaults will be used. If mandatory values are provided, they will
22 * override any defaults or custom settings.
24 * It's expected that the SettingsBag will have O(50-250) settings -- and that
25 * we'll load the full bag on many page requests. Consequently, we don't
26 * want the full metadata (help text and version history and HTML widgets)
27 * for all 250 settings, but we do need the default values.
29 * This class is not usually instantiated directly. Instead, use SettingsManager
30 * or Civi::settings().
32 * @see \Civi::settings()
33 * @see SettingsManagerTest
43 * Array(string $settingName => mixed $value).
49 * Array(string $settingName => mixed $value).
54 * The result of combining default values, mandatory
55 * values, and user values.
58 * Array(string $settingName => mixed $value).
68 * @param int $domainId
69 * The domain for which we want settings.
70 * @param int|null $contactId
71 * The contact for which we want settings. Use NULL for domain settings.
73 public function __construct($domainId, $contactId) {
74 $this->domainId
= $domainId;
75 $this->contactId
= $contactId;
77 $this->combined
= NULL;
81 * Set/replace the default values.
83 * @param array $defaults
84 * Array(string $settingName => mixed $value).
87 public function loadDefaults($defaults) {
88 $this->defaults
= $defaults;
89 $this->combined
= NULL;
94 * Set/replace the mandatory values.
96 * @param array $mandatory
97 * Array(string $settingName => mixed $value).
100 public function loadMandatory($mandatory) {
101 $this->mandatory
= $mandatory;
102 $this->combined
= NULL;
107 * Load all explicit settings that apply to this domain or contact.
109 * @return SettingsBag
111 public function loadValues() {
112 // Note: Don't use DAO child classes. They require fields() which require
113 // translations -- which are keyed off settings!
116 $this->combined
= NULL;
118 // Ordinarily, we just load values from `civicrm_setting`. But upgrades require care.
119 // In v4.0 and earlier, all values were stored in `civicrm_domain.config_backend`.
120 // In v4.1-v4.6, values were split between `civicrm_domain` and `civicrm_setting`.
121 // In v4.7+, all values are stored in `civicrm_setting`.
122 // Whenever a value is available in civicrm_setting, it will take precedence.
124 $isUpgradeMode = \CRM_Core_Config
::isUpgradeMode();
126 if ($isUpgradeMode && empty($this->contactId
) && \CRM_Core_BAO_SchemaHandler
::checkIfFieldExists('civicrm_domain', 'config_backend', FALSE)) {
127 $config_backend = \CRM_Core_DAO
::singleValueQuery('SELECT config_backend FROM civicrm_domain WHERE id = %1',
128 [1 => [$this->domainId
, 'Positive']]);
129 $oldSettings = \CRM_Upgrade_Incremental_php_FourSeven
::convertBackendToSettings($this->domainId
, $config_backend);
130 \CRM_Utils_Array
::extend($this->values
, $oldSettings);
133 // Normal case. Aside: Short-circuit prevents unnecessary query.
134 if (!$isUpgradeMode || \CRM_Core_DAO
::checkTableExists('civicrm_setting')) {
135 $dao = \CRM_Core_DAO
::executeQuery($this->createQuery()->toSQL());
136 while ($dao->fetch()) {
137 $this->values
[$dao->name
] = ($dao->value
!== NULL) ? \CRM_Utils_String
::unserialize($dao->value
) : NULL;
139 $dao->values
['contribution_invoice_settings'] = $this->getContributionSettings();
146 * Add a batch of settings. Save them.
148 * @param array $settings
149 * Array(string $settingName => mixed $settingValue).
150 * @return SettingsBag
152 public function add(array $settings) {
153 foreach ($settings as $key => $value) {
154 $this->set($key, $value);
160 * Get a list of all effective settings.
163 * Array(string $settingName => mixed $settingValue).
165 public function all() {
166 if ($this->combined
=== NULL) {
167 $this->combined
= $this->combine(
168 [$this->defaults
, $this->values
, $this->mandatory
]
171 return $this->combined
;
175 * Determine the effective value.
180 public function get($key) {
182 return $all[$key] ??
NULL;
186 * Determine the default value of a setting.
189 * The simple name of the setting.
192 public function getDefault($key) {
193 return $this->defaults
[$key] ??
NULL;
197 * Determine the explicitly designated value, regardless of
198 * any default or mandatory values.
201 * The simple name of the setting.
204 public function getExplicit($key) {
205 return ($this->values
[$key] ??
NULL);
209 * Determine the mandatory value of a setting.
212 * The simple name of the setting.
215 public function getMandatory($key) {
216 return $this->mandatory
[$key] ??
NULL;
220 * Determine if the entity has explicitly designated a value.
222 * Note that get() may still return other values based on
223 * mandatory values or defaults.
226 * The simple name of the setting.
229 public function hasExplict($key) {
230 // NULL means no designated value.
231 return isset($this->values
[$key]);
235 * Removes any explicit settings. This restores the default.
238 * The simple name of the setting.
239 * @return SettingsBag
241 public function revert($key) {
242 // It might be better to DELETE (to avoid long-term leaks),
243 // but setting NULL is simpler for now.
244 return $this->set($key, NULL);
248 * Add a single setting. Save it.
251 * The simple name of the setting.
252 * @param mixed $value
253 * The new, explicit value of the setting.
254 * @return SettingsBag
256 public function set($key, $value) {
257 if ($key === 'contribution_invoice_settings') {
258 $this->setContributionSettings($value);
261 $this->setDb($key, $value);
262 $this->values
[$key] = $value;
263 $this->combined
= NULL;
268 * Temporary handling for phasing out contribution_invoice_settings.
270 * Until we have transitioned we need to handle setting & retrieving
271 * contribution_invoice_settings.
273 * Once removed from core we will add deprecation notices & then remove this.
275 * https://lab.civicrm.org/dev/core/issues/1558
277 * @param array $value
279 public function setContributionSettings($value) {
280 foreach (SettingsBag
::getContributionInvoiceSettingKeys() as $possibleKeyName => $settingName) {
281 $keyValue = $value[$possibleKeyName] ??
'';
282 $this->set($settingName, $keyValue);
284 $this->values
['contribution_invoice_settings'] = $this->getContributionSettings();
288 * Temporary function to handle returning the contribution_settings key despite it being deprecated.
290 * See more in comment block on previous function.
294 public function getContributionSettings() {
295 $contributionSettings = [];
296 foreach (SettingsBag
::getContributionInvoiceSettingKeys() as $keyName => $settingName) {
297 $contributionSettings[$keyName] = $this->values
[$settingName] ??
'';
299 return $contributionSettings;
303 * @return \CRM_Utils_SQL_Select
305 protected function createQuery() {
306 $select = \CRM_Utils_SQL_Select
::from('civicrm_setting')
307 ->select('id, name, value, domain_id, contact_id, is_domain, component_id, created_date, created_id')
308 ->where('domain_id = #id', [
309 'id' => $this->domainId
,
311 if ($this->contactId
=== NULL) {
312 $select->where('is_domain = 1');
315 $select->where('contact_id = #id', [
316 'id' => $this->contactId
,
318 $select->where('is_domain = 0');
324 * Combine a series of arrays, excluding any
325 * null values. Later values override earlier
328 * @param array $arrays
329 * List of arrays to combine.
332 protected function combine($arrays) {
334 foreach ($arrays as $array) {
335 foreach ($array as $k => $v) {
345 * Update the DB record for this setting.
347 * @param string $name
348 * The simple name of the setting.
349 * @param mixed $value
350 * The new value of the setting.
352 protected function setDb($name, $value) {
354 $fieldsToSet = \CRM_Core_BAO_Setting
::validateSettingsInput([$name => $value], $fields);
355 //We haven't traditionally validated inputs to setItem, so this breaks things.
356 //foreach ($fieldsToSet as $settingField => &$settingValue) {
357 // self::validateSetting($settingValue, $fields['values'][$settingField]);
360 $metadata = $fields['values'][$name];
362 $dao = new \
CRM_Core_DAO_Setting();
364 $dao->domain_id
= $this->domainId
;
365 if ($this->contactId
) {
366 $dao->contact_id
= $this->contactId
;
374 // Call 'on_change' listeners. It would be nice to only fire when there's
375 // a genuine change in the data. However, PHP developers have mixed
376 // expectations about whether 0, '0', '', NULL, and FALSE represent the same
377 // value, so there's no universal way to determine if a change is genuine.
378 if (isset($metadata['on_change'])) {
379 foreach ($metadata['on_change'] as $callback) {
381 \Civi\Core\Resolver
::singleton()->get($callback),
382 \CRM_Utils_String
::unserialize($dao->value
),
390 if (!is_array($value) && \CRM_Utils_System
::isNull($value)) {
391 $dao->value
= 'null';
394 $dao->value
= serialize($value);
397 if (!isset(\Civi
::$statics[__CLASS__
]['upgradeMode'])) {
398 \Civi
::$statics[__CLASS__
]['upgradeMode'] = \CRM_Core_Config
::isUpgradeMode();
400 if (\Civi
::$statics[__CLASS__
]['upgradeMode'] && \CRM_Core_BAO_SchemaHandler
::checkIfFieldExists('civicrm_setting', 'group_name')) {
401 $dao->group_name
= 'placeholder';
404 $dao->created_date
= \CRM_Utils_Time
::getTime('YmdHis');
406 $session = \CRM_Core_Session
::singleton();
407 if (\CRM_Contact_BAO_Contact_Utils
::isContactId($session->get('userID'))) {
408 $dao->created_id
= $session->get('userID');
415 // Cannot use $dao->save(); in upgrade mode (eg WP + Civi 4.4=>4.7), the DAO will refuse
416 // to save the field `group_name`, which is required in older schema.
417 \CRM_Core_DAO
::executeQuery(\CRM_Utils_SQL_Insert
::dao($dao)->toSQL());
424 public static function getContributionInvoiceSettingKeys(): array {
426 'credit_notes_prefix' => 'credit_notes_prefix',
427 'invoice_prefix' => 'invoice_prefix',
428 'due_date' => 'invoice_due_date',
429 'due_date_period' => 'invoice_due_date_period',
430 'notes' => 'invoice_notes',
431 'is_email_pdf' => 'invoice_is_email_pdf',
432 'tax_term' => 'tax_term',
433 'tax_display_settings' => 'tax_display_settings',
434 'invoicing' => 'invoicing',
436 return $convertedKeys;