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