Merge in 5.20
[civicrm-core.git] / CRM / Core / Config.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
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 +--------------------------------------------------------------------+
10 */
11
12 /**
13 * Config handles all the run time configuration changes that the system needs to deal with.
14 *
15 * Typically we'll have different values for a user's sandbox, a qa sandbox and a production area.
16 * The default values in general, should reflect production values (minimizes chances of screwing up)
17 *
18 * @package CRM
19 * @copyright CiviCRM LLC https://civicrm.org/licensing
20 */
21
22 require_once 'Log.php';
23 require_once 'Mail.php';
24
25 require_once 'api/api.php';
26
27 /**
28 * Class CRM_Core_Config
29 *
30 * @property CRM_Utils_System_Base $userSystem
31 * @property CRM_Core_Permission_Base $userPermissionClass
32 * @property array $enableComponents
33 * @property array $languageLimit
34 * @property bool $debug
35 * @property bool $doNotResetCache
36 * @property string $maxFileSize
37 * @property string $defaultCurrency
38 * @property string $defaultCurrencySymbol
39 * @property string $lcMessages
40 * @property string $fieldSeparator
41 * @property string $userFramework
42 * @property string $verpSeparator
43 * @property string $dateFormatFull
44 * @property string $resourceBase
45 * @property string $dsn
46 * @property string $customTemplateDir
47 * @property string $defaultContactCountry
48 * @property string $defaultContactStateProvince
49 * @property string $monetaryDecimalPoint
50 * @property string $monetaryThousandSeparator
51 * @property array fiscalYearStart
52 */
53 class CRM_Core_Config extends CRM_Core_Config_MagicMerge {
54
55 /**
56 * The handle to the log that we are using
57 * @var object
58 */
59 private static $_log = NULL;
60
61 /**
62 * We only need one instance of this object. So we use the singleton
63 * pattern and cache the instance in this variable
64 *
65 * @var CRM_Core_Config
66 */
67 private static $_singleton = NULL;
68
69 /**
70 * The constructor. Sets domain id if defined, otherwise assumes
71 * single instance installation.
72 */
73 public function __construct() {
74 parent::__construct();
75 }
76
77 /**
78 * Singleton function used to manage this object.
79 *
80 * @param bool $loadFromDB
81 * whether to load from the database.
82 * @param bool $force
83 * whether to force a reconstruction.
84 *
85 * @return CRM_Core_Config
86 */
87 public static function &singleton($loadFromDB = TRUE, $force = FALSE) {
88 if (self::$_singleton === NULL || $force) {
89 $GLOBALS['civicrm_default_error_scope'] = CRM_Core_TemporaryErrorScope::create(['CRM_Core_Error', 'handle']);
90 $errorScope = CRM_Core_TemporaryErrorScope::create(['CRM_Core_Error', 'simpleHandler']);
91
92 if (defined('E_DEPRECATED')) {
93 error_reporting(error_reporting() & ~E_DEPRECATED);
94 }
95
96 self::$_singleton = new CRM_Core_Config();
97 \Civi\Core\Container::boot($loadFromDB);
98 if ($loadFromDB && self::$_singleton->dsn) {
99 $domain = \CRM_Core_BAO_Domain::getDomain();
100 \CRM_Core_BAO_ConfigSetting::applyLocale(\Civi::settings($domain->id), $domain->locales);
101
102 unset($errorScope);
103
104 CRM_Utils_Hook::config(self::$_singleton);
105 self::$_singleton->authenticate();
106
107 // Extreme backward compat: $config binds to active domain at moment of setup.
108 self::$_singleton->getSettings();
109
110 Civi::service('settings_manager')->useDefaults();
111
112 self::$_singleton->handleFirstRun();
113 }
114 }
115 return self::$_singleton;
116 }
117
118 /**
119 * Returns the singleton logger for the application.
120 *
121 * @deprecated
122 * @return object
123 * @see Civi::log()
124 */
125 public static function &getLog() {
126 if (!isset(self::$_log)) {
127 self::$_log = Log::singleton('display');
128 }
129
130 return self::$_log;
131 }
132
133 /**
134 * Retrieve a mailer to send any mail from the application.
135 *
136 * @return Mail
137 * @deprecated
138 * @see Civi::service()
139 */
140 public static function getMailer() {
141 return Civi::service('pear_mail');
142 }
143
144 /**
145 * Deletes the web server writable directories.
146 *
147 * @param int $value
148 * 1: clean templates_c, 2: clean upload, 3: clean both
149 * @param bool $rmdir
150 */
151 public function cleanup($value, $rmdir = TRUE) {
152 $value = (int ) $value;
153
154 if ($value & 1) {
155 // clean templates_c
156 CRM_Utils_File::cleanDir($this->templateCompileDir, $rmdir);
157 CRM_Utils_File::createDir($this->templateCompileDir);
158 }
159 if ($value & 2) {
160 // clean upload dir
161 CRM_Utils_File::cleanDir($this->uploadDir);
162 CRM_Utils_File::createDir($this->uploadDir);
163 }
164
165 // Whether we delete/create or simply preserve directories, we should
166 // certainly make sure the restrictions are enforced.
167 foreach ([
168 $this->templateCompileDir,
169 $this->uploadDir,
170 $this->configAndLogDir,
171 $this->customFileUploadDir,
172 ] as $dir) {
173 if ($dir && is_dir($dir)) {
174 CRM_Utils_File::restrictAccess($dir);
175 }
176 }
177 }
178
179 /**
180 * Verify that the needed parameters are not null in the config.
181 *
182 * @param CRM_Core_Config $config (reference) the system config object
183 * @param array $required (reference) the parameters that need a value
184 *
185 * @return bool
186 */
187 public static function check(&$config, &$required) {
188 foreach ($required as $name) {
189 if (CRM_Utils_System::isNull($config->$name)) {
190 return FALSE;
191 }
192 }
193 return TRUE;
194 }
195
196 /**
197 * Reset the serialized array and recompute.
198 * use with care
199 */
200 public function reset() {
201 $query = "UPDATE civicrm_domain SET config_backend = null";
202 CRM_Core_DAO::executeQuery($query);
203 }
204
205 /**
206 * This method should initialize auth sources.
207 */
208 public function authenticate() {
209 // make sure session is always initialised
210 $session = CRM_Core_Session::singleton();
211
212 // for logging purposes, pass the userID to the db
213 $userID = $session->get('userID');
214 if ($userID) {
215 CRM_Core_DAO::executeQuery('SET @civicrm_user_id = %1',
216 [1 => [$userID, 'Integer']]
217 );
218 }
219
220 if ($session->get('userID') && !$session->get('authSrc')) {
221 $session->set('authSrc', CRM_Core_Permission::AUTH_SRC_LOGIN);
222 }
223
224 // checksum source
225 CRM_Contact_BAO_Contact_Permission::initChecksumAuthSrc();
226 }
227
228 /**
229 * One function to get domain ID.
230 *
231 * @param int $domainID
232 * @param bool $reset
233 *
234 * @return int|null
235 */
236 public static function domainID($domainID = NULL, $reset = FALSE) {
237 static $domain;
238 if ($domainID) {
239 $domain = $domainID;
240 }
241 if ($reset || empty($domain)) {
242 $domain = defined('CIVICRM_DOMAIN_ID') ? CIVICRM_DOMAIN_ID : 1;
243 }
244
245 return $domain;
246 }
247
248 /**
249 * Function to get environment.
250 *
251 * @param string $env
252 * @param bool $reset
253 *
254 * @return string
255 */
256 public static function environment($env = NULL, $reset = FALSE) {
257 if ($env) {
258 $environment = $env;
259 }
260 if ($reset || empty($environment)) {
261 $environment = Civi::settings()->get('environment');
262 }
263 if (!$environment) {
264 $environment = 'Production';
265 }
266 return $environment;
267 }
268
269 /**
270 * Do general cleanup of caches, temp directories and temp tables
271 * CRM-8739
272 *
273 * @param bool $sessionReset
274 */
275 public function cleanupCaches($sessionReset = TRUE) {
276 // cleanup templates_c directory
277 $this->cleanup(1, FALSE);
278
279 // clear all caches
280 self::clearDBCache();
281 Civi::cache('session')->clear();
282 CRM_Utils_System::flushCache();
283
284 if ($sessionReset) {
285 $session = CRM_Core_Session::singleton();
286 $session->reset(2);
287 }
288 }
289
290 /**
291 * Do general cleanup of module permissions.
292 */
293 public function cleanupPermissions() {
294 $module_files = CRM_Extension_System::singleton()->getMapper()->getActiveModuleFiles();
295 if ($this->userPermissionClass->isModulePermissionSupported()) {
296 // Can store permissions -- so do it!
297 $this->userPermissionClass->upgradePermissions(
298 CRM_Core_Permission::basicPermissions()
299 );
300 }
301 else {
302 // Cannot store permissions -- warn if any modules require them
303 $modules_with_perms = [];
304 foreach ($module_files as $module_file) {
305 $perms = $this->userPermissionClass->getModulePermissions($module_file['prefix']);
306 if (!empty($perms)) {
307 $modules_with_perms[] = $module_file['prefix'];
308 }
309 }
310 if (!empty($modules_with_perms)) {
311 CRM_Core_Session::setStatus(
312 ts('Some modules define permissions, but the CMS cannot store them: %1', [1 => implode(', ', $modules_with_perms)]),
313 ts('Permission Error'),
314 'error'
315 );
316 }
317 }
318 }
319
320 /**
321 * Flush information about loaded modules.
322 */
323 public function clearModuleList() {
324 CRM_Extension_System::singleton()->getCache()->flush();
325 CRM_Utils_Hook::singleton(TRUE);
326 CRM_Core_PseudoConstant::getModuleExtensions(TRUE);
327 CRM_Core_Module::getAll(TRUE);
328 }
329
330 /**
331 * Clear db cache.
332 */
333 public static function clearDBCache() {
334 $queries = [
335 'TRUNCATE TABLE civicrm_acl_cache',
336 'TRUNCATE TABLE civicrm_acl_contact_cache',
337 'TRUNCATE TABLE civicrm_cache',
338 'TRUNCATE TABLE civicrm_prevnext_cache',
339 'UPDATE civicrm_group SET cache_date = NULL',
340 'TRUNCATE TABLE civicrm_group_contact_cache',
341 'TRUNCATE TABLE civicrm_menu',
342 'UPDATE civicrm_setting SET value = NULL WHERE name="navigation" AND contact_id IS NOT NULL',
343 ];
344
345 foreach ($queries as $query) {
346 CRM_Core_DAO::executeQuery($query);
347 }
348
349 if ($adapter = CRM_Utils_Constant::value('CIVICRM_BAO_CACHE_ADAPTER')) {
350 return $adapter::clearDBCache();
351 }
352
353 // also delete all the import and export temp tables
354 self::clearTempTables();
355 }
356
357 /**
358 * Clear leftover temporary tables.
359 *
360 * This is called on upgrade, during tests and site move, from the cron and via clear caches in the UI.
361 *
362 * Currently the UI clear caches does not pass a time interval - which may need review as it does risk
363 * ripping the tables out from underneath a current action. This was considered but
364 * out-of-scope for CRM-16167
365 *
366 * @param string|bool $timeInterval
367 * Optional time interval for mysql date function.g '2 day'. This can be used to prevent
368 * tables created recently from being deleted.
369 */
370 public static function clearTempTables($timeInterval = FALSE) {
371
372 $dao = new CRM_Core_DAO();
373 $query = "
374 SELECT TABLE_NAME as tableName
375 FROM INFORMATION_SCHEMA.TABLES
376 WHERE TABLE_SCHEMA = %1
377 AND (
378 TABLE_NAME LIKE 'civicrm_import_job_%'
379 OR TABLE_NAME LIKE 'civicrm_report_temp%'
380 OR TABLE_NAME LIKE 'civicrm_tmp_d%'
381 )
382 ";
383 // NOTE: Cannot find use-cases where "civicrm_report_temp" would be durable. Could probably remove.
384
385 if ($timeInterval) {
386 $query .= " AND CREATE_TIME < DATE_SUB(NOW(), INTERVAL {$timeInterval})";
387 }
388
389 $tableDAO = CRM_Core_DAO::executeQuery($query, [1 => [$dao->database(), 'String']]);
390 $tables = [];
391 while ($tableDAO->fetch()) {
392 $tables[] = $tableDAO->tableName;
393 }
394 if (!empty($tables)) {
395 $table = implode(',', $tables);
396 // drop leftover temporary tables
397 CRM_Core_DAO::executeQuery("DROP TABLE $table");
398 }
399 }
400
401 /**
402 * Check if running in upgrade mode.
403 *
404 * @param string $path
405 *
406 * @return bool
407 */
408 public static function isUpgradeMode($path = NULL) {
409 if (defined('CIVICRM_UPGRADE_ACTIVE')) {
410 return TRUE;
411 }
412
413 if (!$path) {
414 // note: do not re-initialize config here, since this function is part of
415 // config initialization itself
416 $urlVar = 'q';
417 if (defined('CIVICRM_UF') && CIVICRM_UF == 'Joomla') {
418 $urlVar = 'task';
419 }
420
421 $path = CRM_Utils_Array::value($urlVar, $_GET);
422 }
423
424 if ($path && preg_match('/^civicrm\/upgrade(\/.*)?$/', $path)) {
425 return TRUE;
426 }
427
428 if ($path && preg_match('/^civicrm\/ajax\/l10n-js/', $path)
429 && !empty($_SERVER['HTTP_REFERER'])
430 ) {
431 $ref = parse_url($_SERVER['HTTP_REFERER']);
432 if (
433 (!empty($ref['path']) && preg_match('/civicrm\/upgrade/', $ref['path'])) ||
434 (!empty($ref['query']) && preg_match('/civicrm\/upgrade/', urldecode($ref['query'])))
435 ) {
436 return TRUE;
437 }
438 }
439
440 return FALSE;
441 }
442
443 /**
444 * Is back office credit card processing enabled for this site - ie are there any installed processors that support
445 * it?
446 * This function is used for determining whether to show the submit credit card link, not for determining which processors to show, hence
447 * it is a config var
448 * @return bool
449 */
450 public static function isEnabledBackOfficeCreditCardPayments() {
451 return CRM_Financial_BAO_PaymentProcessor::hasPaymentProcessorSupporting(['BackOffice']);
452 }
453
454 /**
455 * @deprecated
456 */
457 public function addressSequence() {
458 CRM_Core_Error::deprecatedFunctionWarning('CRM_Utils_Address::sequence(Civi::settings()->get(\'address_format\')');
459 return CRM_Utils_Address::sequence(Civi::settings()->get('address_format'));
460 }
461
462 /**
463 * @deprecated
464 */
465 public function defaultContactCountry() {
466 CRM_Core_Error::deprecatedFunctionWarning('CRM_Core_BAO_Country::defaultContactCountry');
467 return CRM_Core_BAO_Country::defaultContactCountry();
468 }
469
470 /**
471 * @deprecated
472 */
473 public function defaultContactCountryName() {
474 CRM_Core_Error::deprecatedFunctionWarning('CRM_Core_BAO_Country::defaultContactCountryName');
475 return CRM_Core_BAO_Country::defaultContactCountryName();
476 }
477
478 /**
479 * @deprecated
480 *
481 * @param string $defaultCurrency
482 *
483 * @return string
484 */
485 public function defaultCurrencySymbol($defaultCurrency = NULL) {
486 CRM_Core_Error::deprecatedFunctionWarning('CRM_Core_BAO_Country::defaultCurrencySymbol');
487 return CRM_Core_BAO_Country::defaultCurrencySymbol($defaultCurrency);
488 }
489
490 /**
491 * Resets the singleton, so that the next call to CRM_Core_Config::singleton()
492 * reloads completely.
493 *
494 * While normally we could call the singleton function with $force = TRUE,
495 * this function addresses a very specific use-case in the CiviCRM installer,
496 * where we cannot yet force a reload, but we want to make sure that the next
497 * call to this object gets a fresh start (ex: to initialize the DAO).
498 */
499 public function free() {
500 self::$_singleton = NULL;
501 }
502
503 /**
504 * Conditionally fire an event during the first page run.
505 *
506 * The install system is currently implemented several times, so it's hard to add
507 * new installation logic. We use a makeshift method to detect the first run.
508 *
509 * Situations to test:
510 * - New installation
511 * - Upgrade from an old version (predating first-run tracker)
512 * - Upgrade from an old version (with first-run tracking)
513 */
514 public function handleFirstRun() {
515 // Ordinarily, we prefetch settings en masse and find that the system is already installed.
516 // No extra SQL queries required.
517 if (Civi::settings()->get('installed')) {
518 return;
519 }
520
521 // Q: How should this behave during testing?
522 if (defined('CIVICRM_TEST')) {
523 return;
524 }
525
526 // If schema hasn't been loaded yet, then do nothing. Don't want to interfere
527 // with the existing installers. NOTE: If we change the installer pageflow,
528 // then we may want to modify this behavior.
529 if (!CRM_Core_DAO::checkTableExists('civicrm_domain')) {
530 return;
531 }
532
533 // If we're handling an upgrade, then the system has already been used, so this
534 // is not the first run.
535 if (CRM_Core_Config::isUpgradeMode()) {
536 return;
537 }
538 $dao = CRM_Core_DAO::executeQuery('SELECT version FROM civicrm_domain');
539 while ($dao->fetch()) {
540 if ($dao->version && version_compare($dao->version, CRM_Utils_System::version(), '<')) {
541 return;
542 }
543 }
544
545 // The installation flag is stored in civicrm_setting, which is domain-aware. The
546 // flag could have been stored under a different domain.
547 $dao = CRM_Core_DAO::executeQuery('
548 SELECT domain_id, value FROM civicrm_setting
549 WHERE is_domain = 1 AND name = "installed"
550 ');
551 while ($dao->fetch()) {
552 $value = CRM_Utils_String::unserialize($dao->value);
553 if (!empty($value)) {
554 Civi::settings()->set('installed', 1);
555 return;
556 }
557 }
558
559 // OK, this looks new.
560 Civi::service('dispatcher')->dispatch(\Civi\Core\Event\SystemInstallEvent::EVENT_NAME, new \Civi\Core\Event\SystemInstallEvent());
561 Civi::settings()->set('installed', 1);
562 }
563
564 /**
565 * Is the system permitted to flush caches at the moment.
566 */
567 public static function isPermitCacheFlushMode() {
568 return !CRM_Core_Config::singleton()->doNotResetCache;
569 }
570
571 /**
572 * Set cache clearing to enabled or disabled.
573 *
574 * This might be enabled at the start of a long running process
575 * such as an import in order to delay clearing caches until the end.
576 *
577 * @param bool $enabled
578 * If true then caches can be cleared at this time.
579 */
580 public static function setPermitCacheFlushMode($enabled) {
581 CRM_Core_Config::singleton()->doNotResetCache = $enabled ? 0 : 1;
582 }
583
584 }