Various phpdoc fixes
[civicrm-core.git] / CRM / Utils / System.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
TO
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 |
6a488035 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035
TO
11
12/**
13 *
14 * @package CRM
ca5cec67 15 * @copyright CiviCRM LLC https://civicrm.org/licensing
6a488035
TO
16 */
17
18/**
19 * System wide utilities.
091db908
CW
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 *
4424eabf 25 * @method static void getCMSPermissionsUrlParams() Immediately stop script execution and display a 401 "Access Denied" page.
091db908
CW
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.
4424eabf
BT
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.
091db908
CW
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.
4424eabf 37 * @method static void setHttpHeader(string $name, string $value) Set http header.
091db908 38 * @method static array synchronizeUsers() Create CRM contacts for all existing CMS users.
4424eabf
BT
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.
6a488035
TO
41 */
42class CRM_Utils_System {
43
6714d8d2 44 public static $_callbacks = NULL;
6a488035 45
5af8c999 46 /**
50bfb460
SB
47 * @var string
48 * Page title
5af8c999 49 */
6714d8d2 50 public static $title = '';
5af8c999 51
091db908
CW
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;
be2fb01f 61 return call_user_func_array([$userSystem, $name], $arguments);
091db908
CW
62 }
63
6a488035 64 /**
9911500f 65 * Compose a new URL string from the current URL string.
7890c493 66 *
6a488035
TO
67 * Used by all the framework components, specifically,
68 * pager, sort and qfc
69 *
7890c493
RS
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).
414e3596 76 * @param string $path
7890c493 77 * (optional) The path to use for the new url.
f4aaa82a 78 * @param bool|string $absolute
7890c493 79 * (optional) Whether to return an absolute URL.
6a488035 80 *
7890c493
RS
81 * @return string
82 * The URL fragment.
6a488035 83 */
00be9182 84 public static function makeURL($urlVar, $includeReset = FALSE, $includeForce = TRUE, $path = NULL, $absolute = FALSE) {
13594d55
CW
85 $path = $path ?: CRM_Utils_System::currentPath();
86 if (!$path) {
87 return '';
6a488035
TO
88 }
89
6714d8d2 90 return self::url(
13594d55
CW
91 $path,
92 CRM_Utils_System::getLinksUrl($urlVar, $includeReset, $includeForce),
93 $absolute
94 );
6a488035
TO
95 }
96
97 /**
7890c493
RS
98 * Get the query string and clean it up.
99 *
9911500f 100 * Strips some variables that should not be propagated, specifically variables
7890c493 101 * like 'reset'. Also strips any side-affect actions (e.g. export).
6a488035
TO
102 *
103 * This function is copied mostly verbatim from Pager.php (_getLinksUrl)
104 *
7890c493
RS
105 * @param string $urlVar
106 * The URL variable being considered (e.g. crmPageID, crmSortID etc).
107 * @param bool $includeReset
9911500f
RS
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.
7890c493 110 * @param bool $includeForce
9911500f 111 * (optional)
7890c493 112 * @param bool $skipUFVar
9911500f 113 * (optional)
6a488035
TO
114 *
115 * @return string
6a488035 116 */
00be9182 117 public static function getLinksUrl($urlVar, $includeReset = FALSE, $includeForce = TRUE, $skipUFVar = TRUE) {
6a488035 118 // Sort out query string to prevent messy urls
be2fb01f
CW
119 $querystring = [];
120 $qs = [];
121 $arrays = [];
6a488035
TO
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);
50bfb460 131 // check for arrays in parameters: site.php?foo[]=1&foo[]=2&foo[]=3
6a488035
TO
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
d8249fcb 153 // Ok this is a big assumption but usually works
9911500f
RS
154 // If we are in snippet mode, retain the 'section' param, if not, get rid
155 // of it.
d8249fcb
CW
156 if (!empty($qs['snippet'])) {
157 unset($qs['snippet']);
158 }
159 else {
160 unset($qs['section']);
161 }
6a488035
TO
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));
6a488035 175
fe010227 176 $url = implode('&', $querystring);
3ab88a8c 177 if ($urlVar) {
fe010227 178 $url .= (!empty($querystring) ? '&' : '') . $urlVar . '=';
3ab88a8c
DL
179 }
180
181 return $url;
6a488035
TO
182 }
183
184 /**
70599df6 185 * If we are using a theming system, invoke theme, else just print the content.
6a488035 186 *
7890c493
RS
187 * @param string $content
188 * The content that will be themed.
414e3596 189 * @param bool $print
7890c493
RS
190 * (optional) Are we displaying to the screen or bypassing theming?
191 * @param bool $maintenance
192 * (optional) For maintenance mode.
6a488035 193 *
a75c13cc 194 * @return string
6a488035 195 */
971d41b1 196 public static function theme(
6a488035 197 &$content,
e7292422 198 $print = FALSE,
6a488035
TO
199 $maintenance = FALSE
200 ) {
f3a87cf4 201 return CRM_Core_Config::singleton()->userSystem->theme($content, $print, $maintenance);
6a488035
TO
202 }
203
204 /**
7890c493 205 * Generate a query string if input is an array.
6a488035 206 *
7890c493 207 * @param array|string $query
70599df6 208 *
7890c493 209 * @return string
6a488035 210 */
00be9182 211 public static function makeQueryString($query) {
6a488035
TO
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 /**
7890c493 223 * Generate an internal CiviCRM URL.
6a488035 224 *
7890c493
RS
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
9911500f 231 * URI-scheme such as 'http:'). Useful for links that will be displayed
7890c493
RS
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.
fe010227 235 * @param bool $htmlize
f77dfbae 236 * Whether to encode special html characters such as &.
fe010227 237 * @param bool $frontend
f77dfbae 238 * This link should be to the CMS front end (applies to WP & Joomla).
fe010227 239 * @param bool $forceBackend
f77dfbae 240 * This link should be to the CMS back end (applies to WP & Joomla).
70599df6 241 *
7890c493
RS
242 * @return string
243 * An HTML string containing a link to the given path.
6a488035 244 */
971d41b1 245 public static function url(
6a488035 246 $path = NULL,
e7292422 247 $query = NULL,
6a488035
TO
248 $absolute = FALSE,
249 $fragment = NULL,
e7292422 250 $htmlize = TRUE,
6a488035
TO
251 $frontend = FALSE,
252 $forceBackend = FALSE
253 ) {
254 $query = self::makeQueryString($query);
255
c80e2dbf
CW
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" : '');
6a488035
TO
273 }
274
275 $config = CRM_Core_Config::singleton();
8de2a34e 276 $url = $config->userSystem->url($path, $query, $absolute, $fragment, $frontend, $forceBackend, $htmlize);
c80e2dbf
CW
277
278 if ($htmlize) {
279 $url = htmlentities($url);
280 }
281
282 return $url;
6a488035
TO
283 }
284
cd95cfbe
CW
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
9df3628e
AM
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.
b4c75c15
SL
336 *
337 * @return string rawencoded URL.
9df3628e
AM
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 ]);
4162ab6f 355 Civi::dispatcher()->dispatch('hook_civicrm_alterExternUrl', $event);
b4c75c15 356 return urldecode(CRM_Utils_Url::unparseUrl($event->url));
9df3628e
AM
357 }
358
28e28c36
TO
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) {
b86470b2 374 $urlTxt = CRM_Utils_System::url($path, $query, $e->absolute, $e->fragment, FALSE, TRUE);
28e28c36
TO
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
ea737cda
SL
390 case 'router:extern/widget':
391 $e->url = $mkRouteUri('civicrm/contribute/widget', $e->query);
392 break;
393
28e28c36
TO
394 // Otherwise, keep the default.
395 }
396 }
397
42a40a1c 398 /**
f31f885e
CW
399 * @deprecated
400 * @see \CRM_Utils_System::currentPath
42a40a1c 401 *
402 * @return string|null
403 */
404 public static function getUrlPath() {
f31f885e
CW
405 CRM_Core_Error::deprecatedFunctionWarning('CRM_Utils_System::currentPath');
406 return self::currentPath();
42a40a1c 407 }
408
5bc392e6 409 /**
70599df6 410 * Get href.
411 *
412 * @param string $text
413 * @param string $path
414 * @param string|array $query
5bc392e6 415 * @param bool $absolute
70599df6 416 * @param string $fragment
5bc392e6
EM
417 * @param bool $htmlize
418 * @param bool $frontend
419 * @param bool $forceBackend
420 *
421 * @return string
422 */
971d41b1 423 public static function href(
a3e55d9c 424 $text, $path = NULL, $query = NULL, $absolute = TRUE,
6a488035
TO
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
6a488035 431 /**
f31f885e 432 * Path of the current page e.g. 'civicrm/contact/view'
6a488035 433 *
f31f885e 434 * @return string|null
a6c01b45 435 * the current menu path
6a488035 436 */
00be9182 437 public static function currentPath() {
6a488035 438 $config = CRM_Core_Config::singleton();
f31f885e 439 return isset($_GET[$config->userFrameworkURLVar]) ? trim($_GET[$config->userFrameworkURLVar], '/') : NULL;
6a488035
TO
440 }
441
442 /**
70599df6 443 * Called from a template to compose a url.
6a488035 444 *
7890c493
RS
445 * @param array $params
446 * List of parameters.
6a488035 447 *
a6c01b45
CW
448 * @return string
449 * url
6a488035 450 */
00be9182 451 public static function crmURL($params) {
9c1bc317 452 $p = $params['p'] ?? NULL;
6a488035
TO
453 if (!isset($p)) {
454 $p = self::currentPath();
455 }
456
496e07aa
DL
457 return self::url(
458 $p,
6a488035
TO
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 /**
7890c493 469 * Sets the title of the page.
6a488035
TO
470 *
471 * @param string $title
091db908 472 * Document title - plain text only
6a488035 473 * @param string $pageTitle
091db908 474 * Page title (if different) - may include html
6a488035 475 */
00be9182 476 public static function setTitle($title, $pageTitle = NULL) {
5af8c999 477 self::$title = $title;
6a488035
TO
478 $config = CRM_Core_Config::singleton();
479 return $config->userSystem->setTitle($title, $pageTitle);
480 }
481
482 /**
7890c493 483 * Figures and sets the userContext.
6a488035 484 *
70599df6 485 * Uses the referrer if valid else uses the default.
7890c493
RS
486 *
487 * @param array $names
50bfb460 488 * Referrer should match any str in this array.
7890c493
RS
489 * @param string $default
490 * (optional) The default userContext if no match found.
6a488035 491 */
00be9182 492 public static function setUserContext($names, $default = NULL) {
6a488035
TO
493 $url = $default;
494
495 $session = CRM_Core_Session::singleton();
9c1bc317 496 $referer = $_SERVER['HTTP_REFERER'] ?? NULL;
6a488035
TO
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 /**
7890c493 513 * Gets a class name for an object.
6a488035 514 *
7890c493
RS
515 * @param object $object
516 * Object whose class name is needed.
6a488035 517 *
7890c493
RS
518 * @return string
519 * The class name of the object.
6a488035 520 */
00be9182 521 public static function getClassName($object) {
6a488035
TO
522 return get_class($object);
523 }
524
525 /**
7890c493 526 * Redirect to another URL.
6a488035 527 *
7890c493
RS
528 * @param string $url
529 * The URL to provide to the browser via the Location header.
f9bdf062 530 * @param array $context
531 * Optional additional information for the hook.
6a488035 532 */
f9bdf062 533 public static function redirect($url = NULL, $context = []) {
6a488035
TO
534 if (!$url) {
535 $url = self::url('civicrm/dashboard', 'reset=1');
536 }
6a488035
TO
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);
0a94ab7d 540
9c1bc317 541 $context['output'] = $_GET['snippet'] ?? NULL;
f9bdf062 542
543 $parsedUrl = CRM_Utils_Url::parseUrl($url);
544 CRM_Utils_Hook::alterRedirect($parsedUrl, $context);
545 $url = CRM_Utils_Url::unparseUrl($parsedUrl);
546
0a94ab7d 547 // If we are in a json context, respond appropriately
f9bdf062 548 if ($context['output'] === 'json') {
be2fb01f 549 CRM_Core_Page_AJAX::returnJsonResponse([
0a94ab7d
CW
550 'status' => 'redirect',
551 'userContext' => $url,
be2fb01f 552 ]);
0a94ab7d
CW
553 }
554
d42a224c 555 self::setHttpHeader('Location', $url);
67a10cc4 556 self::civiExit(0, ['url' => $url, 'context' => 'redirect']);
6a488035
TO
557 }
558
559 /**
7890c493
RS
560 * Redirect to another URL using JavaScript.
561 *
562 * Use an html based file with javascript embedded to redirect to another url
6a488035
TO
563 * This prevent the too many redirect errors emitted by various browsers
564 *
7890c493
RS
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.
6a488035 571 */
971d41b1 572 public static function jsRedirect(
e7292422
TO
573 $url = NULL,
574 $title = NULL,
6a488035
TO
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
6a488035 605 /**
7890c493 606 * Get the base URL of the system.
6a488035
TO
607 *
608 * @return string
6a488035 609 */
00be9182 610 public static function baseURL() {
6a488035
TO
611 $config = CRM_Core_Config::singleton();
612 return $config->userFrameworkBaseURL;
613 }
614
7890c493 615 /**
ad37ac8e 616 * Authenticate or abort.
617 *
618 * @param string $message
619 * @param bool $abort
620 *
621 * @return bool
7890c493 622 */
00be9182 623 public static function authenticateAbort($message, $abort) {
6a488035
TO
624 if ($abort) {
625 echo $message;
626 self::civiExit(0);
627 }
628 else {
629 return FALSE;
630 }
631 }
632
7890c493 633 /**
70599df6 634 * Authenticate key.
635 *
7890c493
RS
636 * @param bool $abort
637 * (optional) Whether to exit; defaults to true.
77b97be7
EM
638 *
639 * @return bool
7890c493 640 */
00be9182 641 public static function authenticateKey($abort = TRUE) {
6a488035
TO
642 // also make sure the key is sent and is valid
643 $key = trim(CRM_Utils_Array::value('key', $_REQUEST));
644
23af1818 645 $docAdd = "More info at: " . CRM_Utils_System::docURL2('sysadmin/setup/jobs', TRUE);
6a488035
TO
646
647 if (!$key) {
0af0e4c9
DL
648 return self::authenticateAbort(
649 "ERROR: You need to send a valid key to execute this file. " . $docAdd . "\n",
6a488035
TO
650 $abort
651 );
652 }
653
654 $siteKey = defined('CIVICRM_SITE_KEY') ? CIVICRM_SITE_KEY : NULL;
655
0af0e4c9
DL
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",
6a488035
TO
659 $abort
660 );
661 }
662
663 if (strlen($siteKey) < 8) {
0af0e4c9
DL
664 return self::authenticateAbort(
665 "ERROR: Site key needs to be greater than 7 characters in civicrm.settings.php. " . $docAdd . "\n",
6a488035
TO
666 $abort
667 );
668 }
669
a535ce3f 670 if (!hash_equals($siteKey, $key)) {
0af0e4c9
DL
671 return self::authenticateAbort(
672 "ERROR: Invalid key value sent. " . $docAdd . "\n",
6a488035
TO
673 $abort
674 );
675 }
676
677 return TRUE;
678 }
679
7890c493 680 /**
70599df6 681 * Authenticate script.
682 *
f4aaa82a 683 * @param bool $abort
70599df6 684 * @param string $name
685 * @param string $pass
f4aaa82a
EM
686 * @param bool $storeInSession
687 * @param bool $loadCMSBootstrap
688 * @param bool $requireKey
689 *
7890c493 690 * @return bool
7890c493 691 */
00be9182 692 public static function authenticateScript($abort = TRUE, $name = NULL, $pass = NULL, $storeInSession = TRUE, $loadCMSBootstrap = TRUE, $requireKey = TRUE) {
7890c493 693 // auth to make sure the user has a login/password to do a shell operation
6a488035
TO
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) {
0af0e4c9
DL
702 return self::authenticateAbort(
703 "ERROR: You need to send a valid user name and password to execute this file\n",
6a488035
TO
704 $abort
705 );
706 }
707
bec3fc7c 708 if ($requireKey && !self::authenticateKey($abort)) {
6a488035
TO
709 return FALSE;
710 }
711
712 $result = CRM_Utils_System::authenticate($name, $pass, $loadCMSBootstrap);
713 if (!$result) {
0af0e4c9
DL
714 return self::authenticateAbort(
715 "ERROR: Invalid username and/or password\n",
6a488035
TO
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) {
bec3fc7c 723 $config = CRM_Core_Config::singleton();
be2fb01f 724 $config->userSystem->setUserSession([$userID, $ufID]);
6a488035
TO
725 }
726 else {
0af0e4c9
DL
727 return self::authenticateAbort(
728 "ERROR: Unexpected error, could not match userID and contactID",
6a488035
TO
729 $abort
730 );
731 }
732 }
733
734 return $result;
735 }
736
737 /**
7890c493 738 * Authenticate the user against the uf db.
6a488035 739 *
b44e3f84 740 * In case of successful authentication, returns an array consisting of
7890c493
RS
741 * (contactID, ufID, unique string). Returns FALSE if authentication is
742 * unsuccessful.
6a488035 743 *
7890c493
RS
744 * @param string $name
745 * The username.
746 * @param string $password
747 * The password.
748 * @param bool $loadCMSBootstrap
70599df6 749 * @param string $realPath
7890c493
RS
750 *
751 * @return false|array
6a488035 752 */
00be9182 753 public static function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realPath = NULL) {
6a488035 754 $config = CRM_Core_Config::singleton();
c1e1e8b8 755
7890c493
RS
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
dc1e9be8
TO
760 *
761 * Q: Can we move this to the userSystem class so that it can be tuned
04212a64 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.
7890c493 764 */
c1e1e8b8 765 $session = CRM_Core_Session::singleton();
481a74f4 766 $session->set('civicrmInitSession', TRUE);
c1e1e8b8 767
6a488035
TO
768 return $config->userSystem->authenticate($name, $password, $loadCMSBootstrap, $realPath);
769 }
770
771 /**
7890c493 772 * Set a message in the UF to display to a user.
6a488035 773 *
7890c493
RS
774 * @param string $message
775 * The message to set.
6a488035 776 */
00be9182 777 public static function setUFMessage($message) {
6a488035
TO
778 $config = CRM_Core_Config::singleton();
779 return $config->userSystem->setMessage($message);
780 }
781
7890c493
RS
782 /**
783 * Determine whether a value is null-ish.
784 *
70599df6 785 * @param mixed $value
7890c493 786 * The value to check for null.
70599df6 787 *
7890c493 788 * @return bool
7890c493 789 */
00be9182 790 public static function isNull($value) {
6a488035
TO
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)) {
26659f0c 796 // @todo Reuse of the $value variable = asking for trouble.
6a488035 797 foreach ($value as $key => $value) {
57f8e7f0 798 if (in_array($key, CRM_Core_DAO::acceptedSQLOperators(), TRUE) || !self::isNull($value)) {
6a488035
TO
799 return FALSE;
800 }
801 }
802 return TRUE;
803 }
804 return FALSE;
805 }
806
7890c493
RS
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.
70599df6 814 *
7890c493
RS
815 * @return string
816 * The obscured credit card number.
7890c493 817 */
00be9182 818 public static function mungeCreditCard($number, $keep = 4) {
6a488035
TO
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
414e3596 827 /**
7890c493
RS
828 * Determine which PHP modules are loaded.
829 *
830 * @return array
7890c493 831 */
c8f60166 832 private static function parsePHPModules() {
6a488035
TO
833 ob_start();
834 phpinfo(INFO_MODULES);
835 $s = ob_get_contents();
836 ob_end_clean();
837
353ffa53
TO
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);
be2fb01f 842 $vModules = [];
6a488035
TO
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]);
e7292422 847 foreach ($vTmp2 as $vOne) {
353ffa53 848 $vPat = '<info>([^<]+)<\/info>';
6a488035
TO
849 $vPat3 = "/$vPat\s*$vPat\s*$vPat/";
850 $vPat2 = "/$vPat\s*$vPat/";
851 // 3cols
852 if (preg_match($vPat3, $vOne, $vMat)) {
be2fb01f 853 $vModules[$vName][trim($vMat[1])] = [trim($vMat[2]), trim($vMat[3])];
6a488035
TO
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
7890c493
RS
865 /**
866 * Get a setting from a loaded PHP module.
ad37ac8e 867 *
868 * @param string $pModuleName
869 * @param string $pSetting
870 *
871 * @return mixed
7890c493 872 */
fcc5922d 873 public static function getModuleSetting($pModuleName, $pSetting) {
6a488035
TO
874 $vModules = self::parsePHPModules();
875 return $vModules[$pModuleName][$pSetting];
876 }
877
7890c493 878 /**
70599df6 879 * Do something no-one bothered to document.
880 *
881 * @param string $title
7890c493 882 * (optional)
f4aaa82a
EM
883 *
884 * @return mixed|string
7890c493 885 */
00be9182 886 public static function memory($title = NULL) {
6a488035
TO
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
7890c493 900 /**
70599df6 901 * Download something or other.
902 *
7890c493
RS
903 * @param string $name
904 * @param string $mimeType
70599df6 905 * @param string $buffer
7890c493
RS
906 * @param string $ext
907 * @param bool $output
f4aaa82a 908 * @param string $disposition
7890c493 909 */
971d41b1 910 public static function download(
a3e55d9c 911 $name, $mimeType, &$buffer,
6a488035 912 $ext = NULL,
8f176433
M
913 $output = TRUE,
914 $disposition = 'attachment'
6a488035
TO
915 ) {
916 $now = gmdate('D, d M Y H:i:s') . ' GMT';
917
d42a224c
CW
918 self::setHttpHeader('Content-Type', $mimeType);
919 self::setHttpHeader('Expires', $now);
6a488035 920
50bfb460 921 // lem9 & loic1: IE needs specific headers
8535cfc6 922 $isIE = empty($_SERVER['HTTP_USER_AGENT']) ? FALSE : strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE');
6a488035
TO
923 if ($ext) {
924 $fileString = "filename=\"{$name}.{$ext}\"";
925 }
926 else {
927 $fileString = "filename=\"{$name}\"";
928 }
929 if ($isIE) {
d42a224c
CW
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');
6a488035
TO
933 }
934 else {
d42a224c
CW
935 self::setHttpHeader("Content-Disposition", "$disposition; $fileString");
936 self::setHttpHeader('Pragma', 'no-cache');
6a488035
TO
937 }
938
939 if ($output) {
940 print $buffer;
941 self::civiExit();
942 }
943 }
944
7890c493 945 /**
9911500f
RS
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.
7890c493 951 */
00be9182 952 public static function xMemory($title = NULL, $log = FALSE) {
4424eabf 953 $mem = (float) xdebug_memory_usage() / (float) (1024);
6a488035
TO
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
7890c493 966 /**
9911500f
RS
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 *
7890c493
RS
972 * @param string $url
973 * The URL to operate on.
70599df6 974 *
9911500f
RS
975 * @return string
976 * The fixed URL.
7890c493 977 */
00be9182 978 public static function fixURL($url) {
6a488035
TO
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
50bfb460 986 // something that will mess up, so we need to clean it up here
6a488035
TO
987 return $url;
988 }
989
990 /**
7890c493 991 * Make sure a callback is valid in the current context.
6a488035 992 *
7890c493
RS
993 * @param string $callback
994 * Name of the function to check.
6a488035 995 *
7890c493 996 * @return bool
6a488035 997 */
00be9182 998 public static function validCallback($callback) {
6a488035 999 if (self::$_callbacks === NULL) {
be2fb01f 1000 self::$_callbacks = [];
6a488035
TO
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
e7292422 1008 @include_once $fileName;
6a488035
TO
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 /**
7890c493
RS
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
70599df6 1040 *
7890c493 1041 * @return string[]
6a488035 1042 */
00be9182 1043 public static function explode($separator, $string, $limit) {
6a488035
TO
1044 $result = explode($separator, $string, $limit);
1045 for ($i = count($result); $i < $limit; $i++) {
1046 $result[$i] = NULL;
1047 }
1048 return $result;
1049 }
1050
7890c493 1051 /**
70599df6 1052 * Check url.
1053 *
7890c493
RS
1054 * @param string $url
1055 * The URL to check.
1056 * @param bool $addCookie
1057 * (optional)
f4aaa82a
EM
1058 *
1059 * @return mixed
7890c493 1060 */
00be9182 1061 public static function checkURL($url, $addCookie = FALSE) {
6a488035
TO
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
481a74f4 1071 curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
6a488035 1072
8d1adeef
NG
1073 // CRM-13227, CRM-14744: only return the SSL error status
1074 return (curl_exec($ch) !== FALSE);
6a488035
TO
1075 }
1076
7890c493
RS
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
414e3596 1083 * (optional) Whether to fatally abort if the version requirement is not
7890c493 1084 * met. Defaults to TRUE.
70599df6 1085 *
7890c493
RS
1086 * @return bool
1087 * Returns TRUE if the requirement is met, FALSE if the requirement is not
9911500f
RS
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.
ee3db087
SL
1090 *
1091 * @throws CRM_Core_Exception
7890c493 1092 */
00be9182 1093 public static function checkPHPVersion($ver = 5, $abort = TRUE) {
6a488035
TO
1094 $phpVersion = substr(PHP_VERSION, 0, 1);
1095 if ($phpVersion >= $ver) {
1096 return TRUE;
1097 }
1098
1099 if ($abort) {
ee3db087 1100 throw new CRM_Core_Exception(ts('This feature requires PHP Version %1 or greater',
be2fb01f 1101 [1 => $ver]
353ffa53 1102 ));
6a488035
TO
1103 }
1104 return FALSE;
1105 }
1106
7890c493 1107 /**
70599df6 1108 * Encode url.
1109 *
7890c493 1110 * @param string $url
f4aaa82a
EM
1111 *
1112 * @return null|string
7890c493 1113 */
00be9182 1114 public static function urlEncode($url) {
8e0c92e1 1115 CRM_Core_Error::deprecatedFunctionWarning('urlEncode');
6a488035
TO
1116 $items = parse_url($url);
1117 if ($items === FALSE) {
1118 return NULL;
1119 }
1120
a7488080 1121 if (empty($items['query'])) {
6a488035
TO
1122 return $url;
1123 }
1124
1125 $items['query'] = urlencode($items['query']);
1126
1127 $url = $items['scheme'] . '://';
a7488080 1128 if (!empty($items['user'])) {
6a488035
TO
1129 $url .= "{$items['user']}:{$items['pass']}@";
1130 }
1131
1132 $url .= $items['host'];
a7488080 1133 if (!empty($items['port'])) {
6a488035
TO
1134 $url .= ":{$items['port']}";
1135 }
1136
1137 $url .= "{$items['path']}?{$items['query']}";
a7488080 1138 if (!empty($items['fragment'])) {
6a488035
TO
1139 $url .= "#{$items['fragment']}";
1140 }
1141
1142 return $url;
1143 }
1144
1145 /**
7890c493 1146 * Return the running civicrm version.
6a488035 1147 *
7890c493
RS
1148 * @return string
1149 * civicrm version
ee3db087
SL
1150 *
1151 * @throws CRM_Core_Exception
6a488035 1152 */
00be9182 1153 public static function version() {
6a488035
TO
1154 static $version;
1155
1156 if (!$version) {
1157 $verFile = implode(DIRECTORY_SEPARATOR,
be2fb01f 1158 [dirname(__FILE__), '..', '..', 'xml', 'version.xml']
6a488035
TO
1159 );
1160 if (file_exists($verFile)) {
e48985ca 1161 $str = file_get_contents($verFile);
1162 $xmlObj = simplexml_load_string($str);
1163 $version = (string) $xmlObj->version_no;
6a488035
TO
1164 }
1165
1166 // pattern check
1167 if (!CRM_Utils_System::isVersionFormatValid($version)) {
ee3db087 1168 throw new CRM_Core_Exception('Unknown codebase version.');
6a488035
TO
1169 }
1170 }
1171
1172 return $version;
1173 }
1174
b769826b 1175 /**
3d469574 1176 * Gives the first two parts of the version string E.g. 6.1.
b769826b
CW
1177 *
1178 * @return string
1179 */
1180 public static function majorVersion() {
1181 list($a, $b) = explode('.', self::version());
1182 return "$a.$b";
1183 }
1184
7890c493
RS
1185 /**
1186 * Determines whether a string is a valid CiviCRM version string.
1187 *
1188 * @param string $version
1189 * Version string to be checked.
3d469574 1190 *
7890c493 1191 * @return bool
7890c493 1192 */
00be9182 1193 public static function isVersionFormatValid($version) {
6a488035
TO
1194 return preg_match("/^(\d{1,2}\.){2,3}(\d{1,2}|(alpha|beta)\d{1,2})(\.upgrade)?$/", $version);
1195 }
1196
7890c493
RS
1197 /**
1198 * Wraps or emulates PHP's getallheaders() function.
7890c493 1199 */
00be9182 1200 public static function getAllHeaders() {
6a488035
TO
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
be2fb01f 1207 $headers = [];
6a488035
TO
1208 foreach ($_SERVER as $name => $value) {
1209 if (substr($name, 0, 5) == 'HTTP_') {
1210 $headers[str_replace(' ',
1211 '-',
1212 ucwords(strtolower(str_replace('_',
353ffa53
TO
1213 ' ',
1214 substr($name, 5)
1215 )
1216 ))
6a488035
TO
1217 )] = $value;
1218 }
1219 }
1220 return $headers;
1221 }
1222
3d469574 1223 /**
1224 * Get request headers.
1225 *
1226 * @return array|false
1227 */
00be9182 1228 public static function getRequestHeaders() {
6a488035
TO
1229 if (function_exists('apache_request_headers')) {
1230 return apache_request_headers();
1231 }
1232 else {
1233 return $_SERVER;
1234 }
1235 }
1236
1237 /**
7890c493
RS
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.
6a488035 1242 */
e7292422 1243 public static function isSSL() {
fe0dbeda 1244 return !empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off';
6a488035
TO
1245 }
1246
3d469574 1247 /**
1248 * Redirect to SSL.
1249 *
1250 * @param bool|FALSE $abort
1251 *
ee3db087 1252 * @throws \CRM_Core_Exception
3d469574 1253 */
00be9182 1254 public static function redirectToSSL($abort = FALSE) {
6a488035
TO
1255 $config = CRM_Core_Config::singleton();
1256 $req_headers = self::getRequestHeaders();
3e6b8905 1257 // FIXME: Shouldn't the X-Forwarded-Proto check be part of CRM_Utils_System::isSSL()?
aaffa79f 1258 if (Civi::settings()->get('enableSSL') &&
6a488035
TO
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']}";
b12378a7
CB
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]);
6a488035
TO
1266 if (!self::checkURL($url, TRUE)) {
1267 if ($abort) {
ee3db087 1268 throw new CRM_Core_Exception('HTTPS is not set up on this machine');
6a488035
TO
1269 }
1270 else {
e7292422 1271 CRM_Core_Session::setStatus(ts('HTTPS is not set up on this machine'), ts('Warning'), 'alert');
6a488035
TO
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
d424ffde 1281 /**
5df36634
PJ
1282 * Get logged in user's IP address.
1283 *
7890c493
RS
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)
5df36634 1287 *
7890c493
RS
1288 * @param bool $strictIPV4
1289 * (optional) Whether to return only IPv4 addresses.
1290 *
1291 * @return string
1292 * IP address of logged in user.
5df36634 1293 */
00be9182 1294 public static function ipAddress($strictIPV4 = TRUE) {
9c1bc317 1295 $address = $_SERVER['REMOTE_ADDR'] ?? NULL;
6a488035
TO
1296
1297 $config = CRM_Core_Config::singleton();
414e3596 1298 if ($config->userSystem->is_drupal && function_exists('ip_address')) {
50bfb460 1299 // drupal function handles the server being behind a proxy securely. We still have legacy ipn methods
414e3596 1300 // that reach this point without bootstrapping hence the check that the fn exists
e7292422 1301 $address = ip_address();
6a488035
TO
1302 }
1303
1304 // hack for safari
1305 if ($address == '::1') {
1306 $address = '127.0.0.1';
1307 }
1308
5df36634
PJ
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
6a488035
TO
1318 return $address;
1319 }
1320
1321 /**
7890c493 1322 * Get the referring / previous page URL.
6a488035 1323 *
7890c493
RS
1324 * @return string
1325 * The previous page URL
6a488035 1326 */
00be9182 1327 public static function refererPath() {
914d3734 1328 return $_SERVER['HTTP_REFERER'] ?? NULL;
6a488035
TO
1329 }
1330
1331 /**
7890c493 1332 * Get the documentation base URL.
6a488035 1333 *
7890c493
RS
1334 * @return string
1335 * Base URL of the CRM documentation.
6a488035 1336 */
00be9182 1337 public static function getDocBaseURL() {
6a488035 1338 // FIXME: move this to configuration at some stage
c15f87c1 1339 return 'https://docs.civicrm.org/';
6a488035
TO
1340 }
1341
1342 /**
7890c493 1343 * Returns wiki (alternate) documentation URL base.
6a488035 1344 *
a6c01b45
CW
1345 * @return string
1346 * documentation url
6a488035 1347 */
00be9182 1348 public static function getWikiBaseURL() {
6a488035
TO
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.
7890c493 1355 *
6a488035 1356 * For use in PHP code.
7890c493
RS
1357 * WARNING: Always returns URL, if ts function is not defined ($URLonly has
1358 * no effect).
6a488035 1359 *
7890c493
RS
1360 * @param string $page
1361 * Title of documentation wiki page.
77855840 1362 * @param bool $URLonly
7890c493 1363 * (optional) Whether to return URL only or full HTML link (default).
a2f24340 1364 * @param string|null $text
7890c493 1365 * (optional) Text of HTML link (no effect if $URLonly = false).
a2f24340 1366 * @param string|null $title
7890c493 1367 * (optional) Tooltip text for HTML link (no effect if $URLonly = false)
a2f24340 1368 * @param string|null $style
7890c493 1369 * (optional) Style attribute value for HTML link (no effect if $URLonly = false)
a2f24340 1370 * @param string|null $resource
f4aaa82a 1371 *
7890c493
RS
1372 * @return string
1373 * URL or link to documentation page, based on provided parameters.
6a488035 1374 */
00be9182 1375 public static function docURL2($page, $URLonly = FALSE, $text = NULL, $title = NULL, $style = NULL, $resource = NULL) {
6a488035
TO
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') {
e7292422 1380 $docBaseURL = self::getWikiBaseURL();
0db6c3e1
TO
1381 }
1382 else {
6a488035 1383 $docBaseURL = self::getDocBaseURL();
c15f87c1 1384 $page = self::formatDocUrl($page);
6a488035
TO
1385 }
1386 return $docBaseURL . str_replace(' ', '+', $page);
1387 }
1388 else {
be2fb01f 1389 $params = [
6a488035
TO
1390 'page' => $page,
1391 'URLonly' => $URLonly,
1392 'text' => $text,
1393 'title' => $title,
1394 'style' => $style,
1395 'resource' => $resource,
be2fb01f 1396 ];
6a488035
TO
1397 return self::docURL($params);
1398 }
1399 }
1400
1401 /**
1402 * Returns URL or link to documentation page, based on provided parameters.
7890c493 1403 *
6a488035
TO
1404 * For use in templates code.
1405 *
7890c493
RS
1406 * @param array $params
1407 * An array of parameters (see CRM_Utils_System::docURL2 method for names)
6a488035 1408 *
b8c71ffa 1409 * @return null|string
7890c493 1410 * URL or link to documentation page, based on provided parameters.
6a488035 1411 */
00be9182 1412 public static function docURL($params) {
6a488035
TO
1413
1414 if (!isset($params['page'])) {
c301f76e 1415 return NULL;
6a488035
TO
1416 }
1417
1418 if (CRM_Utils_Array::value('resource', $params) == 'wiki') {
1419 $docBaseURL = self::getWikiBaseURL();
0db6c3e1
TO
1420 }
1421 else {
6a488035 1422 $docBaseURL = self::getDocBaseURL();
c15f87c1 1423 $params['page'] = self::formatDocUrl($params['page']);
6a488035
TO
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) {
d05f3550 1431 $params['text'] = ts('(Learn more...)');
6a488035
TO
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 {
16e71c83 1447 return "<a href=\"{$link}\" $style target=\"_blank\" class=\"crm-doc-link no-popup\" title=\"{$params['title']}\">{$params['text']}</a>";
6a488035
TO
1448 }
1449 }
1450
c15f87c1
CW
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) {
a0848d3f 1460 return preg_replace('#^(installation|user|sysadmin|dev)/#', '\1/en/latest/', $url);
c15f87c1
CW
1461 }
1462
7890c493
RS
1463 /**
1464 * Exit with provided exit code.
1465 *
1466 * @param int $status
1467 * (optional) Code with which to exit.
62e245e6 1468 *
68989e71 1469 * @param array $testParameters
7890c493 1470 */
68989e71 1471 public static function civiExit($status = 0, $testParameters = []) {
307d2406 1472
62e245e6 1473 if (CIVICRM_UF === 'UnitTests') {
68989e71 1474 throw new CRM_Core_Exception_PrematureExitException('civiExit called', $testParameters);
62e245e6 1475 }
d74139ea 1476 if ($status > 0) {
307d2406
MM
1477 http_response_code(500);
1478 }
6a488035
TO
1479 // move things to CiviCRM cache as needed
1480 CRM_Core_Session::storeSessionObjects();
1481
f2e906d7
JP
1482 if (Civi\Core\Container::isContainerBooted()) {
1483 Civi::dispatcher()->dispatch('civi.core.exit');
1484 }
1485
1486 $userSystem = CRM_Core_Config::singleton()->userSystem;
be2fb01f 1487 if (is_callable([$userSystem, 'onCiviExit'])) {
f2e906d7
JP
1488 $userSystem->onCiviExit();
1489 }
6a488035
TO
1490 exit($status);
1491 }
1492
1493 /**
7890c493 1494 * Reset the various system caches and some important static variables.
6a488035 1495 */
e7292422 1496 public static function flushCache() {
6a488035
TO
1497 // flush out all cache entries so we can reload new data
1498 // a bit aggressive, but livable for now
5aac553c 1499 CRM_Utils_Cache::singleton()->flush();
70423e0d
TO
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.
c33f1df1 1505 $localDrivers = ['CRM_Utils_Cache_ArrayCache', 'CRM_Utils_Cache_NoCache'];
70423e0d
TO
1506 if (Civi\Core\Container::isContainerBooted()
1507 && !in_array(get_class(CRM_Utils_Cache::singleton()), $localDrivers)) {
90cdaa0e 1508 Civi::cache('long')->flush();
5aac553c
TO
1509 Civi::cache('settings')->flush();
1510 Civi::cache('js_strings')->flush();
1511 Civi::cache('community_messages')->flush();
972b390e 1512 Civi::cache('groups')->flush();
96689db3 1513 Civi::cache('navigation')->flush();
792fce0b 1514 Civi::cache('customData')->flush();
81c4c148 1515 Civi::cache('contactTypes')->clear();
eb151aab 1516 Civi::cache('metadata')->clear();
5aac553c
TO
1517 CRM_Extension_System::singleton()->getCache()->flush();
1518 CRM_Cxn_CiviCxnHttp::singleton()->getCache()->flush();
1519 }
6a488035
TO
1520
1521 // also reset the various static memory caches
1522
1523 // reset the memory or array cache
9cdf85c1 1524 Civi::cache('fields')->flush();
6a488035
TO
1525
1526 // reset ACL cache
1527 CRM_ACL_BAO_Cache::resetCache();
1528
18f5b231 1529 // clear asset builder folder
1530 \Civi::service('asset_builder')->clear(FALSE);
1531
6a488035 1532 // reset various static arrays used here
c301f76e 1533 CRM_Contact_BAO_Contact::$_importableFields = CRM_Contact_BAO_Contact::$_exportableFields
971d41b1 1534 = CRM_Contribute_BAO_Contribution::$_importableFields
c301f76e 1535 = CRM_Contribute_BAO_Contribution::$_exportableFields
33a766c6 1536 = CRM_Pledge_BAO_Pledge::$_exportableFields
c301f76e 1537 = CRM_Core_BAO_CustomField::$_importFields
90936fd3 1538 = CRM_Core_DAO::$_dbColumnValueCache = NULL;
6a488035
TO
1539
1540 CRM_Core_OptionGroup::flushAll();
1541 CRM_Utils_PseudoConstant::flushAll();
1542 }
1543
1544 /**
7890c493 1545 * Load CMS bootstrap.
6a488035 1546 *
7890c493
RS
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
3d469574 1552 * @param string $realPath
6a488035 1553 */
be2fb01f 1554 public static function loadBootStrap($params = [], $loadUser = TRUE, $throwError = TRUE, $realPath = NULL) {
6a488035 1555 if (!is_array($params)) {
be2fb01f 1556 $params = [];
6a488035
TO
1557 }
1558 $config = CRM_Core_Config::singleton();
49b60138
TO
1559 $result = $config->userSystem->loadBootStrap($params, $loadUser, $throwError, $realPath);
1560 if (is_callable([$config->userSystem, 'setMySQLTimeZone'])) {
1561 $config->userSystem->setMySQLTimeZone();
1562 }
1563 return $result;
6a488035
TO
1564 }
1565
70599df6 1566 /**
1567 * Get Base CMS url.
1568 *
1569 * @return mixed|string
1570 */
00be9182 1571 public static function baseCMSURL() {
6a488035
TO
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'
353ffa53
TO
1589 ) === FALSE
1590 ) {
6a488035
TO
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
7890c493
RS
1612 /**
1613 * Given a URL, return a relative URL if possible.
1614 *
1615 * @param string $url
3d469574 1616 *
7890c493 1617 * @return string
7890c493 1618 */
00be9182 1619 public static function relativeURL($url) {
8e0c92e1 1620 CRM_Core_Error::deprecatedFunctionWarning('url');
6a488035
TO
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
7890c493
RS
1639 /**
1640 * Produce an absolute URL from a possibly-relative URL.
1641 *
1642 * @param string $url
f4aaa82a
EM
1643 * @param bool $removeLanguagePart
1644 *
7890c493 1645 * @return string
7890c493 1646 */
00be9182 1647 public static function absoluteURL($url, $removeLanguagePart = FALSE) {
8e0c92e1 1648 CRM_Core_Error::deprecatedFunctionWarning('url');
6a488035
TO
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 /**
3d469574 1667 * Clean url, replaces first '&' with '?'.
6a488035
TO
1668 *
1669 * @param string $url
1670 *
a6c01b45
CW
1671 * @return string
1672 * , clean url
6a488035 1673 */
00be9182 1674 public static function cleanUrl($url) {
6a488035
TO
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 *
f4aaa82a
EM
1691 * @param bool $addLanguagePart
1692 * @param bool $removeLanguagePart
1693 *
a6c01b45
CW
1694 * @return string
1695 * , formatted url.
6a488035 1696 */
971d41b1 1697 public static function languageNegotiationURL(
a3e55d9c 1698 $url,
6a488035
TO
1699 $addLanguagePart = TRUE,
1700 $removeLanguagePart = FALSE
1701 ) {
f3a87cf4 1702 return CRM_Core_Config::singleton()->userSystem->languageNegotiationURL($url, $addLanguagePart, $removeLanguagePart);
6a488035
TO
1703 }
1704
1705 /**
3d469574 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
6a488035
TO
1709 * multiple custom template directories
1710 *
9911500f
RS
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.
6a488035 1718 */
971d41b1 1719 public static function appendTPLFile(
a3e55d9c 1720 $fileName,
6a488035
TO
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 /**
3d469574 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
6a488035
TO
1741 * each component of the PHP include path.
1742 *
1743 * @author Ken Zalewski
1744 *
9911500f
RS
1745 * @param string $relpath
1746 * A relative path, typically pointing to a directory with multiple class
1747 * files.
6a488035 1748 *
9911500f
RS
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.
6a488035 1753 */
00be9182 1754 public static function listIncludeFiles($relpath) {
be2fb01f 1755 $file_list = [];
6a488035
TO
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 if (is_dir($target_dir)) {
1760 $cur_list = scandir($target_dir);
1761 foreach ($cur_list as $fname) {
1762 if ($fname != '.' && $fname != '..') {
1763 $file_list[$fname] = $fname;
1764 }
1765 }
1766 }
1767 }
1768 return $file_list;
1769 }
6a488035
TO
1770
1771 /**
3d469574 1772 * Get a list of all "plugins".
1773 *
1774 * (PHP classes that implement a piece of
6a488035
TO
1775 * functionality using a well-defined interface) that are found in a
1776 * particular CiviCRM directory (both custom and core are searched).
1777 *
1778 * @author Ken Zalewski
1779 *
9911500f
RS
1780 * @param string $relpath
1781 * A relative path referencing a directory that contains one or more
1782 * plugins.
1783 * @param string $fext
1784 * (optional) Only files with this extension will be considered to be
1785 * plugins.
1786 * @param array $skipList
1787 * (optional) List of files to skip.
6a488035 1788 *
9911500f
RS
1789 * @return array
1790 * List of plugins, where the plugin name is both the key and the value of
1791 * each element.
6a488035 1792 */
be2fb01f 1793 public static function getPluginList($relpath, $fext = '.php', $skipList = []) {
c490a46a 1794 $fext_len = strlen($fext);
be2fb01f 1795 $plugins = [];
6a488035
TO
1796 $inc_files = CRM_Utils_System::listIncludeFiles($relpath);
1797 foreach ($inc_files as $inc_file) {
1798 if (substr($inc_file, 0 - $fext_len) == $fext) {
1799 $plugin_name = substr($inc_file, 0, 0 - $fext_len);
1800 if (!in_array($plugin_name, $skipList)) {
1801 $plugins[$plugin_name] = $plugin_name;
1802 }
1803 }
1804 }
1805 return $plugins;
1806 }
6a488035 1807
3d469574 1808 /**
1809 * Execute scheduled jobs.
1810 */
00be9182 1811 public static function executeScheduledJobs() {
6a488035
TO
1812 $facility = new CRM_Core_JobManager();
1813 $facility->execute(FALSE);
1814
1815 $redirectUrl = self::url('civicrm/admin/job', 'reset=1');
1816
1817 CRM_Core_Session::setStatus(
1818 ts('Scheduled jobs have been executed according to individual timing settings. Please check log for messages.'),
1819 ts('Complete'), 'success');
1820
1821 CRM_Utils_System::redirect($redirectUrl);
1822 }
1823
efceedd4 1824 /**
9911500f 1825 * Evaluate any tokens in a URL.
efceedd4
TO
1826 *
1827 * @param string|FALSE $url
3d469574 1828 *
efceedd4
TO
1829 * @return string|FALSE
1830 */
1831 public static function evalUrl($url) {
202407d7
CW
1832 if (!$url || strpos($url, '{') === FALSE) {
1833 return $url;
efceedd4
TO
1834 }
1835 else {
1836 $config = CRM_Core_Config::singleton();
d59ab677 1837 $tsLocale = CRM_Core_I18n::getLocale();
be2fb01f 1838 $vars = [
efceedd4
TO
1839 '{ver}' => CRM_Utils_System::version(),
1840 '{uf}' => $config->userFramework,
1841 '{php}' => phpversion(),
202407d7 1842 '{sid}' => self::getSiteID(),
c4e76569 1843 '{baseUrl}' => $config->userFrameworkBaseURL,
d59ab677 1844 '{lang}' => $tsLocale,
c4e76569 1845 '{co}' => $config->defaultContactCountry,
be2fb01f 1846 ];
202407d7 1847 return strtr($url, array_map('urlencode', $vars));
efceedd4
TO
1848 }
1849 }
1850
202407d7 1851 /**
1ab26c95
CW
1852 * Returns the unique identifier for this site, as used by community messages.
1853 *
1854 * SiteID will be generated if it is not already stored in the settings table.
1855 *
202407d7
CW
1856 * @return string
1857 */
1858 public static function getSiteID() {
aaffa79f 1859 $sid = Civi::settings()->get('site_id');
1ab26c95
CW
1860 if (!$sid) {
1861 $config = CRM_Core_Config::singleton();
1862 $sid = md5('sid_' . (defined('CIVICRM_SITE_KEY') ? CIVICRM_SITE_KEY : '') . '_' . $config->userFrameworkBaseURL);
be2fb01f 1863 civicrm_api3('Setting', 'create', ['domain_id' => 'all', 'site_id' => $sid]);
1ab26c95
CW
1864 }
1865 return $sid;
202407d7 1866 }
efceedd4 1867
5bc392e6 1868 /**
3d469574 1869 * Is in upgrade mode.
1870 *
5bc392e6 1871 * @return bool
3ec0873b
TO
1872 * @deprecated
1873 * @see CRM_Core_Config::isUpgradeMode()
5bc392e6 1874 */
00be9182 1875 public static function isInUpgradeMode() {
3ec0873b 1876 return CRM_Core_Config::isUpgradeMode();
252e6dbc 1877 }
688ad538
TO
1878
1879 /**
002f5eaf 1880 * Determine the standard URL for view/update/delete of a given entity.
688ad538 1881 *
77855840
TO
1882 * @param array $crudLinkSpec
1883 * With keys:.
002f5eaf
CW
1884 * - action: sting|int, e.g. 'update' or CRM_Core_Action::UPDATE or 'view' or CRM_Core_Action::VIEW [default: 'view']
1885 * - entity|entity_table: string, eg "Contact" or "civicrm_contact"
1886 * - id|entity_id: int
1887 *
1888 * @param bool $absolute whether the generated link should have an absolute (external) URL beginning with http
3d469574 1889 *
72b3a70c
CW
1890 * @return array|NULL
1891 * NULL if unavailable, or an array. array has keys:
16b10e64
CW
1892 * - title: string
1893 * - url: string
688ad538 1894 */
002f5eaf
CW
1895 public static function createDefaultCrudLink($crudLinkSpec, $absolute = FALSE) {
1896 $action = $crudLinkSpec['action'] ?? 'view';
1897 if (is_numeric($action)) {
1898 $action = CRM_Core_Action::description($action);
688ad538 1899 }
002f5eaf
CW
1900 else {
1901 $action = strtolower($action);
688ad538
TO
1902 }
1903
002f5eaf
CW
1904 $daoClass = isset($crudLinkSpec['entity']) ? CRM_Core_DAO_AllCoreTables::getFullName($crudLinkSpec['entity']) : CRM_Core_DAO_AllCoreTables::getClassForTable($crudLinkSpec['entity_table']);
1905 $paths = $daoClass ? $daoClass::getEntityPaths() : [];
1906 $path = $paths[$action] ?? NULL;
1907 if (!$path) {
688ad538
TO
1908 return NULL;
1909 }
1910
002f5eaf
CW
1911 if (empty($crudLinkSpec['id']) && !empty($crudLinkSpec['entity_id'])) {
1912 $crudLinkSpec['id'] = $crudLinkSpec['entity_id'];
1913 }
1914 foreach ($crudLinkSpec as $key => $value) {
1915 $path = str_replace('[' . $key . ']', $value, $path);
688ad538
TO
1916 }
1917
002f5eaf
CW
1918 switch ($action) {
1919 case 'add':
1920 $title = ts('New %1', [1 => $daoClass::getEntityTitle()]);
1921 break;
1922
1923 case 'view':
1924 $title = ts('View %1', [1 => $daoClass::getEntityTitle()]);
1925 break;
1926
1927 case 'update':
1928 $title = ts('Edit %1', [1 => $daoClass::getEntityTitle()]);
1929 break;
1930
1931 case 'delete':
1932 $title = ts('Delete %1', [1 => $daoClass::getEntityTitle()]);
1933 break;
1934
1935 default:
1936 $title = ts(ucfirst($action)) . ' ' . $daoClass::getEntityTitle();
688ad538
TO
1937 }
1938
002f5eaf
CW
1939 return [
1940 'title' => $title,
1941 'url' => self::url($path, NULL, $absolute, NULL, FALSE),
1942 ];
688ad538 1943 }
96025800 1944
79dd7fe9
SL
1945 /**
1946 * Return an HTTP Response with appropriate content and status code set.
46dddc5c 1947 * @param \Psr\Http\Message\ResponseInterface $response
79dd7fe9 1948 */
46dddc5c 1949 public static function sendResponse(\Psr\Http\Message\ResponseInterface $response) {
90f6c8df 1950 $config = CRM_Core_Config::singleton()->userSystem->sendResponse($response);
79dd7fe9
SL
1951 }
1952
bef5923d
CW
1953 /**
1954 * Perform any necessary actions prior to redirecting via POST.
1955 */
1956 public static function prePostRedirect() {
67a10cc4 1957 CRM_Core_Config::singleton()->userSystem->prePostRedirect();
bef5923d
CW
1958 }
1959
5bc392e6 1960}