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