Merge remote-tracking branch 'upstream/4.4' into 4.4-master-2014-07-14-13-42-39
[civicrm-core.git] / CRM / Utils / System.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
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
31 * @copyright CiviCRM LLC (c) 2004-2014
32 * $Id$
33 *
34 */
35
36 /**
37 * System wide utilities.
38 *
39 */
40 class CRM_Utils_System {
41
42 static $_callbacks = NULL;
43
44 /**
45 * @var string Page title
46 */
47 static $title = '';
48
49 /**
50 * Compose a new URL string from the current URL string.
51 *
52 * Used by all the framework components, specifically,
53 * pager, sort and qfc
54 *
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).
61 * @param string $path
62 * (optional) The path to use for the new url.
63 * @param bool|string $absolute
64 * (optional) Whether to return an absolute URL.
65 *
66 * @return string
67 * The URL fragment.
68 * @access public
69 */
70 static function makeURL($urlVar, $includeReset = FALSE, $includeForce = TRUE, $path = NULL, $absolute = FALSE) {
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
79 return
80 self::url(
81 $path,
82 CRM_Utils_System::getLinksUrl($urlVar, $includeReset, $includeForce),
83 $absolute
84 );
85 }
86
87 /**
88 * Get the query string and clean it up.
89 *
90 * Strips some variables that should not be propagated, specifically variables
91 * like 'reset'. Also strips any side-affect actions (e.g. export).
92 *
93 * This function is copied mostly verbatim from Pager.php (_getLinksUrl)
94 *
95 * @param string $urlVar
96 * The URL variable being considered (e.g. crmPageID, crmSortID etc).
97 * @param bool $includeReset
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.
100 * @param bool $includeForce
101 * (optional)
102 * @param bool $skipUFVar
103 * (optional)
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
144 // Ok this is a big assumption but usually works
145 // If we are in snippet mode, retain the 'section' param, if not, get rid
146 // of it.
147 if (!empty($qs['snippet'])) {
148 unset($qs['snippet']);
149 }
150 else {
151 unset($qs['section']);
152 }
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));
166
167 $url = implode('&', $querystring);
168 if ($urlVar) {
169 $url .= (!empty($querystring) ? '&' : '') . $urlVar . '=';
170 }
171
172 return $url;
173 }
174
175 /**
176 * If we are using a theming system, invoke theme, else just print the
177 * content.
178 *
179 * @param string $content
180 * The content that will be themed.
181 * @param bool $print
182 * (optional) Are we displaying to the screen or bypassing theming?
183 * @param bool $maintenance
184 * (optional) For maintenance mode.
185 *
186 * @return string
187 *
188 * @access public
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 /**
200 * Generate a query string if input is an array.
201 *
202 * @param array|string $query
203 * @return string
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 /**
217 * Generate an internal CiviCRM URL.
218 *
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
225 * URI-scheme such as 'http:'). Useful for links that will be displayed
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.
229 *
230 * @param bool $htmlize
231 * @param bool $frontend
232 * @param bool $forceBackend
233 * @return string
234 * An HTML string containing a link to the given path.
235 * @access public
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
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 */
269 static function href($text, $path = NULL, $query = NULL, $absolute = TRUE,
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
276 /**
277 * @return mixed
278 */
279 static function permissionDenied() {
280 $config = CRM_Core_Config::singleton();
281 return $config->userSystem->permissionDenied();
282 }
283
284 /**
285 * @return mixed
286 */
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 /**
312 * This function is called from a template to compose a url.
313 *
314 * @param array $params
315 * List of parameters.
316 *
317 * @return string url
318 * @access public
319 */
320 static function crmURL($params) {
321 $p = CRM_Utils_Array::value('p', $params);
322 if (!isset($p)) {
323 $p = self::currentPath();
324 }
325
326 return self::url(
327 $p,
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 /**
338 * Sets the title of the page.
339 *
340 * @param string $title
341 * @param string $pageTitle
342 *
343 * @access public
344 */
345 static function setTitle($title, $pageTitle = NULL) {
346 self::$title = $title;
347 $config = CRM_Core_Config::singleton();
348 return $config->userSystem->setTitle($title, $pageTitle);
349 }
350
351 /**
352 * Figures and sets the userContext.
353 *
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.
360 *
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 /**
384 * Gets a class name for an object.
385 *
386 * @param object $object
387 * Object whose class name is needed.
388 *
389 * @return string
390 * The class name of the object.
391 *
392 * @access public
393 */
394 static function getClassName($object) {
395 return get_class($object);
396 }
397
398 /**
399 * Redirect to another URL.
400 *
401 * @param string $url
402 * The URL to provide to the browser via the Location header.
403 *
404 * @access public
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);
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
423 header('Location: ' . $url);
424 self::civiExit();
425 }
426
427 /**
428 * Redirect to another URL using JavaScript.
429 *
430 * Use an html based file with javascript embedded to redirect to another url
431 * This prevent the too many redirect errors emitted by various browsers
432 *
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.
439 *
440 * @access public
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 /**
476 * Append an additional breadcrumb tag to the existing breadcrumbs.
477 *
478 * @param $breadCrumbs
479 *
480 * @access public
481 */
482 static function appendBreadCrumb($breadCrumbs) {
483 $config = CRM_Core_Config::singleton();
484 return $config->userSystem->appendBreadCrumb($breadCrumbs);
485 }
486
487 /**
488 * Reset an additional breadcrumb tag to the existing breadcrumb.
489 *
490 * @access public
491 */
492 static function resetBreadCrumb() {
493 $config = CRM_Core_Config::singleton();
494 return $config->userSystem->resetBreadCrumb();
495 }
496
497 /**
498 * Append a string to the head of the HTML file.
499 *
500 * @param string $bc
501 *
502 * @access public
503 */
504 static function addHTMLHead($bc) {
505 $config = CRM_Core_Config::singleton();
506 return $config->userSystem->addHTMLHead($bc);
507 }
508
509 /**
510 * Determine the post URL for a form
511 *
512 * @param $action
513 * The default action if one is pre-specified.
514 *
515 * @return string
516 * The URL to post the form.
517 * @access public
518 */
519 static function postURL($action) {
520 $config = CRM_Core_Config::singleton();
521 return $config->userSystem->postURL($action);
522 }
523
524 /**
525 * Rewrite various system URLs to https.
526 *
527 * @access public
528 */
529 static function mapConfigToSSL() {
530 $config = CRM_Core_Config::singleton();
531 $config->userFrameworkResourceURL = str_replace('http://', 'https://', $config->userFrameworkResourceURL);
532 $config->resourceBase = $config->userFrameworkResourceURL;
533
534 if (! empty($config->extensionsURL)) {
535 $config->extensionsURL = str_replace('http://', 'https://', $config->extensionsURL);
536 }
537
538 return $config->userSystem->mapConfigToSSL();
539 }
540
541 /**
542 * Get the base URL of the system.
543 *
544 * @return string
545 * @access public
546 */
547 static function baseURL() {
548 $config = CRM_Core_Config::singleton();
549 return $config->userFrameworkBaseURL;
550 }
551
552 /**
553 */
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
564 /**
565 * @param bool $abort
566 * (optional) Whether to exit; defaults to true.
567 *
568 * @return bool
569 */
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) {
577 return self::authenticateAbort(
578 "ERROR: You need to send a valid key to execute this file. " . $docAdd . "\n",
579 $abort
580 );
581 }
582
583 $siteKey = defined('CIVICRM_SITE_KEY') ? CIVICRM_SITE_KEY : NULL;
584
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",
588 $abort
589 );
590 }
591
592 if (strlen($siteKey) < 8) {
593 return self::authenticateAbort(
594 "ERROR: Site key needs to be greater than 7 characters in civicrm.settings.php. " . $docAdd . "\n",
595 $abort
596 );
597 }
598
599 if ($key !== $siteKey) {
600 return self::authenticateAbort(
601 "ERROR: Invalid key value sent. " . $docAdd . "\n",
602 $abort
603 );
604 }
605
606 return TRUE;
607 }
608
609 /**
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 *
617 * @return bool
618 */
619 static function authenticateScript($abort = TRUE, $name = NULL, $pass = NULL, $storeInSession = TRUE, $loadCMSBootstrap = TRUE, $requireKey = TRUE) {
620 // auth to make sure the user has a login/password to do a shell operation
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) {
629 return self::authenticateAbort(
630 "ERROR: You need to send a valid user name and password to execute this file\n",
631 $abort
632 );
633 }
634
635 if ($requireKey && !self::authenticateKey($abort)) {
636 return FALSE;
637 }
638
639 $result = CRM_Utils_System::authenticate($name, $pass, $loadCMSBootstrap);
640 if (!$result) {
641 return self::authenticateAbort(
642 "ERROR: Invalid username and/or password\n",
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) {
650 $config = CRM_Core_Config::singleton();
651 $config->userSystem->setUserSession( array($userID, $ufID) );
652 }
653 else {
654 return self::authenticateAbort(
655 "ERROR: Unexpected error, could not match userID and contactID",
656 $abort
657 );
658 }
659 }
660
661 return $result;
662 }
663
664 /**
665 * Authenticate the user against the uf db.
666 *
667 * In case of succesful authentication, returns an array consisting of
668 * (contactID, ufID, unique string). Returns FALSE if authentication is
669 * unsuccessful.
670 *
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
679 * @access public
680 */
681 static function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realPath = NULL) {
682 $config = CRM_Core_Config::singleton();
683
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 */
689 $session = CRM_Core_Session::singleton();
690 $session->set( 'civicrmInitSession', TRUE );
691
692 $dbDrupal = DB::connect($config->userFrameworkDSN);
693 return $config->userSystem->authenticate($name, $password, $loadCMSBootstrap, $realPath);
694 }
695
696 /**
697 * Set a message in the UF to display to a user.
698 *
699 * @param string $message
700 * The message to set.
701 *
702 * @access public
703 */
704 static function setUFMessage($message) {
705 $config = CRM_Core_Config::singleton();
706 return $config->userSystem->setMessage($message);
707 }
708
709
710 /**
711 * Determine whether a value is null-ish.
712 *
713 * @param $value
714 * The value to check for null.
715 * @return bool
716 */
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
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.
742 */
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
752 /**
753 * Determine which PHP modules are loaded.
754 *
755 * @return array
756 */
757 public static function parsePHPModules() {
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
790 /**
791 * Get a setting from a loaded PHP module.
792 */
793 public static function getModuleSetting($pModuleName, $pSetting) {
794 $vModules = self::parsePHPModules();
795 return $vModules[$pModuleName][$pSetting];
796 }
797
798 /**
799 * @param $title
800 * (optional)
801 *
802 * @return mixed|string
803 */
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
818 /**
819 * @param string $name
820 * @param string $mimeType
821 * @param $buffer
822 * @param string $ext
823 * @param bool $output
824 * @param string $disposition
825 */
826 static function download($name, $mimeType, &$buffer,
827 $ext = NULL,
828 $output = TRUE,
829 $disposition = 'attachment'
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 {
850 header("Content-Disposition: $disposition; $fileString");
851 header('Pragma: no-cache');
852 }
853
854 if ($output) {
855 print $buffer;
856 self::civiExit();
857 }
858 }
859
860 /**
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.
866 */
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
881 /**
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 *
887 * @param string $url
888 * The URL to operate on.
889 * @return string
890 * The fixed URL.
891 */
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 /**
905 * Make sure a callback is valid in the current context.
906 *
907 * @param string $callback
908 * Name of the function to check.
909 *
910 * @return bool
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 /**
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[]
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
964 /**
965 * @param string $url
966 * The URL to check.
967 * @param bool $addCookie
968 * (optional)
969 *
970 * @return mixed
971 */
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
982 curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE );
983
984 return curl_exec($ch);
985 }
986
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
993 * (optional) Whether to fatally abort if the version requirement is not
994 * met. Defaults to TRUE.
995 * @return bool
996 * Returns TRUE if the requirement is met, FALSE if the requirement is not
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.
999 */
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
1014 /**
1015 * @param $string
1016 * @param bool $encode
1017 *
1018 * @return string
1019 */
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
1034 /**
1035 * @param string $url
1036 *
1037 * @return null|string
1038 */
1039 static function urlEncode($url) {
1040 $items = parse_url($url);
1041 if ($items === FALSE) {
1042 return NULL;
1043 }
1044
1045 if (empty($items['query'])) {
1046 return $url;
1047 }
1048
1049 $items['query'] = urlencode($items['query']);
1050
1051 $url = $items['scheme'] . '://';
1052 if (!empty($items['user'])) {
1053 $url .= "{$items['user']}:{$items['pass']}@";
1054 }
1055
1056 $url .= $items['host'];
1057 if (!empty($items['port'])) {
1058 $url .= ":{$items['port']}";
1059 }
1060
1061 $url .= "{$items['path']}?{$items['query']}";
1062 if (!empty($items['fragment'])) {
1063 $url .= "#{$items['fragment']}";
1064 }
1065
1066 return $url;
1067 }
1068
1069 /**
1070 * Return the running civicrm version.
1071 *
1072 * @return string
1073 * civicrm version
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
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
1117 */
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
1122 /**
1123 * Wraps or emulates PHP's getallheaders() function.
1124 */
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
1148 /**
1149 */
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 /**
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.
1164 */
1165 static function isSSL( ) {
1166 return
1167 (isset($_SERVER['HTTPS']) &&
1168 !empty($_SERVER['HTTPS']) &&
1169 strtolower($_SERVER['HTTPS']) != 'off') ? TRUE : FALSE;
1170 }
1171
1172 /**
1173 */
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 /*
1199 * Get logged in user's IP address.
1200 *
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)
1204 *
1205 * @param bool $strictIPV4
1206 * (optional) Whether to return only IPv4 addresses.
1207 *
1208 * @return string
1209 * IP address of logged in user.
1210 */
1211 /**
1212 * @param bool $strictIPV4
1213 *
1214 * @return mixed|string
1215 */
1216 static function ipAddress($strictIPV4 = TRUE) {
1217 $address = CRM_Utils_Array::value('REMOTE_ADDR', $_SERVER);
1218
1219 $config = CRM_Core_Config::singleton();
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();
1224 }
1225
1226 // hack for safari
1227 if ($address == '::1') {
1228 $address = '127.0.0.1';
1229 }
1230
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
1240 return $address;
1241 }
1242
1243 /**
1244 * Get the referring / previous page URL.
1245 *
1246 * @return string
1247 * The previous page URL
1248 * @access public
1249 */
1250 static function refererPath() {
1251 return CRM_Utils_Array::value('HTTP_REFERER', $_SERVER);
1252 }
1253
1254 /**
1255 * Get the documentation base URL.
1256 *
1257 * @return string
1258 * Base URL of the CRM documentation.
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 /**
1267 * Returns wiki (alternate) documentation URL base.
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.
1279 *
1280 * For use in PHP code.
1281 * WARNING: Always returns URL, if ts function is not defined ($URLonly has
1282 * no effect).
1283 *
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)
1294 *
1295 * @param null $resource
1296 *
1297 * @return string
1298 * URL or link to documentation page, based on provided parameters.
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.
1327 *
1328 * For use in templates code.
1329 *
1330 * @param array $params
1331 * An array of parameters (see CRM_Utils_System::docURL2 method for names)
1332 *
1333 * @return string
1334 * URL or link to documentation page, based on provided parameters.
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 {
1370 return "<a href=\"{$link}\" $style target=\"_blank\" class=\"crm-doc-link no-popup\" title=\"{$params['title']}\">{$params['text']}</a>";
1371 }
1372 }
1373
1374 /**
1375 * Get the locale set in the hosting CMS
1376 *
1377 * @return string
1378 * The used locale or null for none.
1379 */
1380 static function getUFLocale() {
1381 $config = CRM_Core_Config::singleton();
1382 return $config->userSystem->getUFLocale();
1383 }
1384
1385 /**
1386 * Execute external or internal URLs and return server response.
1387 *
1388 * @param string $url
1389 * Request URL.
1390 * @param bool $addCookie
1391 * Whether to provide a cookie. Should be true to access internal URLs.
1392 *
1393 * @return string
1394 * Response from URL.
1395 */
1396 static function getServerResponse($url, $addCookie = TRUE) {
1397 CRM_Core_TemporaryErrorScope::ignoreException();
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
1419 return $response;
1420 }
1421
1422 /**
1423 */
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) {
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.',
1455 array(1 => $dbVersion, 2 => $codeVersion)
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>";
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
1466 /**
1467 * Exit with provided exit code.
1468 *
1469 * @param int $status
1470 * (optional) Code with which to exit.
1471 */
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 /**
1480 * Reset the various system caches and some important static variables.
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 =
1504 CRM_Core_BAO_Cache::$_cache =
1505 CRM_Core_DAO::$_dbColumnValueCache = NULL;
1506
1507 CRM_Core_OptionGroup::flushAll();
1508 CRM_Utils_PseudoConstant::flushAll();
1509 }
1510
1511 /**
1512 * Load CMS bootstrap.
1513 *
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
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 /**
1531 * Check if user is logged in.
1532 *
1533 * @return bool
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 *
1543 * @return int
1544 * ufId, currently logged in user uf id.
1545 */
1546 public static function getLoggedInUfID() {
1547 $config = CRM_Core_Config::singleton();
1548 return $config->userSystem->getLoggedInUfID();
1549 }
1550
1551 /**
1552 */
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
1593 /**
1594 * Given a URL, return a relative URL if possible.
1595 *
1596 * @param string $url
1597 * @return string
1598 */
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
1618 /**
1619 * Produce an absolute URL from a possibly-relative URL.
1620 *
1621 * @param string $url
1622 * @param bool $removeLanguagePart
1623 *
1624 * @internal param bool $remoteLanguagePart
1625 * @return string
1626 */
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
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 *
1669 * @param bool $addLanguagePart
1670 * @param bool $removeLanguagePart
1671 *
1672 * @return string $url, formatted url.
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 *
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.
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 *
1719 * @param string $relpath
1720 * A relative path, typically pointing to a directory with multiple class
1721 * files.
1722 *
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.
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 *
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.
1762 *
1763 * @return array
1764 * List of plugins, where the plugin name is both the key and the value of
1765 * each element.
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 /**
1786 *
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
1801 /**
1802 * Evaluate any tokens in a URL.
1803 *
1804 * @param string|FALSE $url
1805 * @return string|FALSE
1806 */
1807 public static function evalUrl($url) {
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(),
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,
1821 );
1822 foreach (array_keys($vars) as $k) {
1823 $vars[$k] = urlencode($vars[$k]);
1824 }
1825 return strtr($url, $vars);
1826 }
1827 }
1828
1829
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 }
1843
1844 /**
1845 * @return bool
1846 */
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 }
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 }
1907 }