Merge pull request #14050 from eileenmcnaughton/import
[civicrm-core.git] / CRM / Core / Config / MagicMerge.php
CommitLineData
c0a1f187
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
fee14197 4 | CiviCRM version 5 |
c0a1f187 5 +--------------------------------------------------------------------+
6b83d5bd 6 | Copyright CiviCRM LLC (c) 2004-2019 |
c0a1f187
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
28/**
29 * Class CRM_Core_Config_MagicMerge
30 *
31 * Originally, the $config object was based on a single, serialized
32 * data object stored in the database. As the needs for settings
9e1e6020
TO
33 * grew (with robust metadata, system overrides, extension support,
34 * and multi-tenancy), the $config started to store a mix of:
c0a1f187
TO
35 * (a) canonical config options,
36 * (b) dynamically generated runtime data,
37 * (c) cached data derived from other sources (esp civicrm_setting)
38 * (d) instances of service objects
39 *
40 * The config object is now deprecated. Settings and service objects
41 * should generally be accessed via Civi::settings() and Civi::service().
42 *
43 * MagicMerge provides backward compatibility. You may still access
44 * old properties via $config, but they will be loaded from their
45 * new services.
46 */
47class CRM_Core_Config_MagicMerge {
48
49 /**
50 * Map old config properties to their contemporary counterparts.
51 *
52 * @var array
53 * Array(string $configAlias => Array(string $realType, string $realName)).
54 */
55 private $map;
56
518fa0ee
SL
57 private $locals;
58 private $settings;
c0a1f187 59
be2fb01f 60 private $cache = [];
7dba62da 61
8246bca4 62 /**
63 * CRM_Core_Config_MagicMerge constructor.
64 */
c0a1f187
TO
65 public function __construct() {
66 $this->map = self::getPropertyMap();
67 }
68
8246bca4 69 /**
70 * Set the map to the property map.
71 */
c0a1f187
TO
72 public function __wakeup() {
73 $this->map = self::getPropertyMap();
74 }
75
76 /**
f806379b
TO
77 * Get a list of $config properties and the entities to which they map.
78 *
79 * This is used for two purposes:
80 *
81 * 1. Runtime: Provide backward-compatible interface for reading these
82 * properties.
83 * 2. Upgrade: Migrate old properties of config_backend into settings.
84 *
c0a1f187
TO
85 * @return array
86 */
87 public static function getPropertyMap() {
e3d28c74
TO
88 // Each mapping: $propertyName => Array(0 => $type, 1 => $foreignName|NULL, ...).
89 // If $foreignName is omitted/null, then it's assumed to match the $propertyName.
90 // Other parameters may be specified, depending on the type.
be2fb01f 91 return [
9e1e6020 92 // "local" properties are unique to each instance of CRM_Core_Config (each request).
be2fb01f
CW
93 'doNotResetCache' => ['local'],
94 'inCiviCRM' => ['local'],
95 'keyDisable' => ['local'],
96 'userFrameworkFrontend' => ['local'],
97 'userPermissionTemp' => ['local'],
9e1e6020
TO
98
99 // "runtime" properties are computed from define()s, $_ENV, etc.
100 // See also: CRM_Core_Config_Runtime.
be2fb01f
CW
101 'dsn' => ['runtime'],
102 'initialized' => ['runtime'],
103 'userFramework' => ['runtime'],
104 'userFrameworkClass' => ['runtime'],
105 'userFrameworkDSN' => ['runtime'],
106 'userFrameworkURLVar' => ['runtime'],
107 'userHookClass' => ['runtime'],
108 'cleanURL' => ['runtime'],
109 'configAndLogDir' => ['runtime'],
110 'templateCompileDir' => ['runtime'],
111 'templateDir' => ['runtime'],
9e1e6020 112
d4330c62 113 // "boot-svc" properties are critical services needed during init.
7f835399 114 // See also: Civi\Core\Container::getBootService().
be2fb01f
CW
115 'userSystem' => ['boot-svc'],
116 'userPermissionClass' => ['boot-svc'],
d4330c62 117
be2fb01f
CW
118 'userFrameworkBaseURL' => ['user-system', 'getAbsoluteBaseURL'],
119 'userFrameworkVersion' => ['user-system', 'getVersion'],
518fa0ee
SL
120 // ugh typo.
121 'useFrameworkRelativeBase' => ['user-system', 'getRelativeBaseURL'],
d4330c62 122
9e1e6020
TO
123 // "setting" properties are loaded through the setting layer, esp
124 // table "civicrm_setting" and global $civicrm_setting.
125 // See also: Civi::settings().
be2fb01f
CW
126 'backtrace' => ['setting'],
127 'contact_default_language' => ['setting'],
128 'countryLimit' => ['setting'],
129 'customTranslateFunction' => ['setting'],
130 'dateInputFormat' => ['setting'],
131 'dateformatDatetime' => ['setting'],
132 'dateformatFull' => ['setting'],
133 'dateformatPartial' => ['setting'],
134 'dateformatTime' => ['setting'],
135 'dateformatYear' => ['setting'],
136 'dateformatFinancialBatch' => ['setting'],
137 'dateformatshortdate' => ['setting'],
518fa0ee
SL
138 // renamed.
139 'debug' => ['setting', 'debug_enabled'],
be2fb01f
CW
140 'defaultContactCountry' => ['setting'],
141 'defaultContactStateProvince' => ['setting'],
142 'defaultCurrency' => ['setting'],
143 'defaultSearchProfileID' => ['setting'],
144 'doNotAttachPDFReceipt' => ['setting'],
145 'empoweredBy' => ['setting'],
518fa0ee
SL
146 // renamed.
147 'enableComponents' => ['setting', 'enable_components'],
be2fb01f
CW
148 'enableSSL' => ['setting'],
149 'fatalErrorHandler' => ['setting'],
150 'fieldSeparator' => ['setting'],
151 'fiscalYearStart' => ['setting'],
152 'geoAPIKey' => ['setting'],
153 'geoProvider' => ['setting'],
154 'includeAlphabeticalPager' => ['setting'],
155 'includeEmailInName' => ['setting'],
156 'includeNickNameInName' => ['setting'],
157 'includeOrderByClause' => ['setting'],
158 'includeWildCardInName' => ['setting'],
159 'inheritLocale' => ['setting'],
160 'languageLimit' => ['setting'],
161 'lcMessages' => ['setting'],
162 'legacyEncoding' => ['setting'],
163 'logging' => ['setting'],
164 'mailThrottleTime' => ['setting'],
165 'mailerBatchLimit' => ['setting'],
166 'mailerJobSize' => ['setting'],
167 'mailerJobsMax' => ['setting'],
168 'mapAPIKey' => ['setting'],
169 'mapProvider' => ['setting'],
170 'maxFileSize' => ['setting'],
518fa0ee
SL
171 // renamed.
172 'maxAttachments' => ['setting', 'max_attachments'],
be2fb01f
CW
173 'monetaryDecimalPoint' => ['setting'],
174 'monetaryThousandSeparator' => ['setting'],
175 'moneyformat' => ['setting'],
176 'moneyvalueformat' => ['setting'],
177 'provinceLimit' => ['setting'],
178 'recaptchaOptions' => ['setting'],
179 'recaptchaPublicKey' => ['setting'],
180 'recaptchaPrivateKey' => ['setting'],
181 'forceRecaptcha' => ['setting'],
182 'replyTo' => ['setting'],
183 'secondDegRelPermissions' => ['setting'],
184 'smartGroupCacheTimeout' => ['setting'],
185 'timeInputFormat' => ['setting'],
186 'userFrameworkLogging' => ['setting'],
187 'userFrameworkUsersTableName' => ['setting'],
188 'verpSeparator' => ['setting'],
189 'wkhtmltopdfPath' => ['setting'],
190 'wpBasePage' => ['setting'],
191 'wpLoadPhp' => ['setting'],
e3d28c74 192
9e1e6020
TO
193 // "setting-path" properties are settings with special filtering
194 // to return normalized file paths.
cdbea577
C
195 // Option: `mkdir` - auto-create dir
196 // Option: `restrict` - auto-restrict remote access
be2fb01f
CW
197 'customFileUploadDir' => ['setting-path', NULL, ['mkdir', 'restrict']],
198 'customPHPPathDir' => ['setting-path'],
199 'customTemplateDir' => ['setting-path'],
200 'extensionsDir' => ['setting-path', NULL, ['mkdir']],
201 'imageUploadDir' => ['setting-path', NULL, ['mkdir']],
202 'uploadDir' => ['setting-path', NULL, ['mkdir', 'restrict']],
e3d28c74 203
cdbea577
C
204 // "setting-url" properties are settings with special filtering
205 // to return normalized URLs.
206 // Option: `noslash` - don't append trailing slash
207 // Option: `rel` - convert to relative URL (if possible)
be2fb01f
CW
208 'customCSSURL' => ['setting-url', NULL, ['noslash']],
209 'extensionsURL' => ['setting-url'],
210 'imageUploadURL' => ['setting-url'],
211 'resourceBase' => ['setting-url', 'userFrameworkResourceURL', ['rel']],
212 'userFrameworkResourceURL' => ['setting-url'],
c0a1f187 213
9e1e6020 214 // "callback" properties are generated on-demand by calling a function.
0d1823d7 215 // @todo remove geocodeMethod. As of Feb 2018, $config->geocodeMethod works but gives a deprecation warning.
be2fb01f
CW
216 'geocodeMethod' => ['callback', 'CRM_Utils_Geocode', 'getProviderClass'],
217 'defaultCurrencySymbol' => ['callback', 'CRM_Core_BAO_Country', 'getDefaultCurrencySymbol'],
218 ];
c0a1f187
TO
219 }
220
8246bca4 221 /**
222 * Get value.
223 *
224 * @param string $k
225 *
226 * @return mixed
227 * @throws \CRM_Core_Exception
228 */
c0a1f187
TO
229 public function __get($k) {
230 if (!isset($this->map[$k])) {
231 throw new \CRM_Core_Exception("Cannot read unrecognized property CRM_Core_Config::\${$k}.");
232 }
fd78a9e5 233 if (isset($this->cache[$k])) {
7dba62da
TO
234 return $this->cache[$k];
235 }
e3d28c74
TO
236
237 $type = $this->map[$k][0];
238 $name = isset($this->map[$k][1]) ? $this->map[$k][1] : $k;
c0a1f187
TO
239
240 switch ($type) {
241 case 'setting':
23bb9c85 242 return $this->getSettings()->get($name);
c0a1f187
TO
243
244 case 'setting-path':
ac47f7ca 245 // Array(0 => $type, 1 => $setting, 2 => $actions).
e3d28c74 246 $value = $this->getSettings()->get($name);
e3d28c74
TO
247 $value = Civi::paths()->getPath($value);
248 if ($value) {
249 $value = CRM_Utils_File::addTrailingSlash($value);
ac47f7ca 250 if (isset($this->map[$k][2]) && in_array('mkdir', $this->map[$k][2])) {
f7cc6e55 251 if (!is_dir($value) && !CRM_Utils_File::createDir($value, FALSE)) {
be2fb01f 252 CRM_Core_Session::setStatus(ts('Failed to make directory (%1) at "%2". Please update the settings or file permissions.', [
895c19e7
TO
253 1 => $k,
254 2 => $value,
be2fb01f 255 ]));
895c19e7 256 }
e3d28c74 257 }
ac47f7ca 258 if (isset($this->map[$k][2]) && in_array('restrict', $this->map[$k][2])) {
e3d28c74
TO
259 CRM_Utils_File::restrictAccess($value);
260 }
261 }
7dba62da 262 $this->cache[$k] = $value;
e3d28c74
TO
263 return $value;
264
cdbea577 265 case 'setting-url':
be2fb01f 266 $options = !empty($this->map[$k][2]) ? $this->map[$k][2] : [];
6ec7e639 267 $value = $this->getSettings()->get($name);
cdbea577
C
268 if ($value && !(in_array('noslash', $options))) {
269 $value = CRM_Utils_File::addTrailingSlash($value, '/');
6ec7e639 270 }
cdbea577
C
271 $this->cache[$k] = Civi::paths()->getUrl($value,
272 in_array('rel', $options) ? 'relative' : 'absolute');
7dba62da 273 return $this->cache[$k];
c0a1f187
TO
274
275 case 'runtime':
7f835399 276 return \Civi\Core\Container::getBootService('runtime')->{$name};
c0a1f187 277
d4330c62
TO
278 case 'boot-svc':
279 $this->cache[$k] = \Civi\Core\Container::getBootService($name);
280 return $this->cache[$k];
281
c0a1f187
TO
282 case 'local':
283 $this->initLocals();
284 return $this->locals[$name];
285
d4330c62
TO
286 case 'user-system':
287 $userSystem = \Civi\Core\Container::getBootService('userSystem');
be2fb01f 288 $this->cache[$k] = call_user_func([$userSystem, $name]);
d4330c62
TO
289 return $this->cache[$k];
290
c0a1f187
TO
291 case 'service':
292 return \Civi::service($name);
293
294 case 'callback':
295 // Array(0 => $type, 1 => $obj, 2 => $getter, 3 => $setter, 4 => $unsetter).
296 if (!isset($this->map[$k][1], $this->map[$k][2])) {
297 throw new \CRM_Core_Exception("Cannot find getter for property CRM_Core_Config::\${$k}");
298 }
be2fb01f 299 return \Civi\Core\Resolver::singleton()->call([$this->map[$k][1], $this->map[$k][2]], [$k]);
c0a1f187
TO
300
301 default:
302 throw new \CRM_Core_Exception("Cannot read property CRM_Core_Config::\${$k} ($type)");
303 }
304 }
305
8246bca4 306 /**
307 * Set value.
308 *
309 * @param string $k
310 * @param mixed $v
311 *
312 * @throws \CRM_Core_Exception
313 */
c0a1f187
TO
314 public function __set($k, $v) {
315 if (!isset($this->map[$k])) {
316 throw new \CRM_Core_Exception("Cannot set unrecognized property CRM_Core_Config::\${$k}");
317 }
7dba62da 318 unset($this->cache[$k]);
dc640254 319 $type = $this->map[$k][0];
82eff07b 320
82eff07b
JM
321 // If foreign name is set, use that name (except with callback types because
322 // their second parameter is the object, not the foreign name).
323 $name = isset($this->map[$k][1]) && $type != 'callback' ? $this->map[$k][1] : $k;
c0a1f187
TO
324
325 switch ($type) {
326 case 'setting':
dc640254 327 case 'setting-path':
cdbea577 328 case 'setting-url':
d4330c62 329 case 'user-system':
c0a1f187 330 case 'runtime':
dc640254 331 case 'callback':
6504e96d 332 case 'boot-svc':
dc640254
TO
333 // In the past, changes to $config were not persisted automatically.
334 $this->cache[$name] = $v;
c0a1f187
TO
335 return;
336
337 case 'local':
338 $this->initLocals();
339 $this->locals[$name] = $v;
340 return;
341
c0a1f187
TO
342 default:
343 throw new \CRM_Core_Exception("Cannot set property CRM_Core_Config::\${$k} ($type)");
344 }
345 }
346
8246bca4 347 /**
348 * Is value set.
349 *
350 * @param string $k
351 *
352 * @return bool
353 */
c0a1f187
TO
354 public function __isset($k) {
355 return isset($this->map[$k]);
356 }
357
8246bca4 358 /**
359 * Unset value.
360 *
361 * @param string $k
362 *
363 * @throws \CRM_Core_Exception
364 */
c0a1f187
TO
365 public function __unset($k) {
366 if (!isset($this->map[$k])) {
367 throw new \CRM_Core_Exception("Cannot unset unrecognized property CRM_Core_Config::\${$k}");
368 }
7dba62da 369 unset($this->cache[$k]);
dc640254
TO
370 $type = $this->map[$k][0];
371 $name = isset($this->map[$k][1]) ? $this->map[$k][1] : $k;
c0a1f187
TO
372
373 switch ($type) {
374 case 'setting':
375 case 'setting-path':
cdbea577 376 case 'setting-url':
23bb9c85 377 $this->getSettings()->revert($k);
c0a1f187
TO
378 return;
379
380 case 'local':
381 $this->initLocals();
382 $this->locals[$name] = NULL;
383 return;
384
385 case 'callback':
386 // Array(0 => $type, 1 => $obj, 2 => $getter, 3 => $setter, 4 => $unsetter).
387 if (!isset($this->map[$k][1], $this->map[$k][4])) {
388 throw new \CRM_Core_Exception("Cannot find unsetter for property CRM_Core_Config::\${$k}");
389 }
be2fb01f 390 \Civi\Core\Resolver::singleton()->call([$this->map[$k][1], $this->map[$k][4]], [$k]);
c0a1f187
TO
391 return;
392
393 default:
394 throw new \CRM_Core_Exception("Cannot unset property CRM_Core_Config::\${$k} ($type)");
395 }
396 }
397
23bb9c85
TO
398 /**
399 * @return \Civi\Core\SettingsBag
400 */
401 protected function getSettings() {
402 if ($this->settings === NULL) {
403 $this->settings = Civi::settings();
404 }
405 return $this->settings;
406 }
407
8246bca4 408 /**
409 * Initialise local settings.
410 */
c0a1f187
TO
411 private function initLocals() {
412 if ($this->locals === NULL) {
be2fb01f 413 $this->locals = [
c0a1f187
TO
414 'inCiviCRM' => FALSE,
415 'doNotResetCache' => 0,
5f900b59 416 'keyDisable' => FALSE,
c0a1f187
TO
417 'initialized' => FALSE,
418 'userFrameworkFrontend' => FALSE,
d4330c62 419 'userPermissionTemp' => NULL,
be2fb01f 420 ];
c0a1f187
TO
421 }
422 }
423
424}