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