Merge pull request #95 from ravishnair/webtest-improvement
[civicrm-core.git] / CRM / Utils / System.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.3 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2013 |
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-2013
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 * Compose a new url string from the current url string
46 * Used by all the framework components, specifically,
47 * pager, sort and qfc
48 *
49 * @param string $urlVar the url variable being considered (i.e. crmPageID, crmSortID etc)
50 *
51 * @return string the url fragment
52 * @access public
53 */
54 static function makeURL($urlVar, $includeReset = FALSE, $includeForce = TRUE, $path = NULL) {
55 if (empty($path)) {
56 $config = CRM_Core_Config::singleton();
57 $path = CRM_Utils_Array::value($config->userFrameworkURLVar, $_GET);
58 if (empty($path)) {
59 return '';
60 }
61 }
62
63 return self::url($path,
64 CRM_Utils_System::getLinksUrl($urlVar, $includeReset, $includeForce)
65 );
66 }
67
68 /**
69 * get the query string and clean it up. Strip some variables that should not
70 * be propagated, specically variable like 'reset'. Also strip any side-affect
71 * actions (i.e. export)
72 *
73 * This function is copied mostly verbatim from Pager.php (_getLinksUrl)
74 *
75 * @param string $urlVar the url variable being considered (i.e. crmPageID, crmSortID etc)
76 * @param boolean $includeReset should we include the reset var (generally this variable should be skipped)
77 *
78 * @return string
79 * @access public
80 */
81 static function getLinksUrl($urlVar, $includeReset = FALSE, $includeForce = TRUE, $skipUFVar = TRUE) {
82 // Sort out query string to prevent messy urls
83 $querystring = array();
84 $qs = array();
85 $arrays = array();
86
87 if (!empty($_SERVER['QUERY_STRING'])) {
88 $qs = explode('&', str_replace('&amp;', '&', $_SERVER['QUERY_STRING']));
89 for ($i = 0, $cnt = count($qs); $i < $cnt; $i++) {
90 // check first if exist a pair
91 if (strstr($qs[$i], '=') !== FALSE) {
92 list($name, $value) = explode('=', $qs[$i]);
93 if ($name != $urlVar) {
94 $name = rawurldecode($name);
95 //check for arrays in parameters: site.php?foo[]=1&foo[]=2&foo[]=3
96 if ((strpos($name, '[') !== FALSE) &&
97 (strpos($name, ']') !== FALSE)
98 ) {
99 $arrays[] = $qs[$i];
100 }
101 else {
102 $qs[$name] = $value;
103 }
104 }
105 }
106 else {
107 $qs[$qs[$i]] = '';
108 }
109 unset($qs[$i]);
110 }
111 }
112
113 if ($includeForce) {
114 $qs['force'] = 1;
115 }
116
117 unset($qs['snippet']);
118 unset($qs['section']);
119
120 if ($skipUFVar) {
121 $config = CRM_Core_Config::singleton();
122 unset($qs[$config->userFrameworkURLVar]);
123 }
124
125 foreach ($qs as $name => $value) {
126 if ($name != 'reset' || $includeReset) {
127 $querystring[] = $name . '=' . $value;
128 }
129 }
130
131 $querystring = array_merge($querystring, array_unique($arrays));
132 $querystring = array_map('htmlentities', $querystring);
133
134 return implode('&amp;', $querystring) . (!empty($querystring) ? '&amp;' : '') . $urlVar . '=';
135 }
136
137 /**
138 * if we are using a theming system, invoke theme, else just print the
139 * content
140 *
141 * @param string $content the content that will be themed
142 * @param boolean $print are we displaying to the screen or bypassing theming?
143 * @param boolean $maintenance for maintenance mode
144 *
145 * @return void prints content on stdout
146 * @access public
147 * @static
148 */
149 static function theme(
150 &$content,
151 $print = FALSE,
152 $maintenance = FALSE
153 ) {
154 $config = &CRM_Core_Config::singleton();
155 return $config->userSystem->theme($content, $print, $maintenance);
156 }
157
158 /**
159 * Generate a query string if input is an array
160 *
161 * @param mixed $query: array or string
162 * @return str
163 *
164 * @static
165 */
166 static function makeQueryString($query) {
167 if (is_array($query)) {
168 $buf = '';
169 foreach ($query as $key => $value) {
170 $buf .= ($buf ? '&' : '') . urlencode($key) . '=' . urlencode($value);
171 }
172 $query = $buf;
173 }
174 return $query;
175 }
176
177 /**
178 * Generate an internal CiviCRM URL
179 *
180 * @param $path string The path being linked to, such as "civicrm/add"
181 * @param $query mixed A query string to append to the link, or an array of key-value pairs
182 * @param $absolute boolean Whether to force the output to be an absolute link (beginning with http:).
183 * Useful for links that will be displayed outside the site, such as in an
184 * RSS feed.
185 * @param $fragment string A fragment identifier (named anchor) to append to the link.
186 *
187 * @return string an HTML string containing a link to the given path.
188 * @access public
189 * @static
190 */
191 static function url(
192 $path = NULL,
193 $query = NULL,
194 $absolute = FALSE,
195 $fragment = NULL,
196 $htmlize = TRUE,
197 $frontend = FALSE,
198 $forceBackend = FALSE
199 ) {
200 $query = self::makeQueryString($query);
201
202 // we have a valid query and it has not yet been transformed
203 if ($htmlize && !empty($query) && strpos($query, '&amp;') === FALSE) {
204 $query = htmlentities($query);
205 }
206
207 $config = CRM_Core_Config::singleton();
208 return $config->userSystem->url($path, $query, $absolute, $fragment, $htmlize, $frontend, $forceBackend);
209 }
210
211 function href($text, $path = NULL, $query = NULL, $absolute = TRUE,
212 $fragment = NULL, $htmlize = TRUE, $frontend = FALSE, $forceBackend = FALSE
213 ) {
214 $url = self::url($path, $query, $absolute, $fragment, $htmlize, $frontend, $forceBackend);
215 return "<a href=\"$url\">$text</a>";
216 }
217
218 static function permissionDenied() {
219 $config = CRM_Core_Config::singleton();
220 return $config->userSystem->permissionDenied();
221 }
222
223 static function logout() {
224 $config = CRM_Core_Config::singleton();
225 return $config->userSystem->logout();
226 }
227
228 // this is a very drupal specific function for now
229 static function updateCategories() {
230 $config = CRM_Core_Config::singleton();
231 if ($config->userSystem->is_drupal) {
232 $config->userSystem->updateCategories();
233 }
234 }
235
236 /**
237 * What menu path are we currently on. Called for the primary tpl
238 *
239 * @return string the current menu path
240 * @access public
241 */
242 static function currentPath() {
243 $config = CRM_Core_Config::singleton();
244 return trim(CRM_Utils_Array::value($config->userFrameworkURLVar, $_GET), '/');
245 }
246
247 /**
248 * this function is called from a template to compose a url
249 *
250 * @param array $params list of parameters
251 *
252 * @return string url
253 * @access public
254 * @static
255 */
256 static function crmURL($params) {
257 $p = CRM_Utils_Array::value('p', $params);
258 if (!isset($p)) {
259 $p = self::currentPath();
260 }
261
262 return self::url($p,
263 CRM_Utils_Array::value('q', $params),
264 CRM_Utils_Array::value('a', $params, FALSE),
265 CRM_Utils_Array::value('f', $params),
266 CRM_Utils_Array::value('h', $params, TRUE),
267 CRM_Utils_Array::value('fe', $params, FALSE),
268 CRM_Utils_Array::value('fb', $params, FALSE)
269 );
270 }
271
272 /**
273 * sets the title of the page
274 *
275 * @param string $title
276 * @param string $pageTitle
277 *
278 * @return void
279 * @access public
280 * @static
281 */
282 static function setTitle($title, $pageTitle = NULL) {
283 $config = CRM_Core_Config::singleton();
284 return $config->userSystem->setTitle($title, $pageTitle);
285 }
286
287 /**
288 * figures and sets the userContext. Uses the referer if valid
289 * else uses the default
290 *
291 * @param array $names refererer should match any str in this array
292 * @param string $default the default userContext if no match found
293 *
294 * @return void
295 * @access public
296 */
297 static function setUserContext($names, $default = NULL) {
298 $url = $default;
299
300 $session = CRM_Core_Session::singleton();
301 $referer = CRM_Utils_Array::value('HTTP_REFERER', $_SERVER);
302
303 if ($referer && !empty($names)) {
304 foreach ($names as $name) {
305 if (strstr($referer, $name)) {
306 $url = $referer;
307 break;
308 }
309 }
310 }
311
312 if ($url) {
313 $session->pushUserContext($url);
314 }
315 }
316
317 /**
318 * gets a class name for an object
319 *
320 * @param object $object - object whose class name is needed
321 *
322 * @return string $className - class name
323 *
324 * @access public
325 * @static
326 */
327 static function getClassName($object) {
328 return get_class($object);
329 }
330
331 /**
332 * redirect to another url
333 *
334 * @param string $url the url to goto
335 *
336 * @return void
337 * @access public
338 * @static
339 */
340 static function redirect($url = NULL) {
341 if (!$url) {
342 $url = self::url('civicrm/dashboard', 'reset=1');
343 }
344
345 // replace the &amp; characters with &
346 // this is kinda hackish but not sure how to do it right
347 $url = str_replace('&amp;', '&', $url);
348 header('Location: ' . $url);
349 self::civiExit();
350 }
351
352 /**
353 * use a html based file with javascript embedded to redirect to another url
354 * This prevent the too many redirect errors emitted by various browsers
355 *
356 * @param string $url the url to goto
357 *
358 * @return void
359 * @access public
360 * @static
361 */
362 static function jsRedirect(
363 $url = NULL,
364 $title = NULL,
365 $message = NULL
366 ) {
367 if (!$url) {
368 $url = self::url('civicrm/dashboard', 'reset=1');
369 }
370
371 if (!$title) {
372 $title = ts('CiviCRM task in progress');
373 }
374
375 if (!$message) {
376 $message = ts('A long running CiviCRM task is currently in progress. This message will be refreshed till the task is completed');
377 }
378
379 // replace the &amp; characters with &
380 // this is kinda hackish but not sure how to do it right
381 $url = str_replace('&amp;', '&', $url);
382
383 $template = CRM_Core_Smarty::singleton();
384 $template->assign('redirectURL', $url);
385 $template->assign('title', $title);
386 $template->assign('message', $message);
387
388 $html = $template->fetch('CRM/common/redirectJS.tpl');
389
390 echo $html;
391
392 self::civiExit();
393 }
394
395 /**
396 * Append an additional breadcrumb tag to the existing breadcrumb
397 *
398 * @param string $title
399 * @param string $url
400 *
401 * @return void
402 * @access public
403 * @static
404 */
405 static function appendBreadCrumb($breadCrumbs) {
406 $config = CRM_Core_Config::singleton();
407 return $config->userSystem->appendBreadCrumb($breadCrumbs);
408 }
409
410 /**
411 * Reset an additional breadcrumb tag to the existing breadcrumb
412 *
413 * @return void
414 * @access public
415 * @static
416 */
417 static function resetBreadCrumb() {
418 $config = CRM_Core_Config::singleton();
419 return $config->userSystem->resetBreadCrumb();
420 }
421
422 /**
423 * Append a string to the head of the html file
424 *
425 * @param string $head the new string to be appended
426 *
427 * @return void
428 * @access public
429 * @static
430 */
431 static function addHTMLHead($bc) {
432 $config = CRM_Core_Config::singleton();
433 return $config->userSystem->addHTMLHead($bc);
434 }
435
436 /**
437 * figure out the post url for the form
438 *
439 * @param the default action if one is pre-specified
440 *
441 * @return string the url to post the form
442 * @access public
443 * @static
444 */
445 static function postURL($action) {
446 $config = CRM_Core_Config::singleton();
447 return $config->userSystem->postURL($action);
448 }
449
450 /**
451 * rewrite various system urls to https
452 *
453 * @return void
454 * access public
455 * @static
456 */
457 static function mapConfigToSSL() {
458 $config = CRM_Core_Config::singleton();
459 $config->userFrameworkResourceURL = str_replace('http://', 'https://',
460 $config->userFrameworkResourceURL
461 );
462 $config->resourceBase = $config->userFrameworkResourceURL;
463 return $config->userSystem->mapConfigToSSL();
464 }
465
466 /**
467 * Get the base URL from the system
468 *
469 * @param
470 *
471 * @return string
472 * @access public
473 * @static
474 */
475 static function baseURL() {
476 $config = CRM_Core_Config::singleton();
477 return $config->userFrameworkBaseURL;
478 }
479
480 static function authenticateAbort($message, $abort) {
481 if ($abort) {
482 echo $message;
483 self::civiExit(0);
484 }
485 else {
486 return FALSE;
487 }
488 }
489
490 static function authenticateKey($abort = TRUE) {
491 // also make sure the key is sent and is valid
492 $key = trim(CRM_Utils_Array::value('key', $_REQUEST));
493
494 $docAdd = "More info at:" . CRM_Utils_System::docURL2("Managing Scheduled Jobs", TRUE, NULL, NULL, NULL, "wiki");
495
496 if (!$key) {
497 return self::authenticateAbort("ERROR: You need to send a valid key to execute this file. " . $docAdd . "\n",
498 $abort
499 );
500 }
501
502 $siteKey = defined('CIVICRM_SITE_KEY') ? CIVICRM_SITE_KEY : NULL;
503
504 if (!$siteKey ||
505 empty($siteKey)
506 ) {
507 return self::authenticateAbort("ERROR: You need to set a valid site key in civicrm.settings.php. " . $docAdd . "\n",
508 $abort
509 );
510 }
511
512 if (strlen($siteKey) < 8) {
513 return self::authenticateAbort("ERROR: Site key needs to be greater than 7 characters in civicrm.settings.php. " . $docAdd . "\n",
514 $abort
515 );
516 }
517
518 if ($key !== $siteKey) {
519 return self::authenticateAbort("ERROR: Invalid key value sent. " . $docAdd . "\n",
520 $abort
521 );
522 }
523
524 return TRUE;
525 }
526
527 static function authenticateScript($abort = TRUE, $name = NULL, $pass = NULL, $storeInSession = TRUE, $loadCMSBootstrap = TRUE) {
528 // auth to make sure the user has a login/password to do a shell
529 // operation
530 // later on we'll link this to acl's
531 if (!$name) {
532 $name = trim(CRM_Utils_Array::value('name', $_REQUEST));
533 $pass = trim(CRM_Utils_Array::value('pass', $_REQUEST));
534 }
535
536 // its ok to have an empty password
537 if (!$name) {
538 return self::authenticateAbort("ERROR: You need to send a valid user name and password to execute this file\n",
539 $abort
540 );
541 }
542
543 if (!self::authenticateKey($abort)) {
544 return FALSE;
545 }
546
547 $result = CRM_Utils_System::authenticate($name, $pass, $loadCMSBootstrap);
548 if (!$result) {
549 return self::authenticateAbort("ERROR: Invalid username and/or password\n",
550 $abort
551 );
552 }
553 elseif ($storeInSession) {
554 // lets store contact id and user id in session
555 list($userID, $ufID, $randomNumber) = $result;
556 if ($userID && $ufID) {
557 $session = CRM_Core_Session::singleton();
558 $session->set('ufID', $ufID);
559 $session->set('userID', $userID);
560 }
561 else {
562 return self::authenticateAbort("ERROR: Unexpected error, could not match userID and contactID",
563 $abort
564 );
565 }
566 }
567
568 return $result;
569 }
570
571 /**
572 * Authenticate the user against the uf db
573 *
574 * @param string $name the user name
575 * @param string $password the password for the above user name
576 *
577 * @return mixed false if no auth
578 * array(
579 contactID, ufID, unique string ) if success
580 * @access public
581 * @static
582 */
583 static function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realPath = NULL) {
584 $config = CRM_Core_Config::singleton();
585 return $config->userSystem->authenticate($name, $password, $loadCMSBootstrap, $realPath);
586 }
587
588 /**
589 * Set a message in the UF to display to a user
590 *
591 * @param string $name the message to set
592 *
593 * @access public
594 * @static
595 */
596 static function setUFMessage($message) {
597 $config = CRM_Core_Config::singleton();
598 return $config->userSystem->setMessage($message);
599 }
600
601
602
603 static function isNull($value) {
604 // FIXME: remove $value = 'null' string test when we upgrade our DAO code to handle passing null in a better way.
605 if (!isset($value) || $value === NULL || $value === '' || $value === 'null') {
606 return TRUE;
607 }
608 if (is_array($value)) {
609 foreach ($value as $key => $value) {
610 if (!self::isNull($value)) {
611 return FALSE;
612 }
613 }
614 return TRUE;
615 }
616 return FALSE;
617 }
618
619 static function mungeCreditCard($number, $keep = 4) {
620 $number = trim($number);
621 if (empty($number)) {
622 return NULL;
623 }
624 $replace = str_repeat('*', strlen($number) - $keep);
625 return substr_replace($number, $replace, 0, -$keep);
626 }
627
628 /** parse php modules from phpinfo */
629 function parsePHPModules() {
630 ob_start();
631 phpinfo(INFO_MODULES);
632 $s = ob_get_contents();
633 ob_end_clean();
634
635 $s = strip_tags($s, '<h2><th><td>');
636 $s = preg_replace('/<th[^>]*>([^<]+)<\/th>/', "<info>\\1</info>", $s);
637 $s = preg_replace('/<td[^>]*>([^<]+)<\/td>/', "<info>\\1</info>", $s);
638 $vTmp = preg_split('/(<h2>[^<]+<\/h2>)/', $s, -1, PREG_SPLIT_DELIM_CAPTURE);
639 $vModules = array();
640 for ($i = 1; $i < count($vTmp); $i++) {
641 if (preg_match('/<h2>([^<]+)<\/h2>/', $vTmp[$i], $vMat)) {
642 $vName = trim($vMat[1]);
643 $vTmp2 = explode("\n", $vTmp[$i + 1]);
644 foreach ($vTmp2 AS $vOne) {
645 $vPat = '<info>([^<]+)<\/info>';
646 $vPat3 = "/$vPat\s*$vPat\s*$vPat/";
647 $vPat2 = "/$vPat\s*$vPat/";
648 // 3cols
649 if (preg_match($vPat3, $vOne, $vMat)) {
650 $vModules[$vName][trim($vMat[1])] = array(trim($vMat[2]), trim($vMat[3]));
651 // 2cols
652 }
653 elseif (preg_match($vPat2, $vOne, $vMat)) {
654 $vModules[$vName][trim($vMat[1])] = trim($vMat[2]);
655 }
656 }
657 }
658 }
659 return $vModules;
660 }
661
662 /** get a module setting */
663 function getModuleSetting($pModuleName, $pSetting) {
664 $vModules = self::parsePHPModules();
665 return $vModules[$pModuleName][$pSetting];
666 }
667
668 static function memory($title = NULL) {
669 static $pid = NULL;
670 if (!$pid) {
671 $pid = posix_getpid();
672 }
673
674 $memory = str_replace("\n", '', shell_exec("ps -p" . $pid . " -o rss="));
675 $memory .= ", " . time();
676 if ($title) {
677 CRM_Core_Error::debug_var($title, $memory);
678 }
679 return $memory;
680 }
681
682 static function download($name, $mimeType, &$buffer,
683 $ext = NULL,
684 $output = TRUE
685 ) {
686 $now = gmdate('D, d M Y H:i:s') . ' GMT';
687
688 header('Content-Type: ' . $mimeType);
689 header('Expires: ' . $now);
690
691 // lem9 & loic1: IE need specific headers
692 $isIE = strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE');
693 if ($ext) {
694 $fileString = "filename=\"{$name}.{$ext}\"";
695 }
696 else {
697 $fileString = "filename=\"{$name}\"";
698 }
699 if ($isIE) {
700 header("Content-Disposition: inline; $fileString");
701 header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
702 header('Pragma: public');
703 }
704 else {
705 header("Content-Disposition: attachment; $fileString");
706 header('Pragma: no-cache');
707 }
708
709 if ($output) {
710 print $buffer;
711 self::civiExit();
712 }
713 }
714
715 static function xMemory($title = NULL, $log = FALSE) {
716 $mem = (float ) xdebug_memory_usage() / (float )(1024);
717 $mem = number_format($mem, 5) . ", " . time();
718 if ($log) {
719 echo "<p>$title: $mem<p>";
720 flush();
721 CRM_Core_Error::debug_var($title, $mem);
722 }
723 else {
724 echo "<p>$title: $mem<p>";
725 flush();
726 }
727 }
728
729 static function fixURL($url) {
730 $components = parse_url($url);
731
732 if (!$components) {
733 return NULL;
734 }
735
736 // at some point we'll add code here to make sure the url is not
737 // something that will mess up up, so we need to clean it up here
738 return $url;
739 }
740
741 /**
742 * make sure the callback is valid in the current context
743 *
744 * @param string $callback the name of the function
745 *
746 * @return boolean
747 * @static
748 */
749 static function validCallback($callback) {
750 if (self::$_callbacks === NULL) {
751 self::$_callbacks = array();
752 }
753
754 if (!array_key_exists($callback, self::$_callbacks)) {
755 if (strpos($callback, '::') !== FALSE) {
756 list($className, $methodName) = explode('::', $callback);
757 $fileName = str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
758 // ignore errors if any
759 @include_once ($fileName);
760 if (!class_exists($className)) {
761 self::$_callbacks[$callback] = FALSE;
762 }
763 else {
764 // instantiate the class
765 $object = new $className();
766 if (!method_exists($object, $methodName)) {
767 self::$_callbacks[$callback] = FALSE;
768 }
769 else {
770 self::$_callbacks[$callback] = TRUE;
771 }
772 }
773 }
774 else {
775 self::$_callbacks[$callback] = function_exists($callback);
776 }
777 }
778 return self::$_callbacks[$callback];
779 }
780
781 /**
782 * This serves as a wrapper to the php explode function
783 * we expect exactly $limit arguments in return, and if we dont
784 * get them, we pad it with null
785 */
786 static function explode($separator, $string, $limit) {
787 $result = explode($separator, $string, $limit);
788 for ($i = count($result); $i < $limit; $i++) {
789 $result[$i] = NULL;
790 }
791 return $result;
792 }
793
794 static function checkURL($url, $addCookie = FALSE) {
795 // make a GET request to $url
796 $ch = curl_init($url);
797 if ($addCookie) {
798 curl_setopt($ch, CURLOPT_COOKIE, http_build_query($_COOKIE));
799 }
800 // it's quite alright to use a self-signed cert
801 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
802
803 // lets capture the return stuff rather than echo
804 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true );
805
806 return curl_exec($ch);
807 }
808
809 static function checkPHPVersion($ver = 5, $abort = TRUE) {
810 $phpVersion = substr(PHP_VERSION, 0, 1);
811 if ($phpVersion >= $ver) {
812 return TRUE;
813 }
814
815 if ($abort) {
816 CRM_Core_Error::fatal(ts('This feature requires PHP Version %1 or greater',
817 array(1 => $ver)
818 ));
819 }
820 return FALSE;
821 }
822
823 static function formatWikiURL($string, $encode = FALSE) {
824 $items = explode(' ', trim($string), 2);
825 if (count($items) == 2) {
826 $title = $items[1];
827 }
828 else {
829 $title = $items[0];
830 }
831
832 // fix for CRM-4044
833 $url = $encode ? self::urlEncode($items[0]) : $items[0];
834 return "<a href=\"$url\">$title</a>";
835 }
836
837 static function urlEncode($url) {
838 $items = parse_url($url);
839 if ($items === FALSE) {
840 return NULL;
841 }
842
843 if (!CRM_Utils_Array::value('query', $items)) {
844 return $url;
845 }
846
847 $items['query'] = urlencode($items['query']);
848
849 $url = $items['scheme'] . '://';
850 if (CRM_Utils_Array::value('user', $items)) {
851 $url .= "{$items['user']}:{$items['pass']}@";
852 }
853
854 $url .= $items['host'];
855 if (CRM_Utils_Array::value('port', $items)) {
856 $url .= ":{$items['port']}";
857 }
858
859 $url .= "{$items['path']}?{$items['query']}";
860 if (CRM_Utils_Array::value('fragment', $items)) {
861 $url .= "#{$items['fragment']}";
862 }
863
864 return $url;
865 }
866
867 /**
868 * Function to return the latest civicrm version.
869 *
870 * @return string civicrm version
871 * @access public
872 */
873 static function version() {
874 static $version;
875
876 if (!$version) {
877 $verFile = implode(DIRECTORY_SEPARATOR,
878 array(dirname(__FILE__), '..', '..', 'civicrm-version.php')
879 );
880 if (file_exists($verFile)) {
881 require_once ($verFile);
882 if (function_exists('civicrmVersion')) {
883 $info = civicrmVersion();
884 $version = $info['version'];
885 }
886 }
887 else {
888 // svn installs don't have version.txt by default. In that case version.xml should help -
889 $verFile = implode(DIRECTORY_SEPARATOR,
890 array(dirname(__FILE__), '..', '..', 'xml', 'version.xml')
891 );
892 if (file_exists($verFile)) {
893 $str = file_get_contents($verFile);
894 $xmlObj = simplexml_load_string($str);
895 $version = (string) $xmlObj->version_no;
896 }
897 }
898
899 // pattern check
900 if (!CRM_Utils_System::isVersionFormatValid($version)) {
901 CRM_Core_Error::fatal('Unknown codebase version.');
902 }
903 }
904
905 return $version;
906 }
907
908 static function isVersionFormatValid($version) {
909 return preg_match("/^(\d{1,2}\.){2,3}(\d{1,2}|(alpha|beta)\d{1,2})(\.upgrade)?$/", $version);
910 }
911
912 static function getAllHeaders() {
913 if (function_exists('getallheaders')) {
914 return getallheaders();
915 }
916
917 // emulate get all headers
918 // http://www.php.net/manual/en/function.getallheaders.php#66335
919 $headers = array();
920 foreach ($_SERVER as $name => $value) {
921 if (substr($name, 0, 5) == 'HTTP_') {
922 $headers[str_replace(' ',
923 '-',
924 ucwords(strtolower(str_replace('_',
925 ' ',
926 substr($name, 5)
927 )
928 ))
929 )] = $value;
930 }
931 }
932 return $headers;
933 }
934
935 static function getRequestHeaders() {
936 if (function_exists('apache_request_headers')) {
937 return apache_request_headers();
938 }
939 else {
940 return $_SERVER;
941 }
942 }
943
944 /**
945 * Check and determine is this is an SSL request
946 * Note that we inline this function in install/civicrm.php, so if
947 * you change this function, please go and change the code in the install script
948 */
949 static function isSSL( ) {
950 return
951 (isset($_SERVER['HTTPS']) &&
952 !empty($_SERVER['HTTPS']) &&
953 strtolower($_SERVER['HTTPS']) != 'off') ? true : false;
954 }
955
956 static function redirectToSSL($abort = FALSE) {
957 $config = CRM_Core_Config::singleton();
958 $req_headers = self::getRequestHeaders();
959 if (CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'enableSSL') &&
960 !self::isSSL() &&
961 strtolower(CRM_Utils_Array::value('X_FORWARDED_PROTO', $req_headers)) != 'https'
962 ) {
963 // ensure that SSL is enabled on a civicrm url (for cookie reasons etc)
964 $url = "https://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
965 if (!self::checkURL($url, TRUE)) {
966 if ($abort) {
967 CRM_Core_Error::fatal('HTTPS is not set up on this machine');
968 }
969 else {
970 CRM_Core_Session::setStatus(ts('HTTPS is not set up on this machine'), ts('Warning'), 'alert');
971 // admin should be the only one following this
972 // since we dont want the user stuck in a bad place
973 return;
974 }
975 }
976 CRM_Utils_System::redirect($url);
977 }
978 }
979
980 /*
981 * Get logged in user's IP address.
982 *
983 * Get IP address from HTTP Header. If the CMS is Drupal then use the Drupal function
984 * as this also handles reverse proxies (based on proper configuration in settings.php)
985 *
986 * @return string ip address of logged in user
987 */
988
989 static function ipAddress() {
990 $address = CRM_Utils_Array::value('REMOTE_ADDR', $_SERVER);
991
992 $config = CRM_Core_Config::singleton();
993 if ($config->userSystem->is_drupal) {
994 //drupal function handles the server being behind a proxy securely
995 return ip_address();
996 }
997
998 // hack for safari
999 if ($address == '::1') {
1000 $address = '127.0.0.1';
1001 }
1002
1003 return $address;
1004 }
1005
1006 /**
1007 * Returns you the referring / previous page url
1008 *
1009 * @return string the previous page url
1010 * @access public
1011 */
1012 static function refererPath() {
1013 return CRM_Utils_Array::value('HTTP_REFERER', $_SERVER);
1014 }
1015
1016 /**
1017 * Returns default documentation URL base
1018 *
1019 * @return string documentation url
1020 * @access public
1021 */
1022 static function getDocBaseURL() {
1023 // FIXME: move this to configuration at some stage
1024 return 'http://book.civicrm.org/';
1025 }
1026
1027 /**
1028 * Returns wiki (alternate) documentation URL base
1029 *
1030 * @return string documentation url
1031 * @access public
1032 */
1033 static function getWikiBaseURL() {
1034 // FIXME: move this to configuration at some stage
1035 return 'http://wiki.civicrm.org/confluence/display/CRMDOC/';
1036 }
1037
1038 /**
1039 * Returns URL or link to documentation page, based on provided parameters.
1040 * For use in PHP code.
1041 * WARNING: Always returns URL, if ts function is not defined ($URLonly has no effect).
1042 *
1043 * @param string $page Title of documentation wiki page
1044 * @param boolean $URLonly Whether function should return URL only or whole link (default)
1045 * @param string $text Text of HTML link (no effect if $URLonly = false)
1046 * @param string $title Tooltip text for HTML link (no effect if $URLonly = false)
1047 * @param string $style Style attribute value for HTML link (no effect if $URLonly = false)
1048 *
1049 * @return string URL or link to documentation page, based on provided parameters
1050 * @access public
1051 */
1052 static function docURL2($page, $URLonly = FALSE, $text = NULL, $title = NULL, $style = NULL, $resource = NULL) {
1053 // if ts function doesn't exist, it means that CiviCRM hasn't been fully initialised yet -
1054 // return just the URL, no matter what other parameters are defined
1055 if (!function_exists('ts')) {
1056 if ($resource == 'wiki') {
1057 $docBaseURL = self::getWikiBaseURL();
1058 } else {
1059 $docBaseURL = self::getDocBaseURL();
1060 }
1061 return $docBaseURL . str_replace(' ', '+', $page);
1062 }
1063 else {
1064 $params = array(
1065 'page' => $page,
1066 'URLonly' => $URLonly,
1067 'text' => $text,
1068 'title' => $title,
1069 'style' => $style,
1070 'resource' => $resource,
1071 );
1072 return self::docURL($params);
1073 }
1074 }
1075
1076 /**
1077 * Returns URL or link to documentation page, based on provided parameters.
1078 * For use in templates code.
1079 *
1080 * @param array $params An array of parameters (see CRM_Utils_System::docURL2 method for names)
1081 *
1082 * @return string URL or link to documentation page, based on provided parameters
1083 * @access public
1084 */
1085 static function docURL($params) {
1086
1087 if (!isset($params['page'])) {
1088 return;
1089 }
1090
1091 if (CRM_Utils_Array::value('resource', $params) == 'wiki') {
1092 $docBaseURL = self::getWikiBaseURL();
1093 } else {
1094 $docBaseURL = self::getDocBaseURL();
1095 }
1096
1097 if (!isset($params['title']) or $params['title'] === NULL) {
1098 $params['title'] = ts('Opens documentation in a new window.');
1099 }
1100
1101 if (!isset($params['text']) or $params['text'] === NULL) {
1102 $params['text'] = ts('(learn more...)');
1103 }
1104
1105 if (!isset($params['style']) || $params['style'] === NULL) {
1106 $style = '';
1107 }
1108 else {
1109 $style = "style=\"{$params['style']}\"";
1110 }
1111
1112 $link = $docBaseURL . str_replace(' ', '+', $params['page']);
1113
1114 if (isset($params['URLonly']) && $params['URLonly'] == TRUE) {
1115 return $link;
1116 }
1117 else {
1118 return "<a href=\"{$link}\" $style target=\"_blank\" title=\"{$params['title']}\">{$params['text']}</a>";
1119 }
1120 }
1121
1122 /**
1123 * Get the locale set in the hosting CMS
1124 *
1125 * @return string the used locale or null for none
1126 */
1127 static function getUFLocale() {
1128 $config = CRM_Core_Config::singleton();
1129 return $config->userSystem->getUFLocale();
1130 }
1131
1132 /**
1133 * Execute external or internal urls and return server response
1134 *
1135 * @param string $url request url
1136 * @param boolean $addCookie should be true to access internal urls
1137 *
1138 * @return string $response response from url
1139 * @static
1140 */
1141 static function getServerResponse($url, $addCookie = TRUE) {
1142 CRM_Core_Error::ignoreException();
1143 require_once 'HTTP/Request.php';
1144 $request = new HTTP_Request($url);
1145
1146 if ($addCookie) {
1147 foreach ($_COOKIE as $name => $value) {
1148 $request->addCookie($name, $value);
1149 }
1150 }
1151
1152 if (isset($_SERVER['AUTH_TYPE'])) {
1153 $request->setBasicAuth($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']);
1154 }
1155
1156 $config = CRM_Core_Config::singleton();
1157 if ($config->userFramework == 'WordPress') {
1158 session_write_close();
1159 }
1160
1161 $request->sendRequest();
1162 $response = $request->getResponseBody();
1163
1164 CRM_Core_Error::setCallback();
1165 return $response;
1166 }
1167
1168 static function isDBVersionValid(&$errorMessage) {
1169 $dbVersion = CRM_Core_BAO_Domain::version();
1170
1171 if (!$dbVersion) {
1172 // if db.ver missing
1173 $errorMessage = ts('Version information found to be missing in database. You will need to determine the correct version corresponding to your current database state.');
1174 return FALSE;
1175 }
1176 elseif (!CRM_Utils_System::isVersionFormatValid($dbVersion)) {
1177 $errorMessage = ts('Database is marked with invalid version format. You may want to investigate this before you proceed further.');
1178 return FALSE;
1179 }
1180 elseif (stripos($dbVersion, 'upgrade')) {
1181 // if db.ver indicates a partially upgraded db
1182 $upgradeUrl = CRM_Utils_System::url("civicrm/upgrade", "reset=1");
1183 $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));
1184 return FALSE;
1185 }
1186 else {
1187 $codeVersion = CRM_Utils_System::version();
1188
1189 // if db.ver < code.ver, time to upgrade
1190 if (version_compare($dbVersion, $codeVersion) < 0) {
1191 $upgradeUrl = CRM_Utils_System::url("civicrm/upgrade", "reset=1");
1192 $errorMessage = ts('New codebase version detected. You might want to visit <a href=\'%1\'>upgrade screen</a> to upgrade the database.', array(1 => $upgradeUrl));
1193 return FALSE;
1194 }
1195
1196 // if db.ver > code.ver, sth really wrong
1197 if (version_compare($dbVersion, $codeVersion) > 0) {
1198 $errorMessage = 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.',
1199 array(1 => $dbVersion, 2 => $codeVersion)
1200 );
1201 $errorMessage .= "<p>" . ts('OR if this is an svn install, you might want to fix civicrm-version.php file.') . "</p>";
1202 return FALSE;
1203 }
1204 }
1205 // FIXME: there should be another check to make sure version is in valid format - X.Y.alpha_num
1206
1207 return TRUE;
1208 }
1209
1210 static function civiExit($status = 0) {
1211 // move things to CiviCRM cache as needed
1212 CRM_Core_Session::storeSessionObjects();
1213
1214 exit($status);
1215 }
1216
1217 /**
1218 * Reset the various system caches and some important
1219 * static variables
1220 */
1221 static function flushCache( ) {
1222 // flush out all cache entries so we can reload new data
1223 // a bit aggressive, but livable for now
1224 $cache = CRM_Utils_Cache::singleton();
1225 $cache->flush();
1226
1227 // also reset the various static memory caches
1228
1229 // reset the memory or array cache
1230 CRM_Core_BAO_Cache::deleteGroup('contact fields', NULL, FALSE);
1231
1232 // reset ACL cache
1233 CRM_ACL_BAO_Cache::resetCache();
1234
1235 // reset various static arrays used here
1236 CRM_Contact_BAO_Contact::$_importableFields =
1237 CRM_Contact_BAO_Contact::$_exportableFields =
1238 CRM_Contribute_BAO_Contribution::$_importableFields =
1239 CRM_Contribute_BAO_Contribution::$_exportableFields =
1240 CRM_Pledge_BAO_Pledge::$_exportableFields =
1241 CRM_Contribute_BAO_Query::$_contributionFields =
1242 CRM_Core_BAO_CustomField::$_importFields =
1243 CRM_Core_DAO::$_dbColumnValueCache = NULL;
1244
1245 CRM_Core_OptionGroup::flushAll();
1246 CRM_Utils_PseudoConstant::flushAll();
1247 }
1248
1249 /**
1250 * load cms bootstrap
1251 *
1252 * @param $params array with uid name and pass
1253 * @param $loadUser boolean load user or not
1254 */
1255 static function loadBootStrap($params = array(
1256 ), $loadUser = TRUE, $throwError = TRUE, $realPath = NULL) {
1257 if (!is_array($params)) {
1258 $params = array();
1259 }
1260 $config = CRM_Core_Config::singleton();
1261 return $config->userSystem->loadBootStrap($params, $loadUser, $throwError, $realPath);
1262 }
1263
1264 /**
1265 * check is user logged in.
1266 *
1267 * @return boolean.
1268 */
1269 public static function isUserLoggedIn() {
1270 $config = CRM_Core_Config::singleton();
1271 return $config->userSystem->isUserLoggedIn();
1272 }
1273
1274 /**
1275 * Get current logged in user id.
1276 *
1277 * @return int ufId, currently logged in user uf id.
1278 */
1279 public static function getLoggedInUfID() {
1280 $config = CRM_Core_Config::singleton();
1281 return $config->userSystem->getLoggedInUfID();
1282 }
1283
1284 static function baseCMSURL() {
1285 static $_baseURL = NULL;
1286 if (!$_baseURL) {
1287 $config = CRM_Core_Config::singleton();
1288 $_baseURL = $userFrameworkBaseURL = $config->userFrameworkBaseURL;
1289
1290 if ($config->userFramework == 'Joomla') {
1291 // gross hack
1292 // we need to remove the administrator/ from the end
1293 $_baseURL = str_replace("/administrator/", "/", $userFrameworkBaseURL);
1294 }
1295 else {
1296 // Drupal setting
1297 global $civicrm_root;
1298 if (strpos($civicrm_root,
1299 DIRECTORY_SEPARATOR . 'sites' .
1300 DIRECTORY_SEPARATOR . 'all' .
1301 DIRECTORY_SEPARATOR . 'modules'
1302 ) === FALSE) {
1303 $startPos = strpos($civicrm_root,
1304 DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR
1305 );
1306 $endPos = strpos($civicrm_root,
1307 DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR
1308 );
1309 if ($startPos && $endPos) {
1310 // if component is in sites/SITENAME/modules
1311 $siteName = substr($civicrm_root,
1312 $startPos + 7,
1313 $endPos - $startPos - 7
1314 );
1315
1316 $_baseURL = $userFrameworkBaseURL . "sites/$siteName/";
1317 }
1318 }
1319 }
1320 }
1321 return $_baseURL;
1322 }
1323
1324 static function relativeURL($url) {
1325 // check if url is relative, if so return immediately
1326 if (substr($url, 0, 4) != 'http') {
1327 return $url;
1328 }
1329
1330 // make everything relative from the baseFilePath
1331 $baseURL = self::baseCMSURL();
1332
1333 // check if baseURL is a substr of $url, if so
1334 // return rest of string
1335 if (substr($url, 0, strlen($baseURL)) == $baseURL) {
1336 return substr($url, strlen($baseURL));
1337 }
1338
1339 // return the original value
1340 return $url;
1341 }
1342
1343 static function absoluteURL($url, $removeLanguagePart = FALSE) {
1344 // check if url is already absolute, if so return immediately
1345 if (substr($url, 0, 4) == 'http') {
1346 return $url;
1347 }
1348
1349 // make everything absolute from the baseFileURL
1350 $baseURL = self::baseCMSURL();
1351
1352 //CRM-7622: drop the language from the URL if requested (and it’s there)
1353 $config = CRM_Core_Config::singleton();
1354 if ($removeLanguagePart) {
1355 $baseURL = self::languageNegotiationURL($baseURL, FALSE, TRUE);
1356 }
1357
1358 return $baseURL . $url;
1359 }
1360
1361 /**
1362 * Function to clean url, replaces first '&' with '?'
1363 *
1364 * @param string $url
1365 *
1366 * @return string $url, clean url
1367 * @static
1368 */
1369 static function cleanUrl($url) {
1370 if (!$url) {
1371 return NULL;
1372 }
1373
1374 if ($pos = strpos($url, '&')) {
1375 $url = substr_replace($url, '?', $pos, 1);
1376 }
1377
1378 return $url;
1379 }
1380
1381 /**
1382 * Format the url as per language Negotiation.
1383 *
1384 * @param string $url
1385 *
1386 * @return string $url, formatted url.
1387 * @static
1388 */
1389 static function languageNegotiationURL($url,
1390 $addLanguagePart = TRUE,
1391 $removeLanguagePart = FALSE
1392 ) {
1393 $config = &CRM_Core_Config::singleton();
1394 return $config->userSystem->languageNegotiationURL($url, $addLanguagePart, $removeLanguagePart);
1395 }
1396
1397 /**
1398 * Append the contents of an 'extra' smarty template file if it is present in
1399 * the custom template directory. This does not work if there are
1400 * multiple custom template directories
1401 *
1402 * @param string $fileName - the name of the tpl file that we are processing
1403 * @param string $content (by reference) - the current content string
1404 * @param string $overideFileName - an optional parameter which is sent by contribution/event reg/profile pages
1405 * which uses a id specific extra file name if present
1406 *
1407 * @return void - the content string is modified if needed
1408 * @static
1409 */
1410 static function appendTPLFile($fileName,
1411 &$content,
1412 $overideFileName = NULL
1413 ) {
1414 $template = CRM_Core_Smarty::singleton();
1415 if ($overideFileName) {
1416 $additionalTPLFile = $overideFileName;
1417 }
1418 else {
1419 $additionalTPLFile = str_replace('.tpl', '.extra.tpl', $fileName);
1420 }
1421
1422 if ($template->template_exists($additionalTPLFile)) {
1423 $content .= $template->fetch($additionalTPLFile);
1424 }
1425 }
1426
1427 /**
1428 * Get a list of all files that are found within the directories
1429 * that are the result of appending the provided relative path to
1430 * each component of the PHP include path.
1431 *
1432 * @author Ken Zalewski
1433 *
1434 * @param string $relpath a relative path, typically pointing to
1435 * a directory with multiple class files
1436 *
1437 * @return array An array of files that exist in one or more of the
1438 * directories that are referenced by the relative path
1439 * when appended to each element of the PHP include path
1440 * @access public
1441 */
1442 static function listIncludeFiles($relpath) {
1443 $file_list = array();
1444 $inc_dirs = explode(PATH_SEPARATOR, get_include_path());
1445 foreach ($inc_dirs as $inc_dir) {
1446 $target_dir = $inc_dir . DIRECTORY_SEPARATOR . $relpath;
1447 if (is_dir($target_dir)) {
1448 $cur_list = scandir($target_dir);
1449 foreach ($cur_list as $fname) {
1450 if ($fname != '.' && $fname != '..') {
1451 $file_list[$fname] = $fname;
1452 }
1453 }
1454 }
1455 }
1456 return $file_list;
1457 }
1458 // listIncludeFiles()
1459
1460 /**
1461 * Get a list of all "plugins" (PHP classes that implement a piece of
1462 * functionality using a well-defined interface) that are found in a
1463 * particular CiviCRM directory (both custom and core are searched).
1464 *
1465 * @author Ken Zalewski
1466 *
1467 * @param string $relpath a relative path referencing a directory that
1468 * contains one or more plugins
1469 * @param string $fext only files with this extension will be considered
1470 * to be plugins
1471 * @param array $skipList list of files to skip
1472 *
1473 * @return array List of plugins, where the plugin name is both the
1474 * key and the value of each element.
1475 * @access public
1476 */
1477 static function getPluginList($relpath, $fext = '.php', $skipList = array(
1478 )) {
1479 $fext_len = strlen($fext);
1480 $plugins = array();
1481 $inc_files = CRM_Utils_System::listIncludeFiles($relpath);
1482 foreach ($inc_files as $inc_file) {
1483 if (substr($inc_file, 0 - $fext_len) == $fext) {
1484 $plugin_name = substr($inc_file, 0, 0 - $fext_len);
1485 if (!in_array($plugin_name, $skipList)) {
1486 $plugins[$plugin_name] = $plugin_name;
1487 }
1488 }
1489 }
1490 return $plugins;
1491 }
1492 // getPluginList()
1493
1494 /**
1495 *
1496 * @param string $fileName - the name of the tpl file that we are processing
1497 * @param string $content (by reference) - the current content string
1498 *
1499 * @return void - the content string is modified if needed
1500 * @static
1501 */
1502 static function executeScheduledJobs() {
1503 $facility = new CRM_Core_JobManager();
1504 $facility->execute(FALSE);
1505
1506 $redirectUrl = self::url('civicrm/admin/job', 'reset=1');
1507
1508 CRM_Core_Session::setStatus(
1509 ts('Scheduled jobs have been executed according to individual timing settings. Please check log for messages.'),
1510 ts('Complete'), 'success');
1511
1512 CRM_Utils_System::redirect($redirectUrl);
1513 }
1514
1515 /**
1516 * Determine whether this is a developmental system.
1517 *
1518 * @return bool
1519 */
1520 static function isDevelopment() {
1521 static $cache = NULL;
1522 if ($cache === NULL) {
1523 global $civicrm_root;
1524 $cache = file_exists("{$civicrm_root}/.svn") || file_exists("{$civicrm_root}/.git");
1525 }
1526 return $cache;
1527 }
1528 }
1529