3 +--------------------------------------------------------------------+
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
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. |
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. |
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 +--------------------------------------------------------------------+
31 * Class SettingsManager
34 * The SettingsManager is responsible for tracking settings across various
37 * Generally, for any given setting, there are three levels where values
40 * - Mandatory values (which come from a global $civicrm_setting).
41 * - Explicit values (which are chosen by the user and stored in the DB).
42 * - Default values (which come from the settings metadata).
44 * Note: During the early stages of bootstrap, default values are not be available.
45 * Loading the defaults requires loading metadata from various sources. However,
46 * near the end of bootstrap, one calls SettingsManager::useDefaults() to fetch
47 * and merge the defaults.
49 * Note: In a typical usage, there will only be one active domain and one
50 * active contact (each having its own bag) within a given request. However,
51 * in some edge-cases, you may need to work with multiple domains/contacts
54 * Note: The global $civicrm_setting is meant to provide sysadmins with a way
55 * to override settings in `civicrm.settings.php`, but it has traditionally been
56 * possible for extensions to manipulate $civicrm_setting in a hook. If you do
57 * this, please call `useMandatory()` to tell SettingsManager to re-scan
60 * @see SettingsManagerTest
62 class SettingsManager
{
65 * @var \CRM_Utils_Cache_Interface
71 * Array (int $id => SettingsBag $bag).
73 protected $bagsByDomain = [];
78 * Array (int $id => SettingsBag $bag).
80 protected $bagsByContact = [];
84 * Array(string $entity => array(string $settingName => mixed $value)).
85 * Ex: $mandatory['domain']['uploadDir'].
86 * NULL means "autoload from $civicrm_setting".
88 protected $mandatory = NULL;
91 * Whether to use defaults.
95 protected $useDefaults = FALSE;
98 * @param \CRM_Utils_Cache_Interface $cache
99 * A semi-durable location to store metadata.
101 public function __construct($cache) {
102 $this->cache
= $cache;
106 * Ensure that all defaults values are included with
107 * all current and future bags.
109 * @return SettingsManager
111 public function useDefaults() {
112 if (!$this->useDefaults
) {
113 $this->useDefaults
= TRUE;
115 if (!empty($this->bagsByDomain
)) {
116 foreach ($this->bagsByDomain
as $bag) {
117 /** @var SettingsBag $bag */
118 $bag->loadDefaults($this->getDefaults('domain'));
122 if (!empty($this->bagsByContact
)) {
123 foreach ($this->bagsByContact
as $bag) {
124 /** @var SettingsBag $bag */
125 $bag->loadDefaults($this->getDefaults('contact'));
134 * Ensure that mandatory values are included with
135 * all current and future bags.
137 * If you call useMandatory multiple times, it will
138 * re-scan the global $civicrm_setting.
140 * @return SettingsManager
142 public function useMandatory() {
143 $this->mandatory
= NULL;
145 foreach ($this->bagsByDomain
as $bag) {
146 /** @var SettingsBag $bag */
147 $bag->loadMandatory($this->getMandatory('domain'));
150 foreach ($this->bagsByContact
as $bag) {
151 /** @var SettingsBag $bag */
152 $bag->loadMandatory($this->getMandatory('contact'));
159 * @param int|NULL $domainId
160 * @return SettingsBag
162 public function getBagByDomain($domainId) {
163 if ($domainId === NULL) {
164 $domainId = \CRM_Core_Config
::domainID();
167 if (!isset($this->bagsByDomain
[$domainId])) {
168 $this->bagsByDomain
[$domainId] = new SettingsBag($domainId, NULL);
169 if (\CRM_Core_Config
::singleton()->dsn
) {
170 $this->bagsByDomain
[$domainId]->loadValues();
172 $this->bagsByDomain
[$domainId]
173 ->loadMandatory($this->getMandatory('domain'))
174 ->loadDefaults($this->getDefaults('domain'));
176 return $this->bagsByDomain
[$domainId];
180 * @param int|NULL $domainId
181 * For the default domain, leave $domainID as NULL.
182 * @param int|NULL $contactId
183 * For the default/active user's contact, leave $domainID as NULL.
184 * @return SettingsBag
185 * @throws \CRM_Core_Exception
186 * If there is no contact, then there's no SettingsBag, and we'll throw
189 public function getBagByContact($domainId, $contactId) {
190 if ($domainId === NULL) {
191 $domainId = \CRM_Core_Config
::domainID();
193 if ($contactId === NULL) {
194 $contactId = \CRM_Core_Session
::getLoggedInContactID();
196 throw new \
CRM_Core_Exception("Cannot access settings subsystem - user or domain is unavailable");
200 $key = "$domainId:$contactId";
201 if (!isset($this->bagsByContact
[$key])) {
202 $this->bagsByContact
[$key] = new SettingsBag($domainId, $contactId);
203 if (\CRM_Core_Config
::singleton()->dsn
) {
204 $this->bagsByContact
[$key]->loadValues();
206 $this->bagsByContact
[$key]
207 ->loadDefaults($this->getDefaults('contact'))
208 ->loadMandatory($this->getMandatory('contact'));
210 return $this->bagsByContact
[$key];
214 * Determine the default settings.
216 * @param string $entity
217 * Ex: 'domain' or 'contact'.
219 * Array(string $settingName => mixed $value).
221 protected function getDefaults($entity) {
222 if (!$this->useDefaults
) {
223 return self
::getSystemDefaults($entity);
226 $cacheKey = 'defaults_' . $entity;
227 $defaults = $this->cache
->get($cacheKey);
228 if (!is_array($defaults)) {
229 $specs = SettingsMetadata
::getMetadata([
230 'is_contact' => ($entity === 'contact' ?
1 : 0),
233 foreach ($specs as $key => $spec) {
234 $defaults[$key] = \CRM_Utils_Array
::value('default', $spec);
236 \CRM_Utils_Array
::extend($defaults, self
::getSystemDefaults($entity));
237 $this->cache
->set($cacheKey, $defaults);
243 * Get a list of mandatory/overriden settings.
245 * @param string $entity
246 * Ex: 'domain' or 'contact'.
248 * Array(string $settingName => mixed $value).
250 protected function getMandatory($entity) {
251 if ($this->mandatory
=== NULL) {
252 $this->mandatory
= self
::parseMandatorySettings(\CRM_Utils_Array
::value('civicrm_setting', $GLOBALS));
254 return $this->mandatory
[$entity];
258 * Parse mandatory settings.
260 * In previous versions, settings were broken down into verbose+dynamic group names, e.g.
262 * $civicrm_settings['Foo Bar Preferences']['foo'] = 'bar';
264 * We now simplify to two simple groups, 'domain' and 'contact'.
266 * $civicrm_settings['domain']['foo'] = 'bar';
268 * However, the old groups are grand-fathered in as aliases.
270 * @param array $civicrm_setting
271 * Ex: $civicrm_setting['Group Name']['field'] = 'value'.
272 * Group names are an historical quirk; ignore them.
275 public static function parseMandatorySettings($civicrm_setting) {
282 //\CRM_Core_BAO_Setting::ADDRESS_STANDARDIZATION_PREFERENCES_NAME => 'domain',
283 //\CRM_Core_BAO_Setting::CAMPAIGN_PREFERENCES_NAME => 'domain',
284 //\CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME => 'domain',
285 //\CRM_Core_BAO_Setting::DEVELOPER_PREFERENCES_NAME => 'domain',
286 //\CRM_Core_BAO_Setting::DIRECTORY_PREFERENCES_NAME => 'domain',
287 //\CRM_Core_BAO_Setting::EVENT_PREFERENCES_NAME => 'domain',
288 //\CRM_Core_BAO_Setting::LOCALIZATION_PREFERENCES_NAME => 'domain',
289 //\CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME => 'domain',
290 //\CRM_Core_BAO_Setting::MAP_PREFERENCES_NAME => 'domain',
291 //\CRM_Core_BAO_Setting::MEMBER_PREFERENCES_NAME => 'domain',
292 //\CRM_Core_BAO_Setting::MULTISITE_PREFERENCES_NAME => 'domain',
293 //\CRM_Core_BAO_Setting::PERSONAL_PREFERENCES_NAME => 'contact',
294 'Personal Preferences' => 'contact',
295 //\CRM_Core_BAO_Setting::SEARCH_PREFERENCES_NAME => 'domain',
296 //\CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME => 'domain',
297 //\CRM_Core_BAO_Setting::URL_PREFERENCES_NAME => 'domain',
298 'domain' => 'domain',
299 'contact' => 'contact',
302 if (is_array($civicrm_setting)) {
303 foreach ($civicrm_setting as $oldGroup => $values) {
304 $newGroup = isset($rewriteGroups[$oldGroup]) ?
$rewriteGroups[$oldGroup] : 'domain';
305 $result[$newGroup] = array_merge($result[$newGroup], $values);
312 * Flush all in-memory and persistent caches related to settings.
314 * @return SettingsManager
316 public function flush() {
317 $this->mandatory
= NULL;
319 $this->cache
->flush();
320 // SettingsMetadata; not guaranteed to use same cache.
321 \Civi
::cache('settings')->flush();
323 foreach ($this->bagsByDomain
as $bag) {
324 /** @var SettingsBag $bag */
326 $bag->loadDefaults($this->getDefaults('domain'));
327 $bag->loadMandatory($this->getMandatory('domain'));
330 foreach ($this->bagsByContact
as $bag) {
331 /** @var SettingsBag $bag */
333 $bag->loadDefaults($this->getDefaults('contact'));
334 $bag->loadMandatory($this->getMandatory('contact'));
341 * Get a list of critical system defaults.
343 * The setting system can be modified by extensions, which means that it's not fully available
344 * during bootstrap -- in particular, defaults cannot be loaded. For a very small number of settings,
345 * we must define defaults before the system bootstraps.
347 * @param string $entity
351 private static function getSystemDefaults($entity) {
356 'installed' => FALSE,
357 'enable_components' => ['CiviEvent', 'CiviContribute', 'CiviMember', 'CiviMail', 'CiviReport', 'CiviPledge'],
358 'customFileUploadDir' => '[civicrm.files]/custom/',
359 'imageUploadDir' => '[civicrm.files]/persist/contribute/',
360 'uploadDir' => '[civicrm.files]/upload/',
361 'imageUploadURL' => '[civicrm.files]/persist/contribute/',
362 'extensionsDir' => '[civicrm.files]/ext/',
363 'extensionsURL' => '[civicrm.files]/ext/',
364 'resourceBase' => '[civicrm.root]/',
365 'userFrameworkResourceURL' => '[civicrm.root]/',