4 * Note that this installer has been based of the SilverStripe installer.
5 * You can get more information from the SilverStripe Website at
6 * http://www.silverstripe.com/. Please check
7 * http://www.silverstripe.com/licensing for licensing details.
9 * Copyright (c) 2006-7, SilverStripe Limited - www.silverstripe.com
10 * All rights reserved.
12 * Changes and modifications (c) 2007-2015 by CiviCRM LLC
20 ini_set('max_execution_time', 3000);
22 if (stristr(PHP_OS
, 'WIN')) {
23 define('CIVICRM_DIRECTORY_SEPARATOR', '/');
24 define('CIVICRM_WINDOWS', 1);
27 define('CIVICRM_DIRECTORY_SEPARATOR', DIRECTORY_SEPARATOR
);
28 define('CIVICRM_WINDOWS', 0);
31 // set installation type - drupal
36 // unset civicrm session if any
37 if (array_key_exists('CiviCRM', $_SESSION)) {
38 unset($_SESSION['CiviCRM']);
41 if (isset($_GET['civicrm_install_type'])) {
42 $_SESSION['civicrm_install_type'] = $_GET['civicrm_install_type'];
45 if (!isset($_SESSION['civicrm_install_type'])) {
46 $_SESSION['civicrm_install_type'] = "drupal";
53 global $installDirPath;
54 global $installURLPath;
56 $installType = strtolower($_SESSION['civicrm_install_type']);
58 if ($installType == 'drupal') {
59 $crmPath = dirname(dirname($_SERVER['SCRIPT_FILENAME']));
60 $installDirPath = $installURLPath = '';
62 elseif ($installType == 'wordpress') {
63 $crmPath = WP_PLUGIN_DIR
. DIRECTORY_SEPARATOR
. 'civicrm' . DIRECTORY_SEPARATOR
. 'civicrm' . DIRECTORY_SEPARATOR
;
64 $installDirPath = WP_PLUGIN_DIR
. DIRECTORY_SEPARATOR
. 'civicrm' . DIRECTORY_SEPARATOR
. 'civicrm' . DIRECTORY_SEPARATOR
. 'install' . DIRECTORY_SEPARATOR
;
65 $installURLPath = WP_PLUGIN_URL
. DIRECTORY_SEPARATOR
. 'civicrm' . DIRECTORY_SEPARATOR
. 'civicrm' . DIRECTORY_SEPARATOR
. 'install' . DIRECTORY_SEPARATOR
;
68 $errorTitle = "Oops! Unsupported installation mode";
69 $errorMsg = sprintf('%s: unknown installation mode. Please refer to the online documentation for more information.', $installType);
70 errorDisplayPage($errorTitle, $errorMsg, FALSE);
73 $pkgPath = $crmPath . DIRECTORY_SEPARATOR
. 'packages';
75 require_once $crmPath . '/CRM/Core/ClassLoader.php';
76 CRM_Core_ClassLoader
::singleton()->register();
78 // Load civicrm database config
79 if (isset($_REQUEST['mysql'])) {
80 $databaseConfig = $_REQUEST['mysql'];
83 $databaseConfig = array(
84 "server" => "localhost",
85 "username" => "civicrm",
87 "database" => "civicrm",
91 if ($installType == 'drupal') {
92 // Load drupal database config
93 if (isset($_REQUEST['drupal'])) {
94 $drupalConfig = $_REQUEST['drupal'];
97 $drupalConfig = array(
98 "server" => "localhost",
99 "username" => "drupal",
101 "database" => "drupal",
107 if (isset($_REQUEST['loadGenerated'])) {
111 require_once dirname(__FILE__
) . CIVICRM_DIRECTORY_SEPARATOR
. 'langs.php';
112 foreach ($langs as $locale => $_) {
113 if ($locale == 'en_US') {
116 if (!file_exists(implode(CIVICRM_DIRECTORY_SEPARATOR
, array($crmPath, 'sql', "civicrm_data.$locale.mysql")))) {
117 unset($langs[$locale]);
121 // Set the locale (required by CRM_Core_Config)
122 // This is mostly sympbolic, since nothing we do during the install
123 // really requires CIVICRM_UF to be defined.
124 $installTypeToUF = array(
125 'wordpress' => 'WordPress',
126 'drupal' => 'Drupal',
129 $uf = (isset($installTypeToUF[$installType]) ?
$installTypeToUF[$installType] : 'Drupal');
130 define('CIVICRM_UF', $uf);
135 $seedLanguage = 'en_US';
137 if (isset($_REQUEST['seedLanguage']) and isset($langs[$_REQUEST['seedLanguage']])) {
138 $seedLanguage = $_REQUEST['seedLanguage'];
139 $tsLocale = $_REQUEST['seedLanguage'];
142 $config = CRM_Core_Config
::singleton(FALSE);
143 $GLOBALS['civicrm_default_error_scope'] = NULL;
145 // The translation files are in the parent directory (l10n)
146 $config->gettextResourceDir
= '..' . DIRECTORY_SEPARATOR
. $config->gettextResourceDir
;
147 $i18n = CRM_Core_I18n
::singleton();
150 if ($installType == 'drupal') {
151 //CRM-6840 -don't force to install in sites/all/modules/
152 $object = new CRM_Utils_System_Drupal();
153 $cmsPath = $object->cmsRootPath();
155 $siteDir = getSiteDir($cmsPath, $_SERVER['SCRIPT_FILENAME']);
156 $alreadyInstalled = file_exists($cmsPath . CIVICRM_DIRECTORY_SEPARATOR
.
157 'sites' . CIVICRM_DIRECTORY_SEPARATOR
.
158 $siteDir . CIVICRM_DIRECTORY_SEPARATOR
.
159 'civicrm.settings.php'
162 elseif ($installType == 'wordpress') {
163 $cmsPath = WP_PLUGIN_DIR
. DIRECTORY_SEPARATOR
. 'civicrm';
164 $alreadyInstalled = file_exists($cmsPath . CIVICRM_DIRECTORY_SEPARATOR
.
165 'civicrm.settings.php'
169 if ($installType == 'drupal') {
170 // Lets check only /modules/.
171 $pattern = '/' . preg_quote(CIVICRM_DIRECTORY_SEPARATOR
. 'modules', CIVICRM_DIRECTORY_SEPARATOR
) . '/';
173 if (!preg_match($pattern, str_replace("\\", "/", $_SERVER['SCRIPT_FILENAME']))) {
174 $directory = implode(CIVICRM_DIRECTORY_SEPARATOR
, array('sites', 'all', 'modules'));
175 $errorTitle = ts("Oops! Please correct your install location");
176 $errorMsg = ts("Please untar (uncompress) your downloaded copy of CiviCRM in the <strong>%1</strong> directory below your Drupal root directory.", array(1 => $directory));
177 errorDisplayPage($errorTitle, $errorMsg);
181 // Exit with error if CiviCRM has already been installed.
182 if ($alreadyInstalled) {
183 $errorTitle = ts("Oops! CiviCRM is already installed");
184 $settings_directory = $cmsPath;
186 if ($installType == 'drupal') {
187 $settings_directory = implode(CIVICRM_DIRECTORY_SEPARATOR
, array(
188 ts('[your Drupal root directory]'),
194 $docLink = CRM_Utils_System
::docURL2('Installation and Upgrades', FALSE, ts('Installation Guide'), NULL, NULL, "wiki");
195 $errorMsg = ts("CiviCRM has already been installed. <ul><li>To <strong>start over</strong>, you must delete or rename the existing CiviCRM settings file - <strong>civicrm.settings.php</strong> - from <strong>%1</strong>.</li><li>To <strong>upgrade an existing installation</strong>, <a href='%2'>refer to the online documentation</a>.</li></ul>", array(1 => $settings_directory, 2 => $docLink));
196 errorDisplayPage($errorTitle, $errorMsg, FALSE);
199 $versionFile = $crmPath . CIVICRM_DIRECTORY_SEPARATOR
. 'civicrm-version.php';
200 if (file_exists($versionFile)) {
201 require_once $versionFile;
202 $civicrm_version = civicrmVersion();
205 $civicrm_version = 'unknown';
208 if ($installType == 'drupal') {
209 // Ensure that they have downloaded the correct version of CiviCRM
210 if ($civicrm_version['cms'] != 'Drupal' && $civicrm_version['cms'] != 'Drupal6') {
211 $errorTitle = ts("Oops! Incorrect CiviCRM version");
212 $errorMsg = ts("This installer can only be used for the Drupal version of CiviCRM.");
213 errorDisplayPage($errorTitle, $errorMsg);
216 define('DRUPAL_ROOT', $cmsPath);
217 $drupalVersionFiles = array(
219 implode(CIVICRM_DIRECTORY_SEPARATOR
, array($cmsPath, 'modules', 'system', 'system.module')),
221 implode(CIVICRM_DIRECTORY_SEPARATOR
, array($cmsPath, 'includes', 'bootstrap.inc')),
223 foreach ($drupalVersionFiles as $drupalVersionFile) {
224 if (file_exists($drupalVersionFile)) {
225 require_once $drupalVersionFile;
229 if (!defined('VERSION') or version_compare(VERSION
, '6.0') < 0) {
230 $errorTitle = ts("Oops! Incorrect Drupal version");
231 $errorMsg = ts("This version of CiviCRM can only be used with Drupal 6.x or 7.x. Please ensure that '%1' exists if you are running Drupal 7.0 and over.", array(1 => implode("' or '", $drupalVersionFiles)));
232 errorDisplayPage($errorTitle, $errorMsg);
235 elseif ($installType == 'wordpress') {
237 $civicrm_version['cms'] = 'WordPress';
239 // Ensure that they have downloaded the correct version of CiviCRM
240 if ($civicrm_version['cms'] != 'WordPress') {
241 $errorTitle = ts("Oops! Incorrect CiviCRM version");
242 $errorMsg = ts("This installer can only be used for the WordPress version of CiviCRM.");
243 errorDisplayPage($errorTitle, $errorMsg);
247 // Check requirements
248 $req = new InstallRequirements();
251 if ($req->hasErrors()) {
252 $hasErrorOtherThanDatabase = TRUE;
255 if ($databaseConfig) {
256 $dbReq = new InstallRequirements();
257 $dbReq->checkdatabase($databaseConfig, 'CiviCRM');
258 if ($installType == 'drupal') {
259 $dbReq->checkdatabase($drupalConfig, 'Drupal');
264 if (isset($_REQUEST['go']) && !$req->hasErrors() && !$dbReq->hasErrors()) {
265 // Confirm before reinstalling
266 if (!isset($_REQUEST['force_reinstall']) && $alreadyInstalled) {
267 include $installDirPath . 'template.html';
270 $inst = new Installer();
271 $inst->install($_REQUEST);
274 // Show the config form
277 include $installDirPath . 'template.html';
281 * This class checks requirements
282 * Each of the requireXXX functions takes an argument which gives a user description of the test. It's an array
284 * $description[0] - The test category
285 * $description[1] - The test title
286 * $description[2] - The test error to show, if it goes wrong
288 class InstallRequirements
{
289 var $errors, $warnings, $tests;
291 // @see CRM_Upgrade_Form::MINIMUM_THREAD_STACK
292 const MINIMUM_THREAD_STACK
= 192;
295 * Just check that the database configuration is okay.
296 * @param $databaseConfig
299 public function checkdatabase($databaseConfig, $dbName) {
300 if ($this->requireFunction('mysql_connect',
302 ts("PHP Configuration"),
304 ts("MySQL support not included in PHP."),
308 $this->requireMySQLServer($databaseConfig['server'],
310 ts("MySQL %1 Configuration", array(1 => $dbName)),
311 ts("Does the server exist?"),
312 ts("Can't find the a MySQL server on '%1'.", array(1 => $databaseConfig['server'])),
313 $databaseConfig['server'],
316 if ($this->requireMysqlConnection($databaseConfig['server'],
317 $databaseConfig['username'],
318 $databaseConfig['password'],
320 ts("MySQL %1 Configuration", array(1 => $dbName)),
321 ts("Are the access credentials correct?"),
322 ts("That username/password doesn't work"),
326 @$this->requireMySQLVersion("5.1",
328 ts("MySQL %1 Configuration", array(1 => $dbName)),
329 ts("MySQL version at least %1", array(1 => '5.1')),
330 ts("MySQL version %1 or higher is required, you are running MySQL %2.", array(1 => '5.1', 2 => mysql_get_server_info())),
331 ts("MySQL %1", array(1 => mysql_get_server_info())),
334 $this->requireMySQLAutoIncrementIncrementOne($databaseConfig['server'],
335 $databaseConfig['username'],
336 $databaseConfig['password'],
338 ts("MySQL %1 Configuration", array(1 => $dbName)),
339 ts("Is auto_increment_increment set to 1"),
340 ts("An auto_increment_increment value greater than 1 is not currently supported. Please see issue CRM-7923 for further details and potential workaround."),
343 $this->requireMySQLThreadStack($databaseConfig['server'],
344 $databaseConfig['username'],
345 $databaseConfig['password'],
346 $databaseConfig['database'],
347 self
::MINIMUM_THREAD_STACK
,
349 ts("MySQL %1 Configuration", array(1 => $dbName)),
350 ts("Does MySQL thread_stack meet minimum (%1k)", array(1 => self
::MINIMUM_THREAD_STACK
)),
352 // "The MySQL thread_stack does not meet minimum " . CRM_Upgrade_Form::MINIMUM_THREAD_STACK . "k. Please update thread_stack in my.cnf.",
356 $onlyRequire = ($dbName == 'Drupal') ?
TRUE : FALSE;
357 $this->requireDatabaseOrCreatePermissions(
358 $databaseConfig['server'],
359 $databaseConfig['username'],
360 $databaseConfig['password'],
361 $databaseConfig['database'],
363 ts("MySQL %1 Configuration", array(1 => $dbName)),
364 ts("Can I access/create the database?"),
365 ts("I can't create new databases and the database '%1' doesn't exist.", array(1 => $databaseConfig['database'])),
369 if ($dbName != 'Drupal') {
370 $this->requireMySQLInnoDB($databaseConfig['server'],
371 $databaseConfig['username'],
372 $databaseConfig['password'],
373 $databaseConfig['database'],
375 ts("MySQL %1 Configuration", array(1 => $dbName)),
376 ts("Can I access/create InnoDB tables in the database?"),
377 ts("Unable to create InnoDB tables. MySQL InnoDB support is required for CiviCRM but is either not available or not enabled in this MySQL database server."),
380 $this->requireMySQLTempTables($databaseConfig['server'],
381 $databaseConfig['username'],
382 $databaseConfig['password'],
383 $databaseConfig['database'],
385 ts("MySQL %1 Configuration", array(1 => $dbName)),
386 ts('Can I create temporary tables in the database?'),
387 ts('Unable to create temporary tables. This MySQL user is missing the CREATE TEMPORARY TABLES privilege.'),
390 $this->requireMySQLLockTables($databaseConfig['server'],
391 $databaseConfig['username'],
392 $databaseConfig['password'],
393 $databaseConfig['database'],
395 ts("MySQL %1 Configuration", array(1 => $dbName)),
396 ts('Can I create lock tables in the database?'),
397 ts('Unable to lock tables. This MySQL user is missing the LOCK TABLES privilege.'),
400 $this->requireMySQLTrigger($databaseConfig['server'],
401 $databaseConfig['username'],
402 $databaseConfig['password'],
403 $databaseConfig['database'],
405 ts("MySQL %1 Configuration", array(1 => $dbName)),
406 ts('Can I create triggers in the database?'),
407 ts('Unable to create triggers. This MySQL user is missing the CREATE TRIGGERS privilege.'),
415 * Check everything except the database.
417 public function check() {
418 global $crmPath, $installType;
420 $this->errors
= NULL;
422 $this->requirePHPVersion('5.3.3', array(
423 ts("PHP Configuration"),
424 ts("PHP5 installed"),
426 ts("PHP version %1", array(1 => phpversion())),
429 // Check that we can identify the root folder successfully
430 $this->requireFile($crmPath . CIVICRM_DIRECTORY_SEPARATOR
. 'README.txt',
432 ts("File permissions"),
433 ts("Does the webserver know where files are stored?"),
434 ts("The webserver isn't letting me identify where files are stored."),
440 // CRM-6485: make sure the path does not contain PATH_SEPARATOR, as we don’t know how to escape it
441 $this->requireNoPathSeparator(
443 ts("File permissions"),
444 ts('Does the CiviCRM path contain PATH_SEPARATOR?'),
445 ts('The path %1 contains PATH_SEPARATOR (the %2 character).', array(1 => $this->getBaseDir(), 2 => PATH_SEPARATOR
)),
450 $requiredDirectories = array('CRM', 'packages', 'templates', 'js', 'api', 'i', 'sql');
451 foreach ($requiredDirectories as $dir) {
452 $this->requireFile($crmPath . CIVICRM_DIRECTORY_SEPARATOR
. $dir,
454 ts("File permissions"),
455 ts("Folder '%1' exists?", array(1 => $dir)),
456 ts("There is no '%1' folder.", array(1 => $dir)),
461 $configIDSiniDir = NULL;
463 $siteDir = getSiteDir($cmsPath, $_SERVER['SCRIPT_FILENAME']);
464 if ($installType == 'drupal') {
466 // make sure that we can write to sites/default and files/
467 $writableDirectories = array(
468 $cmsPath . CIVICRM_DIRECTORY_SEPARATOR
.
469 'sites' . CIVICRM_DIRECTORY_SEPARATOR
.
470 $siteDir . CIVICRM_DIRECTORY_SEPARATOR
.
472 $cmsPath . CIVICRM_DIRECTORY_SEPARATOR
.
473 'sites' . CIVICRM_DIRECTORY_SEPARATOR
.
477 elseif ($installType == 'wordpress') {
478 // make sure that we can write to plugins/civicrm and plugins/files/
479 $writableDirectories = array(WP_PLUGIN_DIR
. DIRECTORY_SEPARATOR
. 'files', $cmsPath);
482 foreach ($writableDirectories as $dir) {
483 $dirName = CIVICRM_WINDOWS ?
$dir : CIVICRM_DIRECTORY_SEPARATOR
. $dir;
484 $testDetails = array(
485 ts("File permissions"),
486 ts("Is the %1 folder writeable?", array(1 => $dir)),
489 $this->requireWriteable($dirName, $testDetails, TRUE);
492 //check for Config.IDS.ini, file may exist in re-install
493 $configIDSiniDir = array($cmsPath, 'sites', $siteDir, 'files', 'civicrm', 'upload', 'Config.IDS.ini');
495 if (is_array($configIDSiniDir) && !empty($configIDSiniDir)) {
496 $configIDSiniFile = implode(CIVICRM_DIRECTORY_SEPARATOR
, $configIDSiniDir);
497 if (file_exists($configIDSiniFile)) {
498 unlink($configIDSiniFile);
502 // Check for rewriting
503 if (isset($_SERVER['SERVER_SOFTWARE'])) {
504 $webserver = strip_tags(trim($_SERVER['SERVER_SOFTWARE']));
506 elseif (isset($_SERVER['SERVER_SIGNATURE'])) {
507 $webserver = strip_tags(trim($_SERVER['SERVER_SIGNATURE']));
510 if ($webserver == '') {
511 $webserver = ts("I can't tell what webserver you are running");
514 // Check for $_SERVER configuration
515 $this->requireServerVariables(array('SCRIPT_NAME', 'HTTP_HOST', 'SCRIPT_FILENAME'), array(
516 ts("Webserver config"),
517 ts("Recognised webserver"),
518 ts("You seem to be using an unsupported webserver. The server variables SCRIPT_NAME, HTTP_HOST, SCRIPT_FILENAME need to be set."),
521 // Check for MySQL support
522 $this->requireFunction('mysql_connect', array(
523 ts("PHP Configuration"),
525 ts("MySQL support not included in PHP."),
528 // Check for JSON support
529 $this->requireFunction('json_encode', array(
530 ts("PHP Configuration"),
532 ts("JSON support not included in PHP."),
535 // Check for xcache_isset and emit warning if exists
536 $this->checkXCache(array(
537 ts("PHP Configuration"),
538 ts("XCache compatibility"),
539 ts("XCache is installed and there are known compatibility issues between XCache and CiviCRM. Consider using an alternative PHP caching mechanism or disable PHP caching altogether."),
542 // Check memory allocation
543 $this->requireMemory(32 * 1024 * 1024,
546 ts("PHP Configuration"),
547 ts("Memory allocated (PHP config option 'memory_limit')"),
548 ts("CiviCRM needs a minimum of %1 MB allocated to PHP, but recommends %2 MB.", array(1 => 32, 2 => 64)),
549 ini_get("memory_limit"),
553 return $this->errors
;
558 * @param $recommended
559 * @param $testDetails
561 public function requireMemory($min, $recommended, $testDetails) {
562 $this->testing($testDetails);
563 $mem = $this->getPHPMemory();
565 if ($mem < $min && $mem > 0) {
566 $testDetails[2] .= " " . ts("You only have %1 allocated", array(1 => ini_get("memory_limit")));
567 $this->error($testDetails);
569 elseif ($mem < $recommended && $mem > 0) {
570 $testDetails[2] .= " " . ts("You only have %1 allocated", array(1 => ini_get("memory_limit")));
571 $this->warning($testDetails);
574 $testDetails[2] .= " " . ts("We can't determine how much memory you have allocated. Install only if you're sure you've allocated at least %1 MB.", array(1 => 32));
575 $this->warning($testDetails);
582 public function getPHPMemory() {
583 $memString = ini_get("memory_limit");
585 switch (strtolower(substr($memString, -1))) {
587 return round(substr($memString, 0, -1) * 1024);
590 return round(substr($memString, 0, -1) * 1024 * 1024);
593 return round(substr($memString, 0, -1) * 1024 * 1024 * 1024);
596 return round($memString);
600 public function listErrors() {
602 echo "<p>" . ts("The following problems are preventing me from installing CiviCRM:") . "</p>";
603 foreach ($this->errors
as $error) {
604 echo "<li>" . htmlentities($error) . "</li>";
610 * @param null $section
612 public function showTable($section = NULL) {
614 $tests = $this->tests
[$section];
615 echo "<table class=\"testResults\" width=\"100%\">";
616 foreach ($tests as $test => $result) {
617 echo "<tr class=\"$result[0]\"><td>$test</td><td>" . nl2br(htmlentities($result[1])) . "</td></tr>";
622 foreach ($this->tests
as $section => $tests) {
623 echo "<h3>$section</h3>";
624 echo "<table class=\"testResults\" width=\"100%\">";
626 foreach ($tests as $test => $result) {
627 echo "<tr class=\"$result[0]\"><td>$test</td><td>" . nl2br(htmlentities($result[1])) . "</td></tr>";
635 * @param string $funcName
636 * @param $testDetails
640 public function requireFunction($funcName, $testDetails) {
641 $this->testing($testDetails);
643 if (!function_exists($funcName)) {
644 $this->error($testDetails);
653 * @param $testDetails
655 public function checkXCache($testDetails) {
656 if (function_exists('xcache_isset') &&
657 ini_get('xcache.size') > 0
659 $this->testing($testDetails);
660 $this->warning($testDetails);
666 * @param $testDetails
667 * @param null $maxVersion
669 public function requirePHPVersion($minVersion, $testDetails, $maxVersion = NULL) {
671 $this->testing($testDetails);
673 $phpVersion = phpversion();
674 $aboveMinVersion = version_compare($phpVersion, $minVersion) >= 0;
675 $belowMaxVersion = $maxVersion ?
version_compare($phpVersion, $maxVersion) < 0 : TRUE;
677 if ($maxVersion && $aboveMinVersion && $belowMaxVersion) {
680 elseif (!$maxVersion && $aboveMinVersion) {
684 if (!$testDetails[2]) {
685 if (!$aboveMinVersion) {
686 $testDetails[2] = ts("You need PHP version %1 or later, only %2 is installed. Please upgrade your server, or ask your web-host to do so.", array(1 => $minVersion, 2 => $phpVersion));
689 $testDetails[2] = ts("PHP version %1 is not supported. PHP version earlier than %2 is required. You might want to downgrade your server, or ask your web-host to do so.", array(1 => $maxVersion, 2 => $phpVersion));
693 $this->error($testDetails);
697 * @param string $filename
698 * @param $testDetails
699 * @param bool $absolute
701 public function requireFile($filename, $testDetails, $absolute = FALSE) {
702 $this->testing($testDetails);
704 $filename = $this->getBaseDir() . $filename;
706 if (!file_exists($filename)) {
707 $testDetails[2] .= " (" . ts("file '%1' not found", array(1 => $filename)) . ')';
708 $this->error($testDetails);
713 * @param $testDetails
715 public function requireNoPathSeparator($testDetails) {
716 $this->testing($testDetails);
717 if (substr_count($this->getBaseDir(), PATH_SEPARATOR
)) {
718 $this->error($testDetails);
723 * @param string $filename
724 * @param $testDetails
726 public function requireNoFile($filename, $testDetails) {
727 $this->testing($testDetails);
728 $filename = $this->getBaseDir() . $filename;
729 if (file_exists($filename)) {
730 $testDetails[2] .= " (" . ts("file '%1' found", array(1 => $filename)) . ")";
731 $this->error($testDetails);
736 * @param string $filename
737 * @param $testDetails
739 public function moveFileOutOfTheWay($filename, $testDetails) {
740 $this->testing($testDetails);
741 $filename = $this->getBaseDir() . $filename;
742 if (file_exists($filename)) {
743 if (file_exists("$filename.bak")) {
746 rename($filename, "$filename.bak");
751 * @param string $filename
752 * @param $testDetails
753 * @param bool $absolute
755 public function requireWriteable($filename, $testDetails, $absolute = FALSE) {
756 $this->testing($testDetails);
758 $filename = $this->getBaseDir() . $filename;
761 if (!is_writable($filename)) {
763 if (function_exists('posix_getpwuid')) {
764 $user = posix_getpwuid(posix_geteuid());
765 $name = '- ' . $user['name'] . ' -';
768 if (!isset($testDetails[2])) {
769 $testDetails[2] = NULL;
771 $testDetails[2] .= ts("The user account used by your web-server %1 needs to be granted write access to the following directory in order to configure the CiviCRM settings file:", array(1 => $name)) . "\n$filename";
772 $this->error($testDetails);
777 * @param string $moduleName
778 * @param $testDetails
780 public function requireApacheModule($moduleName, $testDetails) {
781 $this->testing($testDetails);
782 if (!in_array($moduleName, apache_get_modules())) {
783 $this->error($testDetails);
789 * @param string $username
791 * @param $testDetails
793 public function requireMysqlConnection($server, $username, $password, $testDetails) {
794 $this->testing($testDetails);
795 $conn = @mysql_connect
($server, $username, $password);
801 $testDetails[2] .= ": " . mysql_error();
802 $this->error($testDetails);
808 * @param $testDetails
810 public function requireMySQLServer($server, $testDetails) {
811 $this->testing($testDetails);
812 $conn = @mysql_connect
($server, NULL, NULL);
814 if ($conn ||
mysql_errno() < 2000) {
818 $testDetails[2] .= ": " . mysql_error();
819 $this->error($testDetails);
825 * @param $testDetails
827 public function requireMySQLVersion($version, $testDetails) {
828 $this->testing($testDetails);
830 if (!mysql_get_server_info()) {
831 $testDetails[2] = ts('Cannot determine the version of MySQL installed. Please ensure at least version %1 is installed.', array(1 => $version));
832 $this->warning($testDetails);
835 list($majorRequested, $minorRequested) = explode('.', $version);
836 list($majorHas, $minorHas) = explode('.', mysql_get_server_info());
838 if (($majorHas > $majorRequested) ||
($majorHas == $majorRequested && $minorHas >= $minorRequested)) {
842 $testDetails[2] .= "{$majorHas}.{$minorHas}.";
843 $this->error($testDetails);
850 * @param string $username
853 * @param $testDetails
855 public function requireMySQLInnoDB($server, $username, $password, $database, $testDetails) {
856 $this->testing($testDetails);
857 $conn = @mysql_connect
($server, $username, $password);
859 $testDetails[2] .= ' ' . ts("Could not determine if MySQL has InnoDB support. Assuming no.");
860 $this->error($testDetails);
864 $innodb_support = FALSE;
865 $result = mysql_query("SHOW ENGINES", $conn);
866 while ($values = mysql_fetch_array($result)) {
867 if ($values['Engine'] == 'InnoDB') {
868 if (strtolower($values['Support']) == 'yes' ||
869 strtolower($values['Support']) == 'default'
871 $innodb_support = TRUE;
875 if ($innodb_support) {
876 $testDetails[3] = ts('MySQL server does have InnoDB support');
879 $testDetails[2] .= ' ' . ts('Could not determine if MySQL has InnoDB support. Assuming no');
885 * @param string $username
888 * @param $testDetails
890 public function requireMySQLTempTables($server, $username, $password, $database, $testDetails) {
891 $this->testing($testDetails);
892 $conn = @mysql_connect
($server, $username, $password);
894 $testDetails[2] = ts('Could not login to the database.');
895 $this->error($testDetails);
899 if (!@mysql_select_db
($database, $conn)) {
900 $testDetails[2] = ts('Could not select the database.');
901 $this->error($testDetails);
905 $result = mysql_query('CREATE TEMPORARY TABLE civicrm_install_temp_table_test (test text)', $conn);
907 $testDetails[2] = ts('Could not create a temp table.');
908 $this->error($testDetails);
910 $result = mysql_query('DROP TEMPORARY TABLE civicrm_install_temp_table_test');
915 * @param string $username
918 * @param $testDetails
920 public function requireMySQLTrigger($server, $username, $password, $database, $testDetails) {
921 $this->testing($testDetails);
922 $conn = @mysql_connect
($server, $username, $password);
924 $testDetails[2] = ts('Could not login to the database.');
925 $this->error($testDetails);
929 if (!@mysql_select_db
($database, $conn)) {
930 $testDetails[2] = ts('Could not select the database.');
931 $this->error($testDetails);
935 $result = mysql_query('CREATE TABLE civicrm_install_temp_table_test (test text)', $conn);
937 $testDetails[2] = ts('Could not create a table in the database.');
938 $this->error($testDetails);
941 $result = mysql_query('CREATE TRIGGER civicrm_install_temp_table_test_trigger BEFORE INSERT ON civicrm_install_temp_table_test FOR EACH ROW BEGIN END');
943 mysql_query('DROP TABLE civicrm_install_temp_table_test');
944 $testDetails[2] = ts('Could not create a database trigger.');
945 $this->error($testDetails);
948 mysql_query('DROP TRIGGER civicrm_install_temp_table_test_trigger');
949 mysql_query('DROP TABLE civicrm_install_temp_table_test');
955 * @param string $username
958 * @param $testDetails
960 public function requireMySQLLockTables($server, $username, $password, $database, $testDetails) {
961 $this->testing($testDetails);
962 $conn = @mysql_connect
($server, $username, $password);
964 $testDetails[2] = ts('Could not connect to the database server.');
965 $this->error($testDetails);
969 if (!@mysql_select_db
($database, $conn)) {
970 $testDetails[2] = ts('Could not select the database.');
971 $this->error($testDetails);
975 $result = mysql_query('CREATE TEMPORARY TABLE civicrm_install_temp_table_test (test text)', $conn);
977 $testDetails[2] = ts('Could not create a table in the database.');
978 $this->error($testDetails);
982 $result = mysql_query('LOCK TABLES civicrm_install_temp_table_test WRITE', $conn);
984 $testDetails[2] = ts('Could not obtain a write lock for the database table.');
985 $this->error($testDetails);
986 $result = mysql_query('DROP TEMPORARY TABLE civicrm_install_temp_table_test');
990 $result = mysql_query('UNLOCK TABLES', $conn);
992 $testDetails[2] = ts('Could not release the lock for the database table.');
993 $this->error($testDetails);
994 $result = mysql_query('DROP TEMPORARY TABLE civicrm_install_temp_table_test');
998 $result = mysql_query('DROP TEMPORARY TABLE civicrm_install_temp_table_test');
1003 * @param string $username
1005 * @param $testDetails
1007 public function requireMySQLAutoIncrementIncrementOne($server, $username, $password, $testDetails) {
1008 $this->testing($testDetails);
1009 $conn = @mysql_connect
($server, $username, $password);
1011 $testDetails[2] = ts('Could not connect to the database server.');
1012 $this->error($testDetails);
1016 $result = mysql_query("SHOW variables like 'auto_increment_increment'", $conn);
1018 $testDetails[2] = ts('Could not query database server variables.');
1019 $this->error($testDetails);
1023 $values = mysql_fetch_row($result);
1024 if ($values[1] == 1) {
1025 $testDetails[3] = ts('MySQL server auto_increment_increment is 1');
1028 $this->error($testDetails);
1035 * @param string $username
1038 * @param $minValueKB
1039 * @param $testDetails
1041 public function requireMySQLThreadStack($server, $username, $password, $database, $minValueKB, $testDetails) {
1042 $this->testing($testDetails);
1043 $conn = @mysql_connect
($server, $username, $password);
1045 $testDetails[2] = ts('Could not connect to the database server.');
1046 $this->error($testDetails);
1050 if (!@mysql_select_db
($database, $conn)) {
1051 $testDetails[2] = ts('Could not select the database.');
1052 $this->error($testDetails);
1056 $result = mysql_query("SHOW VARIABLES LIKE 'thread_stack'", $conn); // bytes => kb
1058 $testDetails[2] = ts('Could not get information about the thread_stack of the database.');
1059 $this->error($testDetails);
1062 $values = mysql_fetch_row($result);
1063 if ($values[1] < (1024 * $minValueKB)) {
1064 $testDetails[2] = ts('MySQL "thread_stack" is %1 kb', array(1 => ($values[1] / 1024)));
1065 $this->error($testDetails);
1072 * @param string $username
1075 * @param $testDetails
1076 * @param bool $onlyRequire
1078 public function requireDatabaseOrCreatePermissions(
1084 $onlyRequire = FALSE
1086 $this->testing($testDetails);
1087 $conn = @mysql_connect
($server, $username, $password);
1090 if (@mysql_select_db
($database)) {
1091 $okay = "Database '$database' exists";
1093 elseif ($onlyRequire) {
1094 $testDetails[2] = ts("The database: '%1' does not exist.", array(1 => $database));
1095 $this->error($testDetails);
1099 if (@mysql_query
("CREATE DATABASE $database")) {
1100 $okay = ts("Able to create a new database.");
1103 $testDetails[2] .= " (" . ts("user '%1' doesn't have CREATE DATABASE permissions.", array(1 => $username)) . ")";
1104 $this->error($testDetails);
1110 $testDetails[3] = $okay;
1111 $this->testing($testDetails);
1117 * @param $errorMessage
1119 public function requireServerVariables($varNames, $errorMessage) {
1120 //$this->testing($testDetails);
1121 foreach ($varNames as $varName) {
1122 if (!$_SERVER[$varName]) {
1123 $missing[] = '$_SERVER[' . $varName . ']';
1126 if (!isset($missing)) {
1130 $testDetails[2] = " (" . ts('the following PHP variables are missing: %1', array(1 => implode(", ", $missing))) . ")";
1131 $this->error($testDetails);
1136 * @param $testDetails
1140 public function isRunningApache($testDetails) {
1141 $this->testing($testDetails);
1142 if (function_exists('apache_get_modules') ||
stristr($_SERVER['SERVER_SIGNATURE'], 'Apache')) {
1146 $this->warning($testDetails);
1153 public function getBaseDir() {
1154 return dirname($_SERVER['SCRIPT_FILENAME']) . CIVICRM_DIRECTORY_SEPARATOR
;
1158 * @param $testDetails
1160 public function testing($testDetails) {
1161 if (!$testDetails) {
1165 $section = $testDetails[0];
1166 $test = $testDetails[1];
1168 $message = ts("OK");
1169 if (isset($testDetails[3])) {
1170 $message .= " ($testDetails[3])";
1173 $this->tests
[$section][$test] = array("good", $message);
1177 * @param $testDetails
1179 public function error($testDetails) {
1180 $section = $testDetails[0];
1181 $test = $testDetails[1];
1183 $this->tests
[$section][$test] = array("error", $testDetails[2]);
1184 $this->errors
[] = $testDetails;
1188 * @param $testDetails
1190 public function warning($testDetails) {
1191 $section = $testDetails[0];
1192 $test = $testDetails[1];
1194 $this->tests
[$section][$test] = array("warning", $testDetails[2]);
1195 $this->warnings
[] = $testDetails;
1201 public function hasErrors() {
1202 return count($this->errors
);
1208 public function hasWarnings() {
1209 return count($this->warnings
);
1217 class Installer
extends InstallRequirements
{
1224 public function createDatabaseIfNotExists($server, $username, $password, $database) {
1225 $conn = @mysql_connect
($server, $username, $password);
1227 if (@mysql_select_db
($database)) {
1228 // skip if database already present
1232 if (@mysql_query
("CREATE DATABASE $database")) {
1235 $errorTitle = ts("Oops! Could not create database %1", array(1 => $database));
1236 $errorMsg = ts("We encountered an error when attempting to create the database. Please check your MySQL server permissions and the database name and try again.");
1237 errorDisplayPage($errorTitle, $errorMsg);
1246 public function install($config) {
1247 global $installDirPath;
1249 // create database if does not exists
1250 $this->createDatabaseIfNotExists($config['mysql']['server'],
1251 $config['mysql']['username'],
1252 $config['mysql']['password'],
1253 $config['mysql']['database']
1256 global $installDirPath;
1259 require_once $installDirPath . 'civicrm.php';
1260 civicrm_main($config);
1262 if (!$this->errors
) {
1263 global $installType, $installURLPath;
1265 $registerSiteURL = "https://civicrm.org/register-site";
1266 $commonOutputMessage
1267 = "<li>" . ts("Have you registered this site at CiviCRM.org? If not, please help strengthen the CiviCRM ecosystem by taking a few minutes to <a %1>fill out the site registration form</a>. The information collected will help us prioritize improvements, target our communications and build the community. If you have a technical role for this site, be sure to check Keep in Touch to receive technical updates (a low volume mailing list).", array(1 => "href='$registerSiteURL' target='_blank'")) . "</li>"
1268 . "<li>" . ts("We have integrated KCFinder with CKEditor and TinyMCE. This allows a user to upload images. All uploaded images are public.") . "</li>";
1273 $installType == 'drupal' &&
1274 version_compare(VERSION
, '7.0-rc1') >= 0
1280 $output .= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
1281 $output .= '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">';
1282 $output .= '<head>';
1283 $output .= '<title>' . ts('CiviCRM Installed') . '</title>';
1284 $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
1285 $output .= '<link rel="stylesheet" type="text/css" href="template.css" />';
1286 $output .= '</head>';
1287 $output .= '<body>';
1288 $output .= '<div style="padding: 1em;"><p class="good">' . ts('CiviCRM has been successfully installed') . '</p>';
1291 $drupalURL = civicrm_cms_base();
1292 $drupalPermissionsURL = "{$drupalURL}index.php?q=admin/people/permissions";
1293 $drupalURL .= "index.php?q=civicrm/admin/configtask&reset=1";
1295 $output .= "<li>" . ts("Drupal user permissions have been automatically set - giving anonymous and authenticated users access to public CiviCRM forms and features. We recommend that you <a %1>review these permissions</a> to ensure that they are appropriate for your requirements (<a %2>learn more...</a>)", array(1 => "target='_blank' href='{$drupalPermissionsURL}'", 2 => "target='_blank' href='http://wiki.civicrm.org/confluence/display/CRMDOC/Default+Permissions+and+Roles'")) . "</li>";
1296 $output .= "<li>" . ts("Use the <a %1>Configuration Checklist</a> to review and configure settings for your new site", array(1 => "target='_blank' href='$drupalURL'")) . "</li>";
1297 $output .= $commonOutputMessage;
1299 // automatically enable CiviCRM module once it is installed successfully.
1300 // so we need to Bootstrap Drupal, so that we can call drupal hooks.
1301 global $cmsPath, $crmPath;
1303 // relative / abosolute paths are not working for drupal, hence using chdir()
1306 // Force the re-initialisation of the config singleton on the next call
1307 // since so far, we had used the Config object without loading the DB.
1308 $c = CRM_Core_Config
::singleton(FALSE);
1311 include_once "./includes/bootstrap.inc";
1312 include_once "./includes/unicode.inc";
1314 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL
);
1316 // prevent session information from being saved.
1317 drupal_save_session(FALSE);
1319 // Force the current user to anonymous.
1320 $original_user = $GLOBALS['user'];
1321 $GLOBALS['user'] = drupal_anonymous_user();
1323 // explicitly setting error reporting, since we cannot handle drupal related notices
1326 // rebuild modules, so that civicrm is added
1327 system_rebuild_module_data();
1329 // now enable civicrm module.
1330 module_enable(array('civicrm', 'civicrmtheme'));
1332 // clear block, page, theme, and hook caches
1333 drupal_flush_all_caches();
1335 //add basic drupal permissions
1336 civicrm_install_set_drupal_perms();
1338 // restore the user.
1339 $GLOBALS['user'] = $original_user;
1340 drupal_save_session(TRUE);
1342 //change the default language to one chosen
1343 if (isset($config['seedLanguage']) && $config['seedLanguage'] != 'en_US') {
1344 // This ensures that defaults get set, otherwise the user will login
1345 // and most configurations will be empty, not set to en_US defaults.
1346 civicrm_api3('Setting', 'revert');
1348 civicrm_api3('Setting', 'create', array(
1349 'domain_id' => 'current_domain',
1350 'lcMessages' => $config['seedLanguage'],
1356 $output .= '</div>';
1357 $output .= '</body>';
1358 $output .= '</html>';
1361 elseif ($installType == 'drupal' && version_compare(VERSION
, '6.0') >= 0) {
1365 $output .= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
1366 $output .= '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">';
1367 $output .= '<head>';
1368 $output .= '<title>' . ts('CiviCRM Installed') . '</title>';
1369 $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
1370 $output .= '<link rel="stylesheet" type="text/css" href="template.css" />';
1371 $output .= '</head>';
1372 $output .= '<body>';
1373 $output .= '<div style="padding: 1em;"><p class="good">' . ts("CiviCRM has been successfully installed") . '</p>';
1376 $drupalURL = civicrm_cms_base();
1377 $drupalPermissionsURL = "{$drupalURL}index.php?q=admin/user/permissions";
1378 $drupalURL .= "index.php?q=civicrm/admin/configtask&reset=1";
1380 $output .= "<li>" . ts("Drupal user permissions have been automatically set - giving anonymous and authenticated users access to public CiviCRM forms and features. We recommend that you <a %1>review these permissions</a> to ensure that they are appropriate for your requirements (<a %2>learn more...</a>)", array(1 => "target='_blank' href='{$drupalPermissionsURL}'", 2 => "target='_blank' href='http://wiki.civicrm.org/confluence/display/CRMDOC/Default+Permissions+and+Roles'")) . "</li>";
1381 $output .= "<li>" . ts("Use the <a %1>Configuration Checklist</a> to review and configure settings for your new site", array(1 => "target='_blank' href='$drupalURL'")) . "</li>";
1382 $output .= $commonOutputMessage;
1384 // explicitly setting error reporting, since we cannot handle drupal related notices
1387 // automatically enable CiviCRM module once it is installed successfully.
1388 // so we need to Bootstrap Drupal, so that we can call drupal hooks.
1389 global $cmsPath, $crmPath;
1391 // relative / abosolute paths are not working for drupal, hence using chdir()
1394 // Force the re-initialisation of the config singleton on the next call
1395 // since so far, we had used the Config object without loading the DB.
1396 $c = CRM_Core_Config
::singleton(FALSE);
1399 include_once "./includes/bootstrap.inc";
1400 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL
);
1402 // rebuild modules, so that civicrm is added
1403 module_rebuild_cache();
1405 // now enable civicrm module.
1406 module_enable(array('civicrm'));
1408 // clear block, page, theme, and hook caches
1409 drupal_flush_all_caches();
1411 //add basic drupal permissions
1412 db_query('UPDATE {permission} SET perm = CONCAT( perm, \', access CiviMail subscribe/unsubscribe pages, access all custom data, access uploaded files, make online contributions, profile create, profile edit, profile view, register for events, view event info\') WHERE rid IN (1, 2)');
1416 elseif ($installType == 'wordpress') {
1417 echo '<h1>' . ts('CiviCRM Installed') . '</h1>';
1418 echo '<div style="padding: 1em;"><p style="background-color: #0C0; border: 1px #070 solid; color: white;">' . ts("CiviCRM has been successfully installed") . '</p>';
1421 $cmsURL = civicrm_cms_base();
1422 $cmsURL .= "wp-admin/admin.php?page=CiviCRM&q=civicrm/admin/configtask&reset=1";
1423 $wpPermissionsURL = "wp-admin/admin.php?page=CiviCRM&q=civicrm/admin/access/wp-permissions&reset=1";
1425 $output .= "<li>" . ts("WordPress user permissions have been automatically set - giving Anonymous and Subscribers access to public CiviCRM forms and features. We recommend that you <a %1>review these permissions</a> to ensure that they are appropriate for your requirements (<a %2>learn more...</a>)", array(1 => "target='_blank' href='{$wpPermissionsURL}'", 2 => "target='_blank' href='http://wiki.civicrm.org/confluence/display/CRMDOC/Default+Permissions+and+Roles'")) . "</li>";
1426 $output .= "<li>" . ts("Use the <a %1>Configuration Checklist</a> to review and configure settings for your new site", array(1 => "target='_blank' href='$cmsURL'")) . "</li>";
1427 $output .= $commonOutputMessage;
1432 $c = CRM_Core_Config
::singleton(FALSE);
1437 return $this->errors
;
1442 function civicrm_install_set_drupal_perms() {
1443 if (!function_exists('db_select')) {
1444 db_query('UPDATE {permission} SET perm = CONCAT( perm, \', access CiviMail subscribe/unsubscribe pages, access all custom data, access uploaded files, make online contributions, profile listings and forms, register for events, view event info, view event participants\') WHERE rid IN (1, 2)');
1448 'access all custom data',
1449 'access uploaded files',
1450 'make online contributions',
1454 'register for events',
1456 'view event participants',
1457 'access CiviMail subscribe/unsubscribe pages',
1460 // Adding a permission that has not yet been assigned to a module by
1461 // a hook_permission implementation results in a database error.
1463 $allPerms = array_keys(module_invoke_all('permission'));
1464 foreach (array_diff($perms, $allPerms) as $perm) {
1466 'Cannot grant the %perm permission because it does not yet exist.',
1467 array('%perm' => $perm),
1471 $perms = array_intersect($perms, $allPerms);
1472 user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID
, $perms);
1473 user_role_grant_permissions(DRUPAL_ANONYMOUS_RID
, $perms);
1483 function getSiteDir($cmsPath, $str) {
1484 static $siteDir = '';
1490 $sites = CIVICRM_DIRECTORY_SEPARATOR
. 'sites' . CIVICRM_DIRECTORY_SEPARATOR
;
1491 $modules = CIVICRM_DIRECTORY_SEPARATOR
. 'modules' . CIVICRM_DIRECTORY_SEPARATOR
;
1492 preg_match("/" . preg_quote($sites, CIVICRM_DIRECTORY_SEPARATOR
) .
1493 "([\-a-zA-Z0-9_.]+)" .
1494 preg_quote($modules, CIVICRM_DIRECTORY_SEPARATOR
) . "/",
1495 $_SERVER['SCRIPT_FILENAME'], $matches
1497 $siteDir = isset($matches[1]) ?
$matches[1] : 'default';
1499 if (strtolower($siteDir) == 'all') {
1500 // For this case - use drupal's way of finding out multi-site directory
1501 $uri = explode(CIVICRM_DIRECTORY_SEPARATOR
, $_SERVER['SCRIPT_FILENAME']);
1502 $server = explode('.', implode('.', array_reverse(explode(':', rtrim($_SERVER['HTTP_HOST'], '.')))));
1503 for ($i = count($uri) - 1; $i > 0; $i--) {
1504 for ($j = count($server); $j > 0; $j--) {
1505 $dir = implode('.', array_slice($server, -$j)) . implode('.', array_slice($uri, 0, $i));
1506 if (file_exists($cmsPath . CIVICRM_DIRECTORY_SEPARATOR
.
1507 'sites' . CIVICRM_DIRECTORY_SEPARATOR
. $dir
1514 $siteDir = 'default';
1521 * @param $errorTitle
1525 function errorDisplayPage($errorTitle, $errorMsg, $showRefer = TRUE) {
1527 $docLink = CRM_Utils_System
::docURL2('Installation and Upgrades', FALSE, 'Installation Guide', NULL, NULL, "wiki");
1529 if (function_exists('ts')) {
1530 $errorMsg .= '<p>' . ts("<a %1>Refer to the online documentation for more information</a>", array(1 => "href='$docLink'")) . '</p>';
1533 $errorMsg .= '<p>' . sprintf("<a %s>Refer to the online documentation for more information</a>", "href='$docLink'") . '</p>';
1537 include 'error.html';