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