Merge pull request #8916 from cividesk/CRM-19256-4.7
[civicrm-core.git] / Civi / Core / SettingsManager.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2017 |
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
28 namespace Civi\Core;
29
30 /**
31 * Class SettingsManager
32 * @package Civi\Core
33 *
34 * The SettingsManager is responsible for tracking settings across various
35 * domains and users.
36 *
37 * Generally, for any given setting, there are three levels where values
38 * can be declared:
39 *
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).
43 *
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.
48 *
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
52 * at the same time.
53 *
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
58 * $civicrm_setting.
59 *
60 * @see SettingsManagerTest
61 */
62 class SettingsManager {
63
64 /**
65 * @var \CRM_Utils_Cache_Interface
66 */
67 protected $cache;
68
69 /**
70 * @var
71 * Array (int $id => SettingsBag $bag).
72 */
73 protected $bagsByDomain = array(), $bagsByContact = array();
74
75 /**
76 * @var array|NULL
77 * Array(string $entity => array(string $settingName => mixed $value)).
78 * Ex: $mandatory['domain']['uploadDir'].
79 * NULL means "autoload from $civicrm_setting".
80 */
81 protected $mandatory = NULL;
82
83 /**
84 * Whether to use defaults.
85 *
86 * @var bool
87 */
88 protected $useDefaults = FALSE;
89
90 /**
91 * @param \CRM_Utils_Cache_Interface $cache
92 * A semi-durable location to store metadata.
93 */
94 public function __construct($cache) {
95 $this->cache = $cache;
96 }
97
98 /**
99 * Ensure that all defaults values are included with
100 * all current and future bags.
101 *
102 * @return SettingsManager
103 */
104 public function useDefaults() {
105 if (!$this->useDefaults) {
106 $this->useDefaults = TRUE;
107
108 if (!empty($this->bagsByDomain)) {
109 foreach ($this->bagsByDomain as $bag) {
110 /** @var SettingsBag $bag */
111 $bag->loadDefaults($this->getDefaults('domain'));
112 }
113 }
114
115 if (!empty($this->bagsByContact)) {
116 foreach ($this->bagsByContact as $bag) {
117 /** @var SettingsBag $bag */
118 $bag->loadDefaults($this->getDefaults('contact'));
119 }
120 }
121 }
122
123 return $this;
124 }
125
126 /**
127 * Ensure that mandatory values are included with
128 * all current and future bags.
129 *
130 * If you call useMandatory multiple times, it will
131 * re-scan the global $civicrm_setting.
132 *
133 * @return SettingsManager
134 */
135 public function useMandatory() {
136 $this->mandatory = NULL;
137
138 foreach ($this->bagsByDomain as $bag) {
139 /** @var SettingsBag $bag */
140 $bag->loadMandatory($this->getMandatory('domain'));
141 }
142
143 foreach ($this->bagsByContact as $bag) {
144 /** @var SettingsBag $bag */
145 $bag->loadMandatory($this->getMandatory('contact'));
146 }
147
148 return $this;
149 }
150
151 /**
152 * @param int|NULL $domainId
153 * @return SettingsBag
154 */
155 public function getBagByDomain($domainId) {
156 if ($domainId === NULL) {
157 $domainId = \CRM_Core_Config::domainID();
158 }
159
160 if (!isset($this->bagsByDomain[$domainId])) {
161 $this->bagsByDomain[$domainId] = new SettingsBag($domainId, NULL);
162 if (\CRM_Core_Config::singleton()->dsn) {
163 $this->bagsByDomain[$domainId]->loadValues();
164 }
165 $this->bagsByDomain[$domainId]
166 ->loadMandatory($this->getMandatory('domain'))
167 ->loadDefaults($this->getDefaults('domain'));
168 }
169 return $this->bagsByDomain[$domainId];
170 }
171
172 /**
173 * @param int|NULL $domainId
174 * @param int|NULL $contactId
175 * @return SettingsBag
176 */
177 public function getBagByContact($domainId, $contactId) {
178 if ($domainId === NULL) {
179 $domainId = \CRM_Core_Config::domainID();
180 }
181
182 $key = "$domainId:$contactId";
183 if (!isset($this->bagsByContact[$key])) {
184 $this->bagsByContact[$key] = new SettingsBag($domainId, $contactId);
185 if (\CRM_Core_Config::singleton()->dsn) {
186 $this->bagsByContact[$key]->loadValues();
187 }
188 $this->bagsByContact[$key]
189 ->loadDefaults($this->getDefaults('contact'))
190 ->loadMandatory($this->getMandatory('contact'));
191 }
192 return $this->bagsByContact[$key];
193 }
194
195 /**
196 * Determine the default settings.
197 *
198 * @param string $entity
199 * Ex: 'domain' or 'contact'.
200 * @return array
201 * Array(string $settingName => mixed $value).
202 */
203 protected function getDefaults($entity) {
204 if (!$this->useDefaults) {
205 return self::getSystemDefaults($entity);
206 }
207
208 $cacheKey = 'defaults:' . $entity;
209 $defaults = $this->cache->get($cacheKey);
210 if (!is_array($defaults)) {
211 $specs = SettingsMetadata::getMetadata(array(
212 'is_contact' => ($entity === 'contact' ? 1 : 0),
213 ));
214 $defaults = array();
215 foreach ($specs as $key => $spec) {
216 $defaults[$key] = \CRM_Utils_Array::value('default', $spec);
217 }
218 \CRM_Utils_Array::extend($defaults, self::getSystemDefaults($entity));
219 $this->cache->set($cacheKey, $defaults);
220 }
221 return $defaults;
222 }
223
224 /**
225 * Get a list of mandatory/overriden settings.
226 *
227 * @param string $entity
228 * Ex: 'domain' or 'contact'.
229 * @return array
230 * Array(string $settingName => mixed $value).
231 */
232 protected function getMandatory($entity) {
233 if ($this->mandatory === NULL) {
234 $this->mandatory = self::parseMandatorySettings(\CRM_Utils_Array::value('civicrm_setting', $GLOBALS));
235 }
236 return $this->mandatory[$entity];
237 }
238
239 /**
240 * Parse mandatory settings.
241 *
242 * In previous versions, settings were broken down into verbose+dynamic group names, e.g.
243 *
244 * $civicrm_settings['Foo Bar Preferences']['foo'] = 'bar';
245 *
246 * We now simplify to two simple groups, 'domain' and 'contact'.
247 *
248 * $civicrm_settings['domain']['foo'] = 'bar';
249 *
250 * However, the old groups are grand-fathered in as aliases.
251 *
252 * @param array $civicrm_setting
253 * Ex: $civicrm_setting['Group Name']['field'] = 'value'.
254 * Group names are an historical quirk; ignore them.
255 * @return array
256 */
257 public static function parseMandatorySettings($civicrm_setting) {
258 $result = array(
259 'domain' => array(),
260 'contact' => array(),
261 );
262
263 $rewriteGroups = array(
264 //\CRM_Core_BAO_Setting::ADDRESS_STANDARDIZATION_PREFERENCES_NAME => 'domain',
265 //\CRM_Core_BAO_Setting::CAMPAIGN_PREFERENCES_NAME => 'domain',
266 //\CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME => 'domain',
267 //\CRM_Core_BAO_Setting::DEVELOPER_PREFERENCES_NAME => 'domain',
268 //\CRM_Core_BAO_Setting::DIRECTORY_PREFERENCES_NAME => 'domain',
269 //\CRM_Core_BAO_Setting::EVENT_PREFERENCES_NAME => 'domain',
270 //\CRM_Core_BAO_Setting::LOCALIZATION_PREFERENCES_NAME => 'domain',
271 //\CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME => 'domain',
272 //\CRM_Core_BAO_Setting::MAP_PREFERENCES_NAME => 'domain',
273 //\CRM_Core_BAO_Setting::MEMBER_PREFERENCES_NAME => 'domain',
274 //\CRM_Core_BAO_Setting::MULTISITE_PREFERENCES_NAME => 'domain',
275 //\CRM_Core_BAO_Setting::PERSONAL_PREFERENCES_NAME => 'contact',
276 'Personal Preferences' => 'contact',
277 //\CRM_Core_BAO_Setting::SEARCH_PREFERENCES_NAME => 'domain',
278 //\CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME => 'domain',
279 //\CRM_Core_BAO_Setting::URL_PREFERENCES_NAME => 'domain',
280 'domain' => 'domain',
281 'contact' => 'contact',
282 );
283
284 if (is_array($civicrm_setting)) {
285 foreach ($civicrm_setting as $oldGroup => $values) {
286 $newGroup = isset($rewriteGroups[$oldGroup]) ? $rewriteGroups[$oldGroup] : 'domain';
287 $result[$newGroup] = array_merge($result[$newGroup], $values);
288 }
289 }
290 return $result;
291 }
292
293 /**
294 * Flush all in-memory and persistent caches related to settings.
295 *
296 * @return SettingsManager
297 */
298 public function flush() {
299 $this->mandatory = NULL;
300
301 $this->cache->flush();
302 \Civi::cache('settings')->flush(); // SettingsMetadata; not guaranteed to use same cache.
303
304 foreach ($this->bagsByDomain as $bag) {
305 /** @var SettingsBag $bag */
306 $bag->loadValues();
307 $bag->loadDefaults($this->getDefaults('domain'));
308 $bag->loadMandatory($this->getMandatory('domain'));
309 }
310
311 foreach ($this->bagsByContact as $bag) {
312 /** @var SettingsBag $bag */
313 $bag->loadValues();
314 $bag->loadDefaults($this->getDefaults('contact'));
315 $bag->loadMandatory($this->getMandatory('contact'));
316 }
317
318 return $this;
319 }
320
321 /**
322 * Get a list of critical system defaults.
323 *
324 * The setting system can be modified by extensions, which means that it's not fully available
325 * during bootstrap -- in particular, defaults cannot be loaded. For a very small number of settings,
326 * we must define defaults before the system bootstraps.
327 *
328 * @param string $entity
329 *
330 * @return array
331 */
332 private static function getSystemDefaults($entity) {
333 $defaults = array();
334 switch ($entity) {
335 case 'domain':
336 $defaults = array(
337 'installed' => FALSE,
338 'enable_components' => array('CiviEvent', 'CiviContribute', 'CiviMember', 'CiviMail', 'CiviReport', 'CiviPledge'),
339 'customFileUploadDir' => '[civicrm.files]/custom/',
340 'imageUploadDir' => '[civicrm.files]/persist/contribute/',
341 'uploadDir' => '[civicrm.files]/upload/',
342 'imageUploadURL' => '[civicrm.files]/persist/contribute/',
343 'extensionsDir' => '[civicrm.files]/ext/',
344 'extensionsURL' => '[civicrm.files]/ext/',
345 'resourceBase' => '[civicrm.root]/',
346 'userFrameworkResourceURL' => '[civicrm.root]/',
347 );
348 break;
349
350 }
351 return $defaults;
352 }
353
354 }