Merge pull request #15433 from aydun/add_contribution_is_template
[civicrm-core.git] / CRM / Core / Config / MagicMerge.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
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
33 * grew (with robust metadata, system overrides, extension support,
34 * and multi-tenancy), the $config started to store a mix of:
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 */
47 class 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
57 private $locals;
58 private $settings;
59
60 private $cache = [];
61
62 /**
63 * CRM_Core_Config_MagicMerge constructor.
64 */
65 public function __construct() {
66 $this->map = self::getPropertyMap();
67 }
68
69 /**
70 * Set the map to the property map.
71 */
72 public function __wakeup() {
73 $this->map = self::getPropertyMap();
74 }
75
76 /**
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 *
85 * @return array
86 */
87 public static function getPropertyMap() {
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.
91 return [
92 // "local" properties are unique to each instance of CRM_Core_Config (each request).
93 'doNotResetCache' => ['local'],
94 'inCiviCRM' => ['local'],
95 'keyDisable' => ['local'],
96 'userFrameworkFrontend' => ['local'],
97 'userPermissionTemp' => ['local'],
98
99 // "runtime" properties are computed from define()s, $_ENV, etc.
100 // See also: CRM_Core_Config_Runtime.
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 'templateDir' => ['runtime'],
110
111 // "boot-svc" properties are critical services needed during init.
112 // See also: Civi\Core\Container::getBootService().
113 'userSystem' => ['boot-svc'],
114 'userPermissionClass' => ['boot-svc'],
115
116 'userFrameworkBaseURL' => ['user-system', 'getAbsoluteBaseURL'],
117 'userFrameworkVersion' => ['user-system', 'getVersion'],
118 // ugh typo.
119 'useFrameworkRelativeBase' => ['user-system', 'getRelativeBaseURL'],
120
121 // "setting" properties are loaded through the setting layer, esp
122 // table "civicrm_setting" and global $civicrm_setting.
123 // See also: Civi::settings().
124 'backtrace' => ['setting'],
125 'contact_default_language' => ['setting'],
126 'countryLimit' => ['setting'],
127 'customTranslateFunction' => ['setting'],
128 'dateInputFormat' => ['setting'],
129 'dateformatDatetime' => ['setting'],
130 'dateformatFull' => ['setting'],
131 'dateformatPartial' => ['setting'],
132 'dateformatTime' => ['setting'],
133 'dateformatYear' => ['setting'],
134 'dateformatFinancialBatch' => ['setting'],
135 'dateformatshortdate' => ['setting'],
136 // renamed.
137 'debug' => ['setting', 'debug_enabled'],
138 'defaultContactCountry' => ['setting'],
139 'defaultContactStateProvince' => ['setting'],
140 'defaultCurrency' => ['setting'],
141 'defaultSearchProfileID' => ['setting'],
142 'doNotAttachPDFReceipt' => ['setting'],
143 'empoweredBy' => ['setting'],
144 // renamed.
145 'enableComponents' => ['setting', 'enable_components'],
146 'enableSSL' => ['setting'],
147 'fatalErrorHandler' => ['setting'],
148 'fieldSeparator' => ['setting'],
149 'fiscalYearStart' => ['setting'],
150 'geoAPIKey' => ['setting'],
151 'geoProvider' => ['setting'],
152 'includeAlphabeticalPager' => ['setting'],
153 'includeEmailInName' => ['setting'],
154 'includeNickNameInName' => ['setting'],
155 'includeOrderByClause' => ['setting'],
156 'includeWildCardInName' => ['setting'],
157 'inheritLocale' => ['setting'],
158 'languageLimit' => ['setting'],
159 'lcMessages' => ['setting'],
160 'legacyEncoding' => ['setting'],
161 'logging' => ['setting'],
162 'mailThrottleTime' => ['setting'],
163 'mailerBatchLimit' => ['setting'],
164 'mailerJobSize' => ['setting'],
165 'mailerJobsMax' => ['setting'],
166 'mapAPIKey' => ['setting'],
167 'mapProvider' => ['setting'],
168 'maxFileSize' => ['setting'],
169 // renamed.
170 'maxAttachments' => ['setting', 'max_attachments'],
171 'maxAttachmentsBackend' => ['setting', 'max_attachments_backend'],
172 'monetaryDecimalPoint' => ['setting'],
173 'monetaryThousandSeparator' => ['setting'],
174 'moneyformat' => ['setting'],
175 'moneyvalueformat' => ['setting'],
176 'provinceLimit' => ['setting'],
177 'recaptchaOptions' => ['setting'],
178 'recaptchaPublicKey' => ['setting'],
179 'recaptchaPrivateKey' => ['setting'],
180 'forceRecaptcha' => ['setting'],
181 'replyTo' => ['setting'],
182 'secondDegRelPermissions' => ['setting'],
183 'smartGroupCacheTimeout' => ['setting'],
184 'timeInputFormat' => ['setting'],
185 'userFrameworkLogging' => ['setting'],
186 'userFrameworkUsersTableName' => ['setting'],
187 'verpSeparator' => ['setting'],
188 'wkhtmltopdfPath' => ['setting'],
189 'wpBasePage' => ['setting'],
190 'wpLoadPhp' => ['setting'],
191
192 // "path" properties are managed via Civi::paths and $civicrm_paths
193 // Option: `mkdir` - auto-create dir
194 // Option: `restrict` - auto-restrict remote access
195 'configAndLogDir' => ['path', 'civicrm.log', ['mkdir', 'restrict']],
196 'templateCompileDir' => ['path', 'civicrm.compile', ['mkdir', 'restrict']],
197
198 // "setting-path" properties are settings with special filtering
199 // to return normalized file paths.
200 // Option: `mkdir` - auto-create dir
201 // Option: `restrict` - auto-restrict remote access
202 'customFileUploadDir' => ['setting-path', NULL, ['mkdir', 'restrict']],
203 'customPHPPathDir' => ['setting-path'],
204 'customTemplateDir' => ['setting-path'],
205 'extensionsDir' => ['setting-path', NULL, ['mkdir']],
206 'imageUploadDir' => ['setting-path', NULL, ['mkdir']],
207 'uploadDir' => ['setting-path', NULL, ['mkdir', 'restrict']],
208
209 // "setting-url" properties are settings with special filtering
210 // to return normalized URLs.
211 // Option: `noslash` - don't append trailing slash
212 // Option: `rel` - convert to relative URL (if possible)
213 'customCSSURL' => ['setting-url', NULL, ['noslash']],
214 'extensionsURL' => ['setting-url'],
215 'imageUploadURL' => ['setting-url'],
216 'resourceBase' => ['setting-url', 'userFrameworkResourceURL', ['rel']],
217 'userFrameworkResourceURL' => ['setting-url'],
218
219 // "callback" properties are generated on-demand by calling a function.
220 // @todo remove geocodeMethod. As of Feb 2018, $config->geocodeMethod works but gives a deprecation warning.
221 'geocodeMethod' => ['callback', 'CRM_Utils_Geocode', 'getProviderClass'],
222 'defaultCurrencySymbol' => ['callback', 'CRM_Core_BAO_Country', 'getDefaultCurrencySymbol'],
223 ];
224 }
225
226 /**
227 * Get value.
228 *
229 * @param string $k
230 *
231 * @return mixed
232 * @throws \CRM_Core_Exception
233 */
234 public function __get($k) {
235 if (!isset($this->map[$k])) {
236 throw new \CRM_Core_Exception("Cannot read unrecognized property CRM_Core_Config::\${$k}.");
237 }
238 if (isset($this->cache[$k])) {
239 return $this->cache[$k];
240 }
241
242 $type = $this->map[$k][0];
243 $name = isset($this->map[$k][1]) ? $this->map[$k][1] : $k;
244
245 switch ($type) {
246 case 'setting':
247 return $this->getSettings()->get($name);
248
249 // The interpretation of 'path' and 'setting-path' is similar, except
250 // that the latter originates in a stored setting.
251 case 'path':
252 case 'setting-path':
253 // Array(0 => $type, 1 => $setting, 2 => $actions).
254 $value = ($type === 'path')
255 ? Civi::paths()->getVariable($name, 'path')
256 : Civi::paths()->getPath($this->getSettings()->get($name));
257 if ($value) {
258 $value = CRM_Utils_File::addTrailingSlash($value);
259 if (isset($this->map[$k][2]) && in_array('mkdir', $this->map[$k][2])) {
260 if (!is_dir($value) && !CRM_Utils_File::createDir($value, FALSE)) {
261 CRM_Core_Session::setStatus(ts('Failed to make directory (%1) at "%2". Please update the settings or file permissions.', [
262 1 => $k,
263 2 => $value,
264 ]));
265 }
266 }
267 if (isset($this->map[$k][2]) && in_array('restrict', $this->map[$k][2])) {
268 CRM_Utils_File::restrictAccess($value);
269 }
270 }
271 $this->cache[$k] = $value;
272 return $value;
273
274 case 'setting-url':
275 $options = !empty($this->map[$k][2]) ? $this->map[$k][2] : [];
276 $value = $this->getSettings()->get($name);
277 if ($value && !(in_array('noslash', $options))) {
278 $value = CRM_Utils_File::addTrailingSlash($value, '/');
279 }
280 $this->cache[$k] = Civi::paths()->getUrl($value,
281 in_array('rel', $options) ? 'relative' : 'absolute');
282 return $this->cache[$k];
283
284 case 'runtime':
285 return \Civi\Core\Container::getBootService('runtime')->{$name};
286
287 case 'boot-svc':
288 $this->cache[$k] = \Civi\Core\Container::getBootService($name);
289 return $this->cache[$k];
290
291 case 'local':
292 $this->initLocals();
293 return $this->locals[$name];
294
295 case 'user-system':
296 $userSystem = \Civi\Core\Container::getBootService('userSystem');
297 $this->cache[$k] = call_user_func([$userSystem, $name]);
298 return $this->cache[$k];
299
300 case 'service':
301 return \Civi::service($name);
302
303 case 'callback':
304 // Array(0 => $type, 1 => $obj, 2 => $getter, 3 => $setter, 4 => $unsetter).
305 if (!isset($this->map[$k][1], $this->map[$k][2])) {
306 throw new \CRM_Core_Exception("Cannot find getter for property CRM_Core_Config::\${$k}");
307 }
308 return \Civi\Core\Resolver::singleton()->call([$this->map[$k][1], $this->map[$k][2]], [$k]);
309
310 default:
311 throw new \CRM_Core_Exception("Cannot read property CRM_Core_Config::\${$k} ($type)");
312 }
313 }
314
315 /**
316 * Set value.
317 *
318 * @param string $k
319 * @param mixed $v
320 *
321 * @throws \CRM_Core_Exception
322 */
323 public function __set($k, $v) {
324 if (!isset($this->map[$k])) {
325 throw new \CRM_Core_Exception("Cannot set unrecognized property CRM_Core_Config::\${$k}");
326 }
327 unset($this->cache[$k]);
328 $type = $this->map[$k][0];
329
330 switch ($type) {
331 case 'setting':
332 case 'setting-path':
333 case 'setting-url':
334 case 'path':
335 case 'user-system':
336 case 'runtime':
337 case 'callback':
338 case 'boot-svc':
339 // In the past, changes to $config were not persisted automatically.
340 $this->cache[$k] = $v;
341 return;
342
343 case 'local':
344 $this->initLocals();
345 $this->locals[$k] = $v;
346 return;
347
348 default:
349 throw new \CRM_Core_Exception("Cannot set property CRM_Core_Config::\${$k} ($type)");
350 }
351 }
352
353 /**
354 * Is value set.
355 *
356 * @param string $k
357 *
358 * @return bool
359 */
360 public function __isset($k) {
361 return isset($this->map[$k]);
362 }
363
364 /**
365 * Unset value.
366 *
367 * @param string $k
368 *
369 * @throws \CRM_Core_Exception
370 */
371 public function __unset($k) {
372 if (!isset($this->map[$k])) {
373 throw new \CRM_Core_Exception("Cannot unset unrecognized property CRM_Core_Config::\${$k}");
374 }
375 unset($this->cache[$k]);
376 $type = $this->map[$k][0];
377 $name = isset($this->map[$k][1]) ? $this->map[$k][1] : $k;
378
379 switch ($type) {
380 case 'setting':
381 case 'setting-path':
382 case 'setting-url':
383 $this->getSettings()->revert($k);
384 return;
385
386 case 'local':
387 $this->initLocals();
388 $this->locals[$name] = NULL;
389 return;
390
391 case 'callback':
392 // Array(0 => $type, 1 => $obj, 2 => $getter, 3 => $setter, 4 => $unsetter).
393 if (!isset($this->map[$k][1], $this->map[$k][4])) {
394 throw new \CRM_Core_Exception("Cannot find unsetter for property CRM_Core_Config::\${$k}");
395 }
396 \Civi\Core\Resolver::singleton()->call([$this->map[$k][1], $this->map[$k][4]], [$k]);
397 return;
398
399 default:
400 throw new \CRM_Core_Exception("Cannot unset property CRM_Core_Config::\${$k} ($type)");
401 }
402 }
403
404 /**
405 * @return \Civi\Core\SettingsBag
406 */
407 protected function getSettings() {
408 if ($this->settings === NULL) {
409 $this->settings = Civi::settings();
410 }
411 return $this->settings;
412 }
413
414 /**
415 * Initialise local settings.
416 */
417 private function initLocals() {
418 if ($this->locals === NULL) {
419 $this->locals = [
420 'inCiviCRM' => FALSE,
421 'doNotResetCache' => 0,
422 'keyDisable' => FALSE,
423 'initialized' => FALSE,
424 'userFrameworkFrontend' => FALSE,
425 'userPermissionTemp' => NULL,
426 ];
427 }
428 }
429
430 }