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