Merge pull request #22724 from braders/feature/group-search-null-columns
[civicrm-core.git] / CRM / Utils / System.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 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17
18 /**
19 * System wide utilities.
20 *
21 * Provides a collection of Civi utilities + access to the CMS-dependant utilities
22 *
23 * FIXME: This is a massive and random collection that could be split into smaller services
24 *
25 * @method static void getCMSPermissionsUrlParams() Immediately stop script execution and display a 401 "Access Denied" page.
26 * @method static mixed permissionDenied() Show access denied screen.
27 * @method static mixed logout() Log out the current user.
28 * @method static mixed updateCategories() Clear CMS caches related to the user registration/profile forms.
29 * @method static void appendBreadCrumb(array $breadCrumbs) Append an additional breadcrumb tag to the existing breadcrumbs.
30 * @method static void resetBreadCrumb() Reset an additional breadcrumb tag to the existing breadcrumb.
31 * @method static void addHTMLHead(string $head) Append a string to the head of the HTML file.
32 * @method static string postURL(int $action) Determine the post URL for a form.
33 * @method static string|null getUFLocale() Get the locale of the CMS.
34 * @method static bool setUFLocale(string $civicrm_language) Set the locale of the CMS.
35 * @method static bool isUserLoggedIn() Check if user is logged in.
36 * @method static int getLoggedInUfID() Get current logged in user id.
37 * @method static void setHttpHeader(string $name, string $value) Set http header.
38 * @method static array synchronizeUsers() Create CRM contacts for all existing CMS users.
39 * @method static void appendCoreResources(\Civi\Core\Event\GenericHookEvent $e) Callback for hook_civicrm_coreResourceList.
40 * @method static void alterAssetUrl(\Civi\Core\Event\GenericHookEvent $e) Callback for hook_civicrm_getAssetUrl.
41 */
42 class CRM_Utils_System {
43
44 public static $_callbacks = NULL;
45
46 /**
47 * @var string
48 * Page title
49 */
50 public static $title = '';
51
52 /**
53 * Access methods in the appropriate CMS class
54 *
55 * @param $name
56 * @param $arguments
57 * @return mixed
58 */
59 public static function __callStatic($name, $arguments) {
60 $userSystem = CRM_Core_Config::singleton()->userSystem;
61 return call_user_func_array([$userSystem, $name], $arguments);
62 }
63
64 /**
65 * Compose a new URL string from the current URL string.
66 *
67 * Used by all the framework components, specifically,
68 * pager, sort and qfc
69 *
70 * @param string $urlVar
71 * The url variable being considered (i.e. crmPageID, crmSortID etc).
72 * @param bool $includeReset
73 * (optional) Whether to include the reset GET string (if present).
74 * @param bool $includeForce
75 * (optional) Whether to include the force GET string (if present).
76 * @param string $path
77 * (optional) The path to use for the new url.
78 * @param bool|string $absolute
79 * (optional) Whether to return an absolute URL.
80 *
81 * @return string
82 * The URL fragment.
83 */
84 public static function makeURL($urlVar, $includeReset = FALSE, $includeForce = TRUE, $path = NULL, $absolute = FALSE) {
85 $path = $path ?: CRM_Utils_System::currentPath();
86 if (!$path) {
87 return '';
88 }
89
90 return self::url(
91 $path,
92 CRM_Utils_System::getLinksUrl($urlVar, $includeReset, $includeForce),
93 $absolute
94 );
95 }
96
97 /**
98 * Get the query string and clean it up.
99 *
100 * Strips some variables that should not be propagated, specifically variables
101 * like 'reset'. Also strips any side-affect actions (e.g. export).
102 *
103 * This function is copied mostly verbatim from Pager.php (_getLinksUrl)
104 *
105 * @param string $urlVar
106 * The URL variable being considered (e.g. crmPageID, crmSortID etc).
107 * @param bool $includeReset
108 * (optional) By default this is FALSE, meaning that the reset parameter
109 * is skipped. Set to TRUE to leave the reset parameter as-is.
110 * @param bool $includeForce
111 * (optional)
112 * @param bool $skipUFVar
113 * (optional)
114 *
115 * @return string
116 */
117 public static function getLinksUrl($urlVar, $includeReset = FALSE, $includeForce = TRUE, $skipUFVar = TRUE) {
118 // Sort out query string to prevent messy urls
119 $querystring = [];
120 $qs = [];
121 $arrays = [];
122
123 if (!empty($_SERVER['QUERY_STRING'])) {
124 $qs = explode('&', str_replace('&amp;', '&', $_SERVER['QUERY_STRING']));
125 for ($i = 0, $cnt = count($qs); $i < $cnt; $i++) {
126 // check first if exist a pair
127 if (strstr($qs[$i], '=') !== FALSE) {
128 list($name, $value) = explode('=', $qs[$i]);
129 if ($name != $urlVar) {
130 $name = rawurldecode($name);
131 // check for arrays in parameters: site.php?foo[]=1&foo[]=2&foo[]=3
132 if ((strpos($name, '[') !== FALSE) &&
133 (strpos($name, ']') !== FALSE)
134 ) {
135 $arrays[] = $qs[$i];
136 }
137 else {
138 $qs[$name] = $value;
139 }
140 }
141 }
142 else {
143 $qs[$qs[$i]] = '';
144 }
145 unset($qs[$i]);
146 }
147 }
148
149 if ($includeForce) {
150 $qs['force'] = 1;
151 }
152
153 // Ok this is a big assumption but usually works
154 // If we are in snippet mode, retain the 'section' param, if not, get rid
155 // of it.
156 if (!empty($qs['snippet'])) {
157 unset($qs['snippet']);
158 }
159 else {
160 unset($qs['section']);
161 }
162
163 if ($skipUFVar) {
164 $config = CRM_Core_Config::singleton();
165 unset($qs[$config->userFrameworkURLVar]);
166 }
167
168 foreach ($qs as $name => $value) {
169 if ($name != 'reset' || $includeReset) {
170 $querystring[] = $name . '=' . $value;
171 }
172 }
173
174 $querystring = array_merge($querystring, array_unique($arrays));
175
176 $url = implode('&', $querystring);
177 if ($urlVar) {
178 $url .= (!empty($querystring) ? '&' : '') . $urlVar . '=';
179 }
180
181 return $url;
182 }
183
184 /**
185 * If we are using a theming system, invoke theme, else just print the content.
186 *
187 * @param string $content
188 * The content that will be themed.
189 * @param bool $print
190 * (optional) Are we displaying to the screen or bypassing theming?
191 * @param bool $maintenance
192 * (optional) For maintenance mode.
193 *
194 * @return string
195 */
196 public static function theme(
197 &$content,
198 $print = FALSE,
199 $maintenance = FALSE
200 ) {
201 return CRM_Core_Config::singleton()->userSystem->theme($content, $print, $maintenance);
202 }
203
204 /**
205 * Generate a query string if input is an array.
206 *
207 * @param array|string $query
208 *
209 * @return string
210 */
211 public static function makeQueryString($query) {
212 if (is_array($query)) {
213 $buf = '';
214 foreach ($query as $key => $value) {
215 $buf .= ($buf ? '&' : '') . urlencode($key) . '=' . urlencode($value);
216 }
217 $query = $buf;
218 }
219 return $query;
220 }
221
222 /**
223 * Generate an internal CiviCRM URL.
224 *
225 * @param string $path
226 * The path being linked to, such as "civicrm/add".
227 * @param array|string $query
228 * A query string to append to the link, or an array of key-value pairs.
229 * @param bool $absolute
230 * Whether to force the output to be an absolute link (beginning with a
231 * URI-scheme such as 'http:'). Useful for links that will be displayed
232 * outside the site, such as in an RSS feed.
233 * @param string $fragment
234 * A fragment identifier (named anchor) to append to the link.
235 * @param bool $htmlize
236 * Whether to encode special html characters such as &.
237 * @param bool $frontend
238 * This link should be to the CMS front end (applies to WP & Joomla).
239 * @param bool $forceBackend
240 * This link should be to the CMS back end (applies to WP & Joomla).
241 *
242 * @return string
243 * An HTML string containing a link to the given path.
244 */
245 public static function url(
246 $path = NULL,
247 $query = NULL,
248 $absolute = FALSE,
249 $fragment = NULL,
250 $htmlize = TRUE,
251 $frontend = FALSE,
252 $forceBackend = FALSE
253 ) {
254 $query = self::makeQueryString($query);
255
256 // Legacy handling for when the system passes around html escaped strings
257 if (strstr($query, '&amp;')) {
258 $query = html_entity_decode($query);
259 }
260
261 // Extract fragment from path or query if munged together
262 if ($query && strstr($query, '#')) {
263 list($path, $fragment) = explode('#', $query);
264 }
265 if ($path && strstr($path, '#')) {
266 list($path, $fragment) = explode('#', $path);
267 }
268
269 // Extract query from path if munged together
270 if ($path && strstr($path, '?')) {
271 list($path, $extraQuery) = explode('?', $path);
272 $query = $extraQuery . ($query ? "&$query" : '');
273 }
274
275 $config = CRM_Core_Config::singleton();
276 $url = $config->userSystem->url($path, $query, $absolute, $fragment, $frontend, $forceBackend, $htmlize);
277
278 if ($htmlize) {
279 $url = htmlentities($url);
280 }
281
282 return $url;
283 }
284
285 /**
286 * Return the Notification URL for Payments.
287 *
288 * @param string $path
289 * The path being linked to, such as "civicrm/add".
290 * @param array|string $query
291 * A query string to append to the link, or an array of key-value pairs.
292 * @param bool $absolute
293 * Whether to force the output to be an absolute link (beginning with a
294 * URI-scheme such as 'http:'). Useful for links that will be displayed
295 * outside the site, such as in an RSS feed.
296 * @param string $fragment
297 * A fragment identifier (named anchor) to append to the link.
298 * @param bool $htmlize
299 * Whether to encode special html characters such as &.
300 * @param bool $frontend
301 * This link should be to the CMS front end (applies to WP & Joomla).
302 * @param bool $forceBackend
303 * This link should be to the CMS back end (applies to WP & Joomla).
304 *
305 * @return string
306 * The Notification URL.
307 */
308 public static function getNotifyUrl(
309 $path = NULL,
310 $query = NULL,
311 $absolute = FALSE,
312 $fragment = NULL,
313 $htmlize = TRUE,
314 $frontend = FALSE,
315 $forceBackend = FALSE
316 ) {
317 $config = CRM_Core_Config::singleton();
318 $query = self::makeQueryString($query);
319 return $config->userSystem->getNotifyUrl($path, $query, $absolute, $fragment, $frontend, $forceBackend, $htmlize);
320 }
321
322 /**
323 * Generates an extern url.
324 *
325 * @param string $path
326 * The extern path, such as "extern/url".
327 * @param string $query
328 * A query string to append to the link.
329 * @param string $fragment
330 * A fragment identifier (named anchor) to append to the link.
331 * @param bool $absolute
332 * Whether to force the output to be an absolute link (beginning with a
333 * URI-scheme such as 'http:').
334 * @param bool $isSSL
335 * NULL to autodetect. TRUE to force to SSL.
336 *
337 * @return string rawencoded URL.
338 */
339 public static function externUrl($path = NULL, $query = NULL, $fragment = NULL, $absolute = TRUE, $isSSL = NULL) {
340 $query = self::makeQueryString($query);
341
342 $url = Civi::paths()->getUrl("[civicrm.root]/{$path}.php", $absolute ? 'absolute' : 'relative', $isSSL)
343 . ($query ? "?$query" : "")
344 . ($fragment ? "#$fragment" : "");
345
346 $parsedUrl = CRM_Utils_Url::parseUrl($url);
347 $event = \Civi\Core\Event\GenericHookEvent::create([
348 'url' => &$parsedUrl,
349 'path' => $path,
350 'query' => $query,
351 'fragment' => $fragment,
352 'absolute' => $absolute,
353 'isSSL' => $isSSL,
354 ]);
355 Civi::dispatcher()->dispatch('hook_civicrm_alterExternUrl', $event);
356 return urldecode(CRM_Utils_Url::unparseUrl($event->url));
357 }
358
359 /**
360 * Perform any current conversions/migrations on the extern URL.
361 *
362 * @param \Civi\Core\Event\GenericHookEvent $e
363 * @see CRM_Utils_Hook::alterExternUrl
364 */
365 public static function migrateExternUrl(\Civi\Core\Event\GenericHookEvent $e) {
366
367 /**
368 * $mkRouteUri is a small adapter to return generated URL as a "UriInterface".
369 * @param string $path
370 * @param string $query
371 * @return \Psr\Http\Message\UriInterface
372 */
373 $mkRouteUri = function ($path, $query) use ($e) {
374 $urlTxt = CRM_Utils_System::url($path, $query, $e->absolute, $e->fragment, FALSE, TRUE);
375 if ($e->isSSL || ($e->isSSL === NULL && \CRM_Utils_System::isSSL())) {
376 $urlTxt = str_replace('http://', 'https://', $urlTxt);
377 }
378 return CRM_Utils_Url::parseUrl($urlTxt);
379 };
380
381 switch (Civi::settings()->get('defaultExternUrl') . ':' . $e->path) {
382 case 'router:extern/open':
383 $e->url = $mkRouteUri('civicrm/mailing/open', preg_replace('/(^|&)q=/', '\1qid=', $e->query));
384 break;
385
386 case 'router:extern/url':
387 $e->url = $mkRouteUri('civicrm/mailing/url', $e->query);
388 break;
389
390 case 'router:extern/widget':
391 $e->url = $mkRouteUri('civicrm/contribute/widget', $e->query);
392 break;
393
394 // Otherwise, keep the default.
395 }
396 }
397
398 /**
399 * @deprecated
400 * @see \CRM_Utils_System::currentPath
401 *
402 * @return string|null
403 */
404 public static function getUrlPath() {
405 CRM_Core_Error::deprecatedFunctionWarning('CRM_Utils_System::currentPath');
406 return self::currentPath();
407 }
408
409 /**
410 * Get href.
411 *
412 * @param string $text
413 * @param string $path
414 * @param string|array $query
415 * @param bool $absolute
416 * @param string $fragment
417 * @param bool $htmlize
418 * @param bool $frontend
419 * @param bool $forceBackend
420 *
421 * @return string
422 */
423 public static function href(
424 $text, $path = NULL, $query = NULL, $absolute = TRUE,
425 $fragment = NULL, $htmlize = TRUE, $frontend = FALSE, $forceBackend = FALSE
426 ) {
427 $url = self::url($path, $query, $absolute, $fragment, $htmlize, $frontend, $forceBackend);
428 return "<a href=\"$url\">$text</a>";
429 }
430
431 /**
432 * Path of the current page e.g. 'civicrm/contact/view'
433 *
434 * @return string|null
435 * the current menu path
436 */
437 public static function currentPath() {
438 $config = CRM_Core_Config::singleton();
439 return isset($_GET[$config->userFrameworkURLVar]) ? trim($_GET[$config->userFrameworkURLVar], '/') : NULL;
440 }
441
442 /**
443 * Called from a template to compose a url.
444 *
445 * @param array $params
446 * List of parameters.
447 *
448 * @return string
449 * url
450 */
451 public static function crmURL($params) {
452 $p = $params['p'] ?? NULL;
453 if (!isset($p)) {
454 $p = self::currentPath();
455 }
456
457 return self::url(
458 $p,
459 CRM_Utils_Array::value('q', $params),
460 CRM_Utils_Array::value('a', $params, FALSE),
461 CRM_Utils_Array::value('f', $params),
462 CRM_Utils_Array::value('h', $params, TRUE),
463 CRM_Utils_Array::value('fe', $params, FALSE),
464 CRM_Utils_Array::value('fb', $params, FALSE)
465 );
466 }
467
468 /**
469 * Sets the title of the page.
470 *
471 * @param string $title
472 * Document title - plain text only
473 * @param string $pageTitle
474 * Page title (if different) - may include html
475 */
476 public static function setTitle($title, $pageTitle = NULL) {
477 self::$title = $title;
478 $config = CRM_Core_Config::singleton();
479 return $config->userSystem->setTitle($title, $pageTitle);
480 }
481
482 /**
483 * Figures and sets the userContext.
484 *
485 * Uses the referrer if valid else uses the default.
486 *
487 * @param array $names
488 * Referrer should match any str in this array.
489 * @param string $default
490 * (optional) The default userContext if no match found.
491 */
492 public static function setUserContext($names, $default = NULL) {
493 $url = $default;
494
495 $session = CRM_Core_Session::singleton();
496 $referer = $_SERVER['HTTP_REFERER'] ?? NULL;
497
498 if ($referer && !empty($names)) {
499 foreach ($names as $name) {
500 if (strstr($referer, $name)) {
501 $url = $referer;
502 break;
503 }
504 }
505 }
506
507 if ($url) {
508 $session->pushUserContext($url);
509 }
510 }
511
512 /**
513 * Gets a class name for an object.
514 *
515 * @param object $object
516 * Object whose class name is needed.
517 *
518 * @return string
519 * The class name of the object.
520 */
521 public static function getClassName($object) {
522 return get_class($object);
523 }
524
525 /**
526 * Redirect to another URL.
527 *
528 * @param string $url
529 * The URL to provide to the browser via the Location header.
530 * @param array $context
531 * Optional additional information for the hook.
532 */
533 public static function redirect($url = NULL, $context = []) {
534 if (!$url) {
535 $url = self::url('civicrm/dashboard', 'reset=1');
536 }
537 // replace the &amp; characters with &
538 // this is kinda hackish but not sure how to do it right
539 $url = str_replace('&amp;', '&', $url);
540
541 $context['output'] = $_GET['snippet'] ?? NULL;
542
543 $parsedUrl = CRM_Utils_Url::parseUrl($url);
544 CRM_Utils_Hook::alterRedirect($parsedUrl, $context);
545 $url = CRM_Utils_Url::unparseUrl($parsedUrl);
546
547 // If we are in a json context, respond appropriately
548 if ($context['output'] === 'json') {
549 CRM_Core_Page_AJAX::returnJsonResponse([
550 'status' => 'redirect',
551 'userContext' => $url,
552 ]);
553 }
554
555 self::setHttpHeader('Location', $url);
556 self::civiExit(0, ['url' => $url, 'context' => 'redirect']);
557 }
558
559 /**
560 * Redirect to another URL using JavaScript.
561 *
562 * Use an html based file with javascript embedded to redirect to another url
563 * This prevent the too many redirect errors emitted by various browsers
564 *
565 * @param string $url
566 * (optional) The destination URL.
567 * @param string $title
568 * (optional) The page title to use for the redirect page.
569 * @param string $message
570 * (optional) The message to provide in the body of the redirect page.
571 */
572 public static function jsRedirect(
573 $url = NULL,
574 $title = NULL,
575 $message = NULL
576 ) {
577 if (!$url) {
578 $url = self::url('civicrm/dashboard', 'reset=1');
579 }
580
581 if (!$title) {
582 $title = ts('CiviCRM task in progress');
583 }
584
585 if (!$message) {
586 $message = ts('A long running CiviCRM task is currently in progress. This message will be refreshed till the task is completed');
587 }
588
589 // replace the &amp; characters with &
590 // this is kinda hackish but not sure how to do it right
591 $url = str_replace('&amp;', '&', $url);
592
593 $template = CRM_Core_Smarty::singleton();
594 $template->assign('redirectURL', $url);
595 $template->assign('title', $title);
596 $template->assign('message', $message);
597
598 $html = $template->fetch('CRM/common/redirectJS.tpl');
599
600 echo $html;
601
602 self::civiExit();
603 }
604
605 /**
606 * Get the base URL of the system.
607 *
608 * @return string
609 */
610 public static function baseURL() {
611 $config = CRM_Core_Config::singleton();
612 return $config->userFrameworkBaseURL;
613 }
614
615 /**
616 * Authenticate or abort.
617 *
618 * @param string $message
619 * @param bool $abort
620 *
621 * @return bool
622 */
623 public static function authenticateAbort($message, $abort) {
624 if ($abort) {
625 echo $message;
626 self::civiExit(0);
627 }
628 else {
629 return FALSE;
630 }
631 }
632
633 /**
634 * Authenticate key.
635 *
636 * @param bool $abort
637 * (optional) Whether to exit; defaults to true.
638 *
639 * @return bool
640 */
641 public static function authenticateKey($abort = TRUE) {
642 // also make sure the key is sent and is valid
643 $key = trim(CRM_Utils_Array::value('key', $_REQUEST));
644
645 $docAdd = "More info at: " . CRM_Utils_System::docURL2('sysadmin/setup/jobs', TRUE);
646
647 if (!$key) {
648 return self::authenticateAbort(
649 "ERROR: You need to send a valid key to execute this file. " . $docAdd . "\n",
650 $abort
651 );
652 }
653
654 $siteKey = defined('CIVICRM_SITE_KEY') ? CIVICRM_SITE_KEY : NULL;
655
656 if (!$siteKey || empty($siteKey)) {
657 return self::authenticateAbort(
658 "ERROR: You need to set a valid site key in civicrm.settings.php. " . $docAdd . "\n",
659 $abort
660 );
661 }
662
663 if (strlen($siteKey) < 8) {
664 return self::authenticateAbort(
665 "ERROR: Site key needs to be greater than 7 characters in civicrm.settings.php. " . $docAdd . "\n",
666 $abort
667 );
668 }
669
670 if (!hash_equals($siteKey, $key)) {
671 return self::authenticateAbort(
672 "ERROR: Invalid key value sent. " . $docAdd . "\n",
673 $abort
674 );
675 }
676
677 return TRUE;
678 }
679
680 /**
681 * Authenticate script.
682 *
683 * @param bool $abort
684 * @param string $name
685 * @param string $pass
686 * @param bool $storeInSession
687 * @param bool $loadCMSBootstrap
688 * @param bool $requireKey
689 *
690 * @return bool
691 */
692 public static function authenticateScript($abort = TRUE, $name = NULL, $pass = NULL, $storeInSession = TRUE, $loadCMSBootstrap = TRUE, $requireKey = TRUE) {
693 // auth to make sure the user has a login/password to do a shell operation
694 // later on we'll link this to acl's
695 if (!$name) {
696 $name = trim(CRM_Utils_Array::value('name', $_REQUEST));
697 $pass = trim(CRM_Utils_Array::value('pass', $_REQUEST));
698 }
699
700 // its ok to have an empty password
701 if (!$name) {
702 return self::authenticateAbort(
703 "ERROR: You need to send a valid user name and password to execute this file\n",
704 $abort
705 );
706 }
707
708 if ($requireKey && !self::authenticateKey($abort)) {
709 return FALSE;
710 }
711
712 $result = CRM_Utils_System::authenticate($name, $pass, $loadCMSBootstrap);
713 if (!$result) {
714 return self::authenticateAbort(
715 "ERROR: Invalid username and/or password\n",
716 $abort
717 );
718 }
719 elseif ($storeInSession) {
720 // lets store contact id and user id in session
721 list($userID, $ufID, $randomNumber) = $result;
722 if ($userID && $ufID) {
723 $config = CRM_Core_Config::singleton();
724 $config->userSystem->setUserSession([$userID, $ufID]);
725 }
726 else {
727 return self::authenticateAbort(
728 "ERROR: Unexpected error, could not match userID and contactID",
729 $abort
730 );
731 }
732 }
733
734 return $result;
735 }
736
737 /**
738 * Authenticate the user against the uf db.
739 *
740 * In case of successful authentication, returns an array consisting of
741 * (contactID, ufID, unique string). Returns FALSE if authentication is
742 * unsuccessful.
743 *
744 * @param string $name
745 * The username.
746 * @param string $password
747 * The password.
748 * @param bool $loadCMSBootstrap
749 * @param string $realPath
750 *
751 * @return false|array
752 */
753 public static function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realPath = NULL) {
754 $config = CRM_Core_Config::singleton();
755
756 /* Before we do any loading, let's start the session and write to it.
757 * We typically call authenticate only when we need to bootstrap the CMS
758 * directly via Civi and hence bypass the normal CMS auth and bootstrap
759 * process typically done in CLI and cron scripts. See: CRM-12648
760 *
761 * Q: Can we move this to the userSystem class so that it can be tuned
762 * per-CMS? For example, when dealing with UnitTests UF, does it need to
763 * do this session write since the original issue was for Drupal.
764 */
765 $session = CRM_Core_Session::singleton();
766 $session->set('civicrmInitSession', TRUE);
767
768 return $config->userSystem->authenticate($name, $password, $loadCMSBootstrap, $realPath);
769 }
770
771 /**
772 * Set a message in the UF to display to a user.
773 *
774 * @param string $message
775 * The message to set.
776 */
777 public static function setUFMessage($message) {
778 $config = CRM_Core_Config::singleton();
779 return $config->userSystem->setMessage($message);
780 }
781
782 /**
783 * Determine whether a value is null-ish.
784 *
785 * @param mixed $value
786 * The value to check for null.
787 *
788 * @return bool
789 */
790 public static function isNull($value) {
791 // FIXME: remove $value = 'null' string test when we upgrade our DAO code to handle passing null in a better way.
792 if (!isset($value) || $value === NULL || $value === '' || $value === 'null') {
793 return TRUE;
794 }
795 if (is_array($value)) {
796 // @todo Reuse of the $value variable = asking for trouble.
797 foreach ($value as $key => $value) {
798 if (in_array($key, CRM_Core_DAO::acceptedSQLOperators(), TRUE) || !self::isNull($value)) {
799 return FALSE;
800 }
801 }
802 return TRUE;
803 }
804 return FALSE;
805 }
806
807 /**
808 * Obscure all but the last few digits of a credit card number.
809 *
810 * @param string $number
811 * The credit card number to obscure.
812 * @param int $keep
813 * (optional) The number of digits to preserve unmodified.
814 *
815 * @return string
816 * The obscured credit card number.
817 */
818 public static function mungeCreditCard($number, $keep = 4) {
819 $number = trim($number);
820 if (empty($number)) {
821 return NULL;
822 }
823 $replace = str_repeat('*', strlen($number) - $keep);
824 return substr_replace($number, $replace, 0, -$keep);
825 }
826
827 /**
828 * Determine which PHP modules are loaded.
829 *
830 * @return array
831 */
832 private static function parsePHPModules() {
833 ob_start();
834 phpinfo(INFO_MODULES);
835 $s = ob_get_contents();
836 ob_end_clean();
837
838 $s = strip_tags($s, '<h2><th><td>');
839 $s = preg_replace('/<th[^>]*>([^<]+)<\/th>/', "<info>\\1</info>", $s);
840 $s = preg_replace('/<td[^>]*>([^<]+)<\/td>/', "<info>\\1</info>", $s);
841 $vTmp = preg_split('/(<h2>[^<]+<\/h2>)/', $s, -1, PREG_SPLIT_DELIM_CAPTURE);
842 $vModules = [];
843 for ($i = 1; $i < count($vTmp); $i++) {
844 if (preg_match('/<h2>([^<]+)<\/h2>/', $vTmp[$i], $vMat)) {
845 $vName = trim($vMat[1]);
846 $vTmp2 = explode("\n", $vTmp[$i + 1]);
847 foreach ($vTmp2 as $vOne) {
848 $vPat = '<info>([^<]+)<\/info>';
849 $vPat3 = "/$vPat\s*$vPat\s*$vPat/";
850 $vPat2 = "/$vPat\s*$vPat/";
851 // 3cols
852 if (preg_match($vPat3, $vOne, $vMat)) {
853 $vModules[$vName][trim($vMat[1])] = [trim($vMat[2]), trim($vMat[3])];
854 // 2cols
855 }
856 elseif (preg_match($vPat2, $vOne, $vMat)) {
857 $vModules[$vName][trim($vMat[1])] = trim($vMat[2]);
858 }
859 }
860 }
861 }
862 return $vModules;
863 }
864
865 /**
866 * Get a setting from a loaded PHP module.
867 *
868 * @param string $pModuleName
869 * @param string $pSetting
870 *
871 * @return mixed
872 */
873 public static function getModuleSetting($pModuleName, $pSetting) {
874 $vModules = self::parsePHPModules();
875 return $vModules[$pModuleName][$pSetting];
876 }
877
878 /**
879 * Do something no-one bothered to document.
880 *
881 * @param string $title
882 * (optional)
883 *
884 * @return mixed|string
885 */
886 public static function memory($title = NULL) {
887 static $pid = NULL;
888 if (!$pid) {
889 $pid = posix_getpid();
890 }
891
892 $memory = str_replace("\n", '', shell_exec("ps -p" . $pid . " -o rss="));
893 $memory .= ", " . time();
894 if ($title) {
895 CRM_Core_Error::debug_var($title, $memory);
896 }
897 return $memory;
898 }
899
900 /**
901 * Download something or other.
902 *
903 * @param string $name
904 * @param string $mimeType
905 * @param string $buffer
906 * @param string $ext
907 * @param bool $output
908 * @param string $disposition
909 */
910 public static function download(
911 $name, $mimeType, &$buffer,
912 $ext = NULL,
913 $output = TRUE,
914 $disposition = 'attachment'
915 ) {
916 $now = gmdate('D, d M Y H:i:s') . ' GMT';
917
918 self::setHttpHeader('Content-Type', $mimeType);
919 self::setHttpHeader('Expires', $now);
920
921 // lem9 & loic1: IE needs specific headers
922 $isIE = empty($_SERVER['HTTP_USER_AGENT']) ? FALSE : strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE');
923 if ($ext) {
924 $fileString = "filename=\"{$name}.{$ext}\"";
925 }
926 else {
927 $fileString = "filename=\"{$name}\"";
928 }
929 if ($isIE) {
930 self::setHttpHeader("Content-Disposition", "inline; $fileString");
931 self::setHttpHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0');
932 self::setHttpHeader('Pragma', 'public');
933 }
934 else {
935 self::setHttpHeader("Content-Disposition", "$disposition; $fileString");
936 self::setHttpHeader('Pragma', 'no-cache');
937 }
938
939 if ($output) {
940 print $buffer;
941 self::civiExit();
942 }
943 }
944
945 /**
946 * Gather and print (and possibly log) amount of used memory.
947 *
948 * @param string $title
949 * @param bool $log
950 * (optional) Whether to log the memory usage information.
951 */
952 public static function xMemory($title = NULL, $log = FALSE) {
953 $mem = (float) xdebug_memory_usage() / (float) (1024);
954 $mem = number_format($mem, 5) . ", " . time();
955 if ($log) {
956 echo "<p>$title: $mem<p>";
957 flush();
958 CRM_Core_Error::debug_var($title, $mem);
959 }
960 else {
961 echo "<p>$title: $mem<p>";
962 flush();
963 }
964 }
965
966 /**
967 * Take a URL (or partial URL) and make it better.
968 *
969 * Currently, URLs pass straight through unchanged unless they are "seriously
970 * malformed" (see http://us2.php.net/parse_url).
971 *
972 * @param string $url
973 * The URL to operate on.
974 *
975 * @return string
976 * The fixed URL.
977 */
978 public static function fixURL($url) {
979 $components = parse_url($url);
980
981 if (!$components) {
982 return NULL;
983 }
984
985 // at some point we'll add code here to make sure the url is not
986 // something that will mess up, so we need to clean it up here
987 return $url;
988 }
989
990 /**
991 * Make sure a callback is valid in the current context.
992 *
993 * @param string $callback
994 * Name of the function to check.
995 *
996 * @return bool
997 */
998 public static function validCallback($callback) {
999 if (self::$_callbacks === NULL) {
1000 self::$_callbacks = [];
1001 }
1002
1003 if (!array_key_exists($callback, self::$_callbacks)) {
1004 if (strpos($callback, '::') !== FALSE) {
1005 list($className, $methodName) = explode('::', $callback);
1006 $fileName = str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
1007 // ignore errors if any
1008 @include_once $fileName;
1009 if (!class_exists($className)) {
1010 self::$_callbacks[$callback] = FALSE;
1011 }
1012 else {
1013 // instantiate the class
1014 $object = new $className();
1015 if (!method_exists($object, $methodName)) {
1016 self::$_callbacks[$callback] = FALSE;
1017 }
1018 else {
1019 self::$_callbacks[$callback] = TRUE;
1020 }
1021 }
1022 }
1023 else {
1024 self::$_callbacks[$callback] = function_exists($callback);
1025 }
1026 }
1027 return self::$_callbacks[$callback];
1028 }
1029
1030 /**
1031 * Like PHP's built-in explode(), but always return an array of $limit items.
1032 *
1033 * This serves as a wrapper to the PHP explode() function. In the event that
1034 * PHP's explode() returns an array with fewer than $limit elements, pad
1035 * the end of the array with NULLs.
1036 *
1037 * @param string $separator
1038 * @param string $string
1039 * @param int $limit
1040 *
1041 * @return string[]
1042 */
1043 public static function explode($separator, $string, $limit) {
1044 $result = explode($separator, $string, $limit);
1045 for ($i = count($result); $i < $limit; $i++) {
1046 $result[$i] = NULL;
1047 }
1048 return $result;
1049 }
1050
1051 /**
1052 * Check url.
1053 *
1054 * @param string $url
1055 * The URL to check.
1056 * @param bool $addCookie
1057 * (optional)
1058 *
1059 * @return mixed
1060 */
1061 public static function checkURL($url, $addCookie = FALSE) {
1062 // make a GET request to $url
1063 $ch = curl_init($url);
1064 if ($addCookie) {
1065 curl_setopt($ch, CURLOPT_COOKIE, http_build_query($_COOKIE));
1066 }
1067 // it's quite alright to use a self-signed cert
1068 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
1069
1070 // lets capture the return stuff rather than echo
1071 curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
1072
1073 // CRM-13227, CRM-14744: only return the SSL error status
1074 return (curl_exec($ch) !== FALSE);
1075 }
1076
1077 /**
1078 * Assert that we are running on a particular PHP version.
1079 *
1080 * @param int $ver
1081 * The major version of PHP that is required.
1082 * @param bool $abort
1083 * (optional) Whether to fatally abort if the version requirement is not
1084 * met. Defaults to TRUE.
1085 *
1086 * @return bool
1087 * Returns TRUE if the requirement is met, FALSE if the requirement is not
1088 * met and we're not aborting due to the failed requirement. If $abort is
1089 * TRUE and the requirement fails, this function does not return.
1090 *
1091 * @throws CRM_Core_Exception
1092 */
1093 public static function checkPHPVersion($ver = 5, $abort = TRUE) {
1094 $phpVersion = substr(PHP_VERSION, 0, 1);
1095 if ($phpVersion >= $ver) {
1096 return TRUE;
1097 }
1098
1099 if ($abort) {
1100 throw new CRM_Core_Exception(ts('This feature requires PHP Version %1 or greater',
1101 [1 => $ver]
1102 ));
1103 }
1104 return FALSE;
1105 }
1106
1107 /**
1108 * Encode url.
1109 *
1110 * @param string $url
1111 *
1112 * @return null|string
1113 */
1114 public static function urlEncode($url) {
1115 CRM_Core_Error::deprecatedFunctionWarning('urlEncode');
1116 $items = parse_url($url);
1117 if ($items === FALSE) {
1118 return NULL;
1119 }
1120
1121 if (empty($items['query'])) {
1122 return $url;
1123 }
1124
1125 $items['query'] = urlencode($items['query']);
1126
1127 $url = $items['scheme'] . '://';
1128 if (!empty($items['user'])) {
1129 $url .= "{$items['user']}:{$items['pass']}@";
1130 }
1131
1132 $url .= $items['host'];
1133 if (!empty($items['port'])) {
1134 $url .= ":{$items['port']}";
1135 }
1136
1137 $url .= "{$items['path']}?{$items['query']}";
1138 if (!empty($items['fragment'])) {
1139 $url .= "#{$items['fragment']}";
1140 }
1141
1142 return $url;
1143 }
1144
1145 /**
1146 * Return the running civicrm version.
1147 *
1148 * @return string
1149 * civicrm version
1150 *
1151 * @throws CRM_Core_Exception
1152 */
1153 public static function version() {
1154 static $version;
1155
1156 if (!$version) {
1157 $verFile = implode(DIRECTORY_SEPARATOR,
1158 [dirname(__FILE__), '..', '..', 'xml', 'version.xml']
1159 );
1160 if (file_exists($verFile)) {
1161 $str = file_get_contents($verFile);
1162 $xmlObj = simplexml_load_string($str);
1163 $version = (string) $xmlObj->version_no;
1164 }
1165
1166 // pattern check
1167 if (!CRM_Utils_System::isVersionFormatValid($version)) {
1168 throw new CRM_Core_Exception('Unknown codebase version.');
1169 }
1170 }
1171
1172 return $version;
1173 }
1174
1175 /**
1176 * Gives the first two parts of the version string E.g. 6.1.
1177 *
1178 * @return string
1179 */
1180 public static function majorVersion() {
1181 list($a, $b) = explode('.', self::version());
1182 return "$a.$b";
1183 }
1184
1185 /**
1186 * Determines whether a string is a valid CiviCRM version string.
1187 *
1188 * @param string $version
1189 * Version string to be checked.
1190 *
1191 * @return bool
1192 */
1193 public static function isVersionFormatValid($version) {
1194 return preg_match("/^(\d{1,2}\.){2,3}(\d{1,2}|(alpha|beta)\d{1,2})(\.upgrade)?$/", $version);
1195 }
1196
1197 /**
1198 * Wraps or emulates PHP's getallheaders() function.
1199 */
1200 public static function getAllHeaders() {
1201 if (function_exists('getallheaders')) {
1202 return getallheaders();
1203 }
1204
1205 // emulate get all headers
1206 // http://www.php.net/manual/en/function.getallheaders.php#66335
1207 $headers = [];
1208 foreach ($_SERVER as $name => $value) {
1209 if (substr($name, 0, 5) == 'HTTP_') {
1210 $headers[str_replace(' ',
1211 '-',
1212 ucwords(strtolower(str_replace('_',
1213 ' ',
1214 substr($name, 5)
1215 )
1216 ))
1217 )] = $value;
1218 }
1219 }
1220 return $headers;
1221 }
1222
1223 /**
1224 * Get request headers.
1225 *
1226 * @return array|false
1227 */
1228 public static function getRequestHeaders() {
1229 if (function_exists('apache_request_headers')) {
1230 return apache_request_headers();
1231 }
1232 else {
1233 return $_SERVER;
1234 }
1235 }
1236
1237 /**
1238 * Determine whether this is an SSL request.
1239 *
1240 * Note that we inline this function in install/civicrm.php, so if you change
1241 * this function, please go and change the code in the install script as well.
1242 */
1243 public static function isSSL() {
1244 return !empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off';
1245 }
1246
1247 /**
1248 * Redirect to SSL.
1249 *
1250 * @param bool|false $abort
1251 *
1252 * @throws \CRM_Core_Exception
1253 */
1254 public static function redirectToSSL($abort = FALSE) {
1255 $config = CRM_Core_Config::singleton();
1256 $req_headers = self::getRequestHeaders();
1257 // FIXME: Shouldn't the X-Forwarded-Proto check be part of CRM_Utils_System::isSSL()?
1258 if (Civi::settings()->get('enableSSL') &&
1259 !self::isSSL() &&
1260 strtolower(CRM_Utils_Array::value('X_FORWARDED_PROTO', $req_headers)) != 'https'
1261 ) {
1262 // ensure that SSL is enabled on a civicrm url (for cookie reasons etc)
1263 $url = "https://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
1264 // @see https://lab.civicrm.org/dev/core/issues/425 if you're seeing this message.
1265 Civi::log()->warning('CiviCRM thinks site is not SSL, redirecting to {url}', ['url' => $url]);
1266 if (!self::checkURL($url, TRUE)) {
1267 if ($abort) {
1268 throw new CRM_Core_Exception('HTTPS is not set up on this machine');
1269 }
1270 else {
1271 CRM_Core_Session::setStatus(ts('HTTPS is not set up on this machine'), ts('Warning'), 'alert');
1272 // admin should be the only one following this
1273 // since we dont want the user stuck in a bad place
1274 return;
1275 }
1276 }
1277 CRM_Utils_System::redirect($url);
1278 }
1279 }
1280
1281 /**
1282 * Get logged in user's IP address.
1283 *
1284 * Get IP address from HTTP REMOTE_ADDR header. If the CMS is Drupal then use
1285 * the Drupal function as this also handles reverse proxies (based on proper
1286 * configuration in settings.php)
1287 *
1288 * @param bool $strictIPV4
1289 * (optional) Whether to return only IPv4 addresses.
1290 *
1291 * @return string
1292 * IP address of logged in user.
1293 */
1294 public static function ipAddress($strictIPV4 = TRUE) {
1295 $address = $_SERVER['REMOTE_ADDR'] ?? NULL;
1296
1297 $config = CRM_Core_Config::singleton();
1298 if ($config->userSystem->is_drupal && function_exists('ip_address')) {
1299 // drupal function handles the server being behind a proxy securely. We still have legacy ipn methods
1300 // that reach this point without bootstrapping hence the check that the fn exists
1301 $address = ip_address();
1302 }
1303
1304 // hack for safari
1305 if ($address == '::1') {
1306 $address = '127.0.0.1';
1307 }
1308
1309 // when we need to have strictly IPV4 ip address
1310 // convert ipV6 to ipV4
1311 if ($strictIPV4) {
1312 // this converts 'IPV4 mapped IPV6 address' to IPV4
1313 if (filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && strstr($address, '::ffff:')) {
1314 $address = ltrim($address, '::ffff:');
1315 }
1316 }
1317
1318 return $address;
1319 }
1320
1321 /**
1322 * Get the referring / previous page URL.
1323 *
1324 * @return string
1325 * The previous page URL
1326 */
1327 public static function refererPath() {
1328 return $_SERVER['HTTP_REFERER'] ?? NULL;
1329 }
1330
1331 /**
1332 * Get the documentation base URL.
1333 *
1334 * @return string
1335 * Base URL of the CRM documentation.
1336 */
1337 public static function getDocBaseURL() {
1338 // FIXME: move this to configuration at some stage
1339 return 'https://docs.civicrm.org/';
1340 }
1341
1342 /**
1343 * Returns wiki (alternate) documentation URL base.
1344 *
1345 * @return string
1346 * documentation url
1347 */
1348 public static function getWikiBaseURL() {
1349 // FIXME: move this to configuration at some stage
1350 return 'http://wiki.civicrm.org/confluence/display/CRMDOC/';
1351 }
1352
1353 /**
1354 * Returns URL or link to documentation page, based on provided parameters.
1355 *
1356 * For use in PHP code.
1357 * WARNING: Always returns URL, if ts function is not defined ($URLonly has
1358 * no effect).
1359 *
1360 * @param string $page
1361 * Title of documentation wiki page.
1362 * @param bool $URLonly
1363 * (optional) Whether to return URL only or full HTML link (default).
1364 * @param string|null $text
1365 * (optional) Text of HTML link (no effect if $URLonly = false).
1366 * @param string|null $title
1367 * (optional) Tooltip text for HTML link (no effect if $URLonly = false)
1368 * @param string|null $style
1369 * (optional) Style attribute value for HTML link (no effect if $URLonly = false)
1370 * @param string|null $resource
1371 *
1372 * @return string
1373 * URL or link to documentation page, based on provided parameters.
1374 */
1375 public static function docURL2($page, $URLonly = FALSE, $text = NULL, $title = NULL, $style = NULL, $resource = NULL) {
1376 // if ts function doesn't exist, it means that CiviCRM hasn't been fully initialised yet -
1377 // return just the URL, no matter what other parameters are defined
1378 if (!function_exists('ts')) {
1379 if ($resource == 'wiki') {
1380 $docBaseURL = self::getWikiBaseURL();
1381 }
1382 else {
1383 $docBaseURL = self::getDocBaseURL();
1384 $page = self::formatDocUrl($page);
1385 }
1386 return $docBaseURL . str_replace(' ', '+', $page);
1387 }
1388 else {
1389 $params = [
1390 'page' => $page,
1391 'URLonly' => $URLonly,
1392 'text' => $text,
1393 'title' => $title,
1394 'style' => $style,
1395 'resource' => $resource,
1396 ];
1397 return self::docURL($params);
1398 }
1399 }
1400
1401 /**
1402 * Returns URL or link to documentation page, based on provided parameters.
1403 *
1404 * For use in templates code.
1405 *
1406 * @param array $params
1407 * An array of parameters (see CRM_Utils_System::docURL2 method for names)
1408 *
1409 * @return null|string
1410 * URL or link to documentation page, based on provided parameters.
1411 */
1412 public static function docURL($params) {
1413
1414 if (!isset($params['page'])) {
1415 return NULL;
1416 }
1417
1418 if (CRM_Utils_Array::value('resource', $params) == 'wiki') {
1419 $docBaseURL = self::getWikiBaseURL();
1420 }
1421 else {
1422 $docBaseURL = self::getDocBaseURL();
1423 $params['page'] = self::formatDocUrl($params['page']);
1424 }
1425
1426 if (!isset($params['title']) or $params['title'] === NULL) {
1427 $params['title'] = ts('Opens documentation in a new window.');
1428 }
1429
1430 if (!isset($params['text']) or $params['text'] === NULL) {
1431 $params['text'] = ts('(Learn more...)');
1432 }
1433
1434 if (!isset($params['style']) || $params['style'] === NULL) {
1435 $style = '';
1436 }
1437 else {
1438 $style = "style=\"{$params['style']}\"";
1439 }
1440
1441 $link = $docBaseURL . str_replace(' ', '+', $params['page']);
1442
1443 if (isset($params['URLonly']) && $params['URLonly'] == TRUE) {
1444 return $link;
1445 }
1446 else {
1447 return "<a href=\"{$link}\" $style target=\"_blank\" class=\"crm-doc-link no-popup\" title=\"{$params['title']}\">{$params['text']}</a>";
1448 }
1449 }
1450
1451 /**
1452 * Add language and version parameters to the doc url.
1453 *
1454 * Note that this function may run before CiviCRM is initialized and so should not call ts() or perform any db lookups.
1455 *
1456 * @param $url
1457 * @return mixed
1458 */
1459 public static function formatDocUrl($url) {
1460 return preg_replace('#^(installation|user|sysadmin|dev)/#', '\1/en/latest/', $url);
1461 }
1462
1463 /**
1464 * Exit with provided exit code.
1465 *
1466 * @param int $status
1467 * (optional) Code with which to exit.
1468 *
1469 * @param array $testParameters
1470 */
1471 public static function civiExit($status = 0, $testParameters = []) {
1472
1473 if (CIVICRM_UF === 'UnitTests') {
1474 throw new CRM_Core_Exception_PrematureExitException('civiExit called', $testParameters);
1475 }
1476 if ($status > 0) {
1477 http_response_code(500);
1478 }
1479 // move things to CiviCRM cache as needed
1480 CRM_Core_Session::storeSessionObjects();
1481
1482 if (Civi\Core\Container::isContainerBooted()) {
1483 Civi::dispatcher()->dispatch('civi.core.exit');
1484 }
1485
1486 $userSystem = CRM_Core_Config::singleton()->userSystem;
1487 if (is_callable([$userSystem, 'onCiviExit'])) {
1488 $userSystem->onCiviExit();
1489 }
1490 exit($status);
1491 }
1492
1493 /**
1494 * Reset the various system caches and some important static variables.
1495 */
1496 public static function flushCache() {
1497 // flush out all cache entries so we can reload new data
1498 // a bit aggressive, but livable for now
1499 CRM_Utils_Cache::singleton()->flush();
1500
1501 // Traditionally, systems running on memory-backed caches were quite
1502 // zealous about destroying *all* memory-backed caches during a flush().
1503 // These flushes simulate that legacy behavior. However, they should probably
1504 // be removed at some point.
1505 $localDrivers = ['CRM_Utils_Cache_ArrayCache', 'CRM_Utils_Cache_NoCache'];
1506 if (Civi\Core\Container::isContainerBooted()
1507 && !in_array(get_class(CRM_Utils_Cache::singleton()), $localDrivers)) {
1508 Civi::cache('long')->flush();
1509 Civi::cache('settings')->flush();
1510 Civi::cache('js_strings')->flush();
1511 Civi::cache('community_messages')->flush();
1512 Civi::cache('groups')->flush();
1513 Civi::cache('navigation')->flush();
1514 Civi::cache('customData')->flush();
1515 Civi::cache('contactTypes')->clear();
1516 Civi::cache('metadata')->clear();
1517 CRM_Extension_System::singleton()->getCache()->flush();
1518 CRM_Cxn_CiviCxnHttp::singleton()->getCache()->flush();
1519 }
1520
1521 // also reset the various static memory caches
1522
1523 // reset the memory or array cache
1524 Civi::cache('fields')->flush();
1525
1526 // reset ACL cache
1527 CRM_ACL_BAO_Cache::resetCache();
1528
1529 // clear asset builder folder
1530 \Civi::service('asset_builder')->clear(FALSE);
1531
1532 // reset various static arrays used here
1533 CRM_Contact_BAO_Contact::$_importableFields = CRM_Contact_BAO_Contact::$_exportableFields
1534 = CRM_Contribute_BAO_Contribution::$_importableFields
1535 = CRM_Contribute_BAO_Contribution::$_exportableFields
1536 = CRM_Pledge_BAO_Pledge::$_exportableFields
1537 = CRM_Core_BAO_CustomField::$_importFields
1538 = CRM_Core_DAO::$_dbColumnValueCache = NULL;
1539
1540 CRM_Core_OptionGroup::flushAll();
1541 CRM_Utils_PseudoConstant::flushAll();
1542 }
1543
1544 /**
1545 * Load CMS bootstrap.
1546 *
1547 * @param array $params
1548 * Array with uid name and pass
1549 * @param bool $loadUser
1550 * Boolean load user or not.
1551 * @param bool $throwError
1552 * @param string $realPath
1553 */
1554 public static function loadBootStrap($params = [], $loadUser = TRUE, $throwError = TRUE, $realPath = NULL) {
1555 if (!is_array($params)) {
1556 $params = [];
1557 }
1558 $config = CRM_Core_Config::singleton();
1559 $result = $config->userSystem->loadBootStrap($params, $loadUser, $throwError, $realPath);
1560 if (is_callable([$config->userSystem, 'setMySQLTimeZone'])) {
1561 $config->userSystem->setMySQLTimeZone();
1562 }
1563 return $result;
1564 }
1565
1566 /**
1567 * Get Base CMS url.
1568 *
1569 * @return mixed|string
1570 */
1571 public static function baseCMSURL() {
1572 static $_baseURL = NULL;
1573 if (!$_baseURL) {
1574 $config = CRM_Core_Config::singleton();
1575 $_baseURL = $userFrameworkBaseURL = $config->userFrameworkBaseURL;
1576
1577 if ($config->userFramework == 'Joomla') {
1578 // gross hack
1579 // we need to remove the administrator/ from the end
1580 $_baseURL = str_replace("/administrator/", "/", $userFrameworkBaseURL);
1581 }
1582 else {
1583 // Drupal setting
1584 global $civicrm_root;
1585 if (strpos($civicrm_root,
1586 DIRECTORY_SEPARATOR . 'sites' .
1587 DIRECTORY_SEPARATOR . 'all' .
1588 DIRECTORY_SEPARATOR . 'modules'
1589 ) === FALSE
1590 ) {
1591 $startPos = strpos($civicrm_root,
1592 DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR
1593 );
1594 $endPos = strpos($civicrm_root,
1595 DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR
1596 );
1597 if ($startPos && $endPos) {
1598 // if component is in sites/SITENAME/modules
1599 $siteName = substr($civicrm_root,
1600 $startPos + 7,
1601 $endPos - $startPos - 7
1602 );
1603
1604 $_baseURL = $userFrameworkBaseURL . "sites/$siteName/";
1605 }
1606 }
1607 }
1608 }
1609 return $_baseURL;
1610 }
1611
1612 /**
1613 * Given a URL, return a relative URL if possible.
1614 *
1615 * @param string $url
1616 *
1617 * @return string
1618 */
1619 public static function relativeURL($url) {
1620 CRM_Core_Error::deprecatedFunctionWarning('url');
1621 // check if url is relative, if so return immediately
1622 if (substr($url, 0, 4) != 'http') {
1623 return $url;
1624 }
1625
1626 // make everything relative from the baseFilePath
1627 $baseURL = self::baseCMSURL();
1628
1629 // check if baseURL is a substr of $url, if so
1630 // return rest of string
1631 if (substr($url, 0, strlen($baseURL)) == $baseURL) {
1632 return substr($url, strlen($baseURL));
1633 }
1634
1635 // return the original value
1636 return $url;
1637 }
1638
1639 /**
1640 * Produce an absolute URL from a possibly-relative URL.
1641 *
1642 * @param string $url
1643 * @param bool $removeLanguagePart
1644 *
1645 * @return string
1646 */
1647 public static function absoluteURL($url, $removeLanguagePart = FALSE) {
1648 CRM_Core_Error::deprecatedFunctionWarning('url');
1649 // check if url is already absolute, if so return immediately
1650 if (substr($url, 0, 4) == 'http') {
1651 return $url;
1652 }
1653
1654 // make everything absolute from the baseFileURL
1655 $baseURL = self::baseCMSURL();
1656
1657 //CRM-7622: drop the language from the URL if requested (and it’s there)
1658 $config = CRM_Core_Config::singleton();
1659 if ($removeLanguagePart) {
1660 $baseURL = self::languageNegotiationURL($baseURL, FALSE, TRUE);
1661 }
1662
1663 return $baseURL . $url;
1664 }
1665
1666 /**
1667 * Clean url, replaces first '&' with '?'.
1668 *
1669 * @param string $url
1670 *
1671 * @return string
1672 * , clean url
1673 */
1674 public static function cleanUrl($url) {
1675 if (!$url) {
1676 return NULL;
1677 }
1678
1679 if ($pos = strpos($url, '&')) {
1680 $url = substr_replace($url, '?', $pos, 1);
1681 }
1682
1683 return $url;
1684 }
1685
1686 /**
1687 * Format the url as per language Negotiation.
1688 *
1689 * @param string $url
1690 *
1691 * @param bool $addLanguagePart
1692 * @param bool $removeLanguagePart
1693 *
1694 * @return string
1695 * , formatted url.
1696 */
1697 public static function languageNegotiationURL(
1698 $url,
1699 $addLanguagePart = TRUE,
1700 $removeLanguagePart = FALSE
1701 ) {
1702 return CRM_Core_Config::singleton()->userSystem->languageNegotiationURL($url, $addLanguagePart, $removeLanguagePart);
1703 }
1704
1705 /**
1706 * Append the contents of an 'extra' smarty template file.
1707 *
1708 * It must be present in the custom template directory. This does not work if there are
1709 * multiple custom template directories
1710 *
1711 * @param string $fileName
1712 * The name of the tpl file that we are processing.
1713 * @param string $content
1714 * The current content string. May be modified by this function.
1715 * @param string $overideFileName
1716 * (optional) Sent by contribution/event reg/profile pages which uses a id
1717 * specific extra file name if present.
1718 */
1719 public static function appendTPLFile(
1720 $fileName,
1721 &$content,
1722 $overideFileName = NULL
1723 ) {
1724 $template = CRM_Core_Smarty::singleton();
1725 if ($overideFileName) {
1726 $additionalTPLFile = $overideFileName;
1727 }
1728 else {
1729 $additionalTPLFile = str_replace('.tpl', '.extra.tpl', $fileName);
1730 }
1731
1732 if ($template->template_exists($additionalTPLFile)) {
1733 $content .= $template->fetch($additionalTPLFile);
1734 }
1735 }
1736
1737 /**
1738 * Get a list of all files that are found within the directories.
1739 *
1740 * Files must be the result of appending the provided relative path to
1741 * each component of the PHP include path.
1742 *
1743 * @author Ken Zalewski
1744 *
1745 * @param string $relpath
1746 * A relative path, typically pointing to a directory with multiple class
1747 * files.
1748 *
1749 * @return array
1750 * An array of files that exist in one or more of the directories that are
1751 * referenced by the relative path when appended to each element of the PHP
1752 * include path.
1753 */
1754 public static function listIncludeFiles($relpath) {
1755 $file_list = [];
1756 $inc_dirs = explode(PATH_SEPARATOR, get_include_path());
1757 foreach ($inc_dirs as $inc_dir) {
1758 $target_dir = $inc_dir . DIRECTORY_SEPARATOR . $relpath;
1759 // While it seems pointless to have a folder that's outside open_basedir
1760 // listed in include_path and that seems more like a configuration issue,
1761 // not everyone has control over the hosting provider's include_path and
1762 // this does happen out in the wild, so use our wrapper to avoid flooding
1763 // logs.
1764 if (CRM_Utils_File::isDir($target_dir)) {
1765 $cur_list = scandir($target_dir);
1766 foreach ($cur_list as $fname) {
1767 if ($fname != '.' && $fname != '..') {
1768 $file_list[$fname] = $fname;
1769 }
1770 }
1771 }
1772 }
1773 return $file_list;
1774 }
1775
1776 /**
1777 * Get a list of all "plugins".
1778 *
1779 * (PHP classes that implement a piece of
1780 * functionality using a well-defined interface) that are found in a
1781 * particular CiviCRM directory (both custom and core are searched).
1782 *
1783 * @author Ken Zalewski
1784 *
1785 * @param string $relpath
1786 * A relative path referencing a directory that contains one or more
1787 * plugins.
1788 * @param string $fext
1789 * (optional) Only files with this extension will be considered to be
1790 * plugins.
1791 * @param array $skipList
1792 * (optional) List of files to skip.
1793 *
1794 * @return array
1795 * List of plugins, where the plugin name is both the key and the value of
1796 * each element.
1797 */
1798 public static function getPluginList($relpath, $fext = '.php', $skipList = []) {
1799 $fext_len = strlen($fext);
1800 $plugins = [];
1801 $inc_files = CRM_Utils_System::listIncludeFiles($relpath);
1802 foreach ($inc_files as $inc_file) {
1803 if (substr($inc_file, 0 - $fext_len) == $fext) {
1804 $plugin_name = substr($inc_file, 0, 0 - $fext_len);
1805 if (!in_array($plugin_name, $skipList)) {
1806 $plugins[$plugin_name] = $plugin_name;
1807 }
1808 }
1809 }
1810 return $plugins;
1811 }
1812
1813 /**
1814 * Execute scheduled jobs.
1815 */
1816 public static function executeScheduledJobs() {
1817 $facility = new CRM_Core_JobManager();
1818 $facility->execute(FALSE);
1819
1820 $redirectUrl = self::url('civicrm/admin/job', 'reset=1');
1821
1822 CRM_Core_Session::setStatus(
1823 ts('Scheduled jobs have been executed according to individual timing settings. Please check log for messages.'),
1824 ts('Complete'), 'success');
1825
1826 CRM_Utils_System::redirect($redirectUrl);
1827 }
1828
1829 /**
1830 * Evaluate any tokens in a URL.
1831 *
1832 * @param string|false $url
1833 *
1834 * @return string|FALSE
1835 */
1836 public static function evalUrl($url) {
1837 if (!$url || strpos($url, '{') === FALSE) {
1838 return $url;
1839 }
1840 else {
1841 $config = CRM_Core_Config::singleton();
1842 $tsLocale = CRM_Core_I18n::getLocale();
1843 $vars = [
1844 '{ver}' => CRM_Utils_System::version(),
1845 '{uf}' => $config->userFramework,
1846 '{php}' => phpversion(),
1847 '{sid}' => self::getSiteID(),
1848 '{baseUrl}' => $config->userFrameworkBaseURL,
1849 '{lang}' => $tsLocale,
1850 '{co}' => $config->defaultContactCountry,
1851 ];
1852 return strtr($url, array_map('urlencode', $vars));
1853 }
1854 }
1855
1856 /**
1857 * Returns the unique identifier for this site, as used by community messages.
1858 *
1859 * SiteID will be generated if it is not already stored in the settings table.
1860 *
1861 * @return string
1862 */
1863 public static function getSiteID() {
1864 $sid = Civi::settings()->get('site_id');
1865 if (!$sid) {
1866 $config = CRM_Core_Config::singleton();
1867 $sid = md5('sid_' . (defined('CIVICRM_SITE_KEY') ? CIVICRM_SITE_KEY : '') . '_' . $config->userFrameworkBaseURL);
1868 civicrm_api3('Setting', 'create', ['domain_id' => 'all', 'site_id' => $sid]);
1869 }
1870 return $sid;
1871 }
1872
1873 /**
1874 * Is in upgrade mode.
1875 *
1876 * @return bool
1877 * @deprecated
1878 * @see CRM_Core_Config::isUpgradeMode()
1879 */
1880 public static function isInUpgradeMode() {
1881 return CRM_Core_Config::isUpgradeMode();
1882 }
1883
1884 /**
1885 * Determine the standard URL for view/update/delete of a given entity.
1886 *
1887 * @param array $crudLinkSpec
1888 * With keys:.
1889 * - action: sting|int, e.g. 'update' or CRM_Core_Action::UPDATE or 'view' or CRM_Core_Action::VIEW [default: 'view']
1890 * - entity|entity_table: string, eg "Contact" or "civicrm_contact"
1891 * - id|entity_id: int
1892 *
1893 * @param bool $absolute whether the generated link should have an absolute (external) URL beginning with http
1894 *
1895 * @return array|NULL
1896 * NULL if unavailable, or an array. array has keys:
1897 * - title: string
1898 * - url: string
1899 */
1900 public static function createDefaultCrudLink($crudLinkSpec, $absolute = FALSE) {
1901 $action = $crudLinkSpec['action'] ?? 'view';
1902 if (is_numeric($action)) {
1903 $action = CRM_Core_Action::description($action);
1904 }
1905 else {
1906 $action = strtolower($action);
1907 }
1908
1909 $daoClass = isset($crudLinkSpec['entity']) ? CRM_Core_DAO_AllCoreTables::getFullName($crudLinkSpec['entity']) : CRM_Core_DAO_AllCoreTables::getClassForTable($crudLinkSpec['entity_table']);
1910 $paths = $daoClass ? $daoClass::getEntityPaths() : [];
1911 $path = $paths[$action] ?? NULL;
1912 if (!$path) {
1913 return NULL;
1914 }
1915
1916 if (empty($crudLinkSpec['id']) && !empty($crudLinkSpec['entity_id'])) {
1917 $crudLinkSpec['id'] = $crudLinkSpec['entity_id'];
1918 }
1919 foreach ($crudLinkSpec as $key => $value) {
1920 $path = str_replace('[' . $key . ']', $value, $path);
1921 }
1922
1923 switch ($action) {
1924 case 'add':
1925 $title = ts('New %1', [1 => $daoClass::getEntityTitle()]);
1926 break;
1927
1928 case 'view':
1929 $title = ts('View %1', [1 => $daoClass::getEntityTitle()]);
1930 break;
1931
1932 case 'update':
1933 $title = ts('Edit %1', [1 => $daoClass::getEntityTitle()]);
1934 break;
1935
1936 case 'delete':
1937 $title = ts('Delete %1', [1 => $daoClass::getEntityTitle()]);
1938 break;
1939
1940 default:
1941 $title = ts(ucfirst($action)) . ' ' . $daoClass::getEntityTitle();
1942 }
1943
1944 return [
1945 'title' => $title,
1946 'url' => self::url($path, NULL, $absolute, NULL, FALSE),
1947 ];
1948 }
1949
1950 /**
1951 * Return an HTTP Response with appropriate content and status code set.
1952 * @param \Psr\Http\Message\ResponseInterface $response
1953 */
1954 public static function sendResponse(\Psr\Http\Message\ResponseInterface $response) {
1955 $config = CRM_Core_Config::singleton()->userSystem->sendResponse($response);
1956 }
1957
1958 /**
1959 * Perform any necessary actions prior to redirecting via POST.
1960 */
1961 public static function prePostRedirect() {
1962 CRM_Core_Config::singleton()->userSystem->prePostRedirect();
1963 }
1964
1965 }