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