CRM-20926 - Kill Config.IDS.ini
[civicrm-core.git] / install / index.php
1 <?php
2
3 /**
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/.
7 *
8 * Copyright (c) 2006-7, SilverStripe Limited - www.silverstripe.com
9 * All rights reserved.
10 *
11 * License: BSD-3-clause
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions are
14 * met:
15 *
16 * Redistributions of source code must retain the above copyright notice,
17 * this list of conditions and the following disclaimer.
18 *
19 * Redistributions in binary form must reproduce the above copyright
20 * notice, this list of conditions and the following disclaimer in the
21 * documentation and/or other materials provided with the distribution.
22 *
23 * Neither the name of SilverStripe nor the names of its contributors may
24 * be used to endorse or promote products derived from this software
25 * without specific prior written permission.
26 *
27 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
28 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
29 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
30 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
31 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
32 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
33 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
34 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
35 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
36 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
37 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 *
39 * Changes and modifications (c) 2007-2017 by CiviCRM LLC
40 *
41 */
42
43 /**
44 * CiviCRM Installer
45 */
46 ini_set('max_execution_time', 3000);
47
48 if (stristr(PHP_OS, 'WIN')) {
49 define('CIVICRM_DIRECTORY_SEPARATOR', '/');
50 define('CIVICRM_WINDOWS', 1);
51 }
52 else {
53 define('CIVICRM_DIRECTORY_SEPARATOR', DIRECTORY_SEPARATOR);
54 define('CIVICRM_WINDOWS', 0);
55 }
56
57 // set installation type - drupal
58 if (!session_id()) {
59 if (defined('PANTHEON_ENVIRONMENT')) {
60 ini_set('session.save_handler', 'files');
61 }
62 session_start();
63 }
64
65 // unset civicrm session if any
66 if (array_key_exists('CiviCRM', $_SESSION)) {
67 unset($_SESSION['CiviCRM']);
68 }
69
70 if (isset($_GET['civicrm_install_type'])) {
71 $_SESSION['civicrm_install_type'] = $_GET['civicrm_install_type'];
72 }
73 else {
74 if (!isset($_SESSION['civicrm_install_type'])) {
75 $_SESSION['civicrm_install_type'] = "drupal";
76 }
77 }
78
79 global $installType;
80 global $crmPath;
81 global $pkgPath;
82 global $installDirPath;
83 global $installURLPath;
84
85 $installType = strtolower($_SESSION['civicrm_install_type']);
86
87 if ($installType == 'drupal' || $installType == 'backdrop') {
88 $crmPath = dirname(dirname($_SERVER['SCRIPT_FILENAME']));
89 $installDirPath = $installURLPath = '';
90 }
91 elseif ($installType == 'wordpress') {
92 $crmPath = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'civicrm' . DIRECTORY_SEPARATOR . 'civicrm' . DIRECTORY_SEPARATOR;
93 $installDirPath = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'civicrm' . DIRECTORY_SEPARATOR . 'civicrm' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR;
94 $installURLPath = WP_PLUGIN_URL . DIRECTORY_SEPARATOR . 'civicrm' . DIRECTORY_SEPARATOR . 'civicrm' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR;
95 }
96 else {
97 $errorTitle = "Oops! Unsupported installation mode";
98 $errorMsg = sprintf('%s: unknown installation mode. Please refer to the online documentation for more information.', $installType);
99 errorDisplayPage($errorTitle, $errorMsg, FALSE);
100 }
101
102 $pkgPath = $crmPath . DIRECTORY_SEPARATOR . 'packages';
103
104 require_once $crmPath . '/CRM/Core/ClassLoader.php';
105 CRM_Core_ClassLoader::singleton()->register();
106
107 // Load civicrm database config
108 if (isset($_POST['mysql'])) {
109 $databaseConfig = $_POST['mysql'];
110 }
111 else {
112 $databaseConfig = array(
113 "server" => "localhost",
114 "username" => "civicrm",
115 "password" => "",
116 "database" => "civicrm",
117 );
118 }
119
120 if ($installType == 'wordpress') {
121 // Load WP database config
122 if (isset($_POST['mysql'])) {
123 $databaseConfig = $_POST['mysql'];
124 }
125 else {
126 $databaseConfig = array(
127 "server" => DB_HOST,
128 "username" => DB_USER,
129 "password" => DB_PASSWORD,
130 "database" => DB_NAME,
131 );
132 }
133 }
134
135 if ($installType == 'drupal') {
136 // Load drupal database config
137 if (isset($_POST['drupal'])) {
138 $drupalConfig = $_POST['drupal'];
139 }
140 else {
141 $drupalConfig = array(
142 "server" => "localhost",
143 "username" => "drupal",
144 "password" => "",
145 "database" => "drupal",
146 );
147 }
148 }
149
150 if ($installType == 'backdrop') {
151 // Load backdrop database config
152 if (isset($_POST['backdrop'])) {
153 $backdropConfig = $_POST['backdrop'];
154 }
155 else {
156 $backdropConfig = array(
157 "server" => "localhost",
158 "username" => "backdrop",
159 "password" => "",
160 "database" => "backdrop",
161 );
162 }
163 }
164
165 $loadGenerated = 0;
166 if (isset($_POST['loadGenerated'])) {
167 $loadGenerated = 1;
168 }
169
170 require_once dirname(__FILE__) . CIVICRM_DIRECTORY_SEPARATOR . 'langs.php';
171 foreach ($langs as $locale => $_) {
172 if ($locale == 'en_US') {
173 continue;
174 }
175 if (!file_exists(implode(CIVICRM_DIRECTORY_SEPARATOR, array($crmPath, 'sql', "civicrm_data.$locale.mysql")))) {
176 unset($langs[$locale]);
177 }
178 }
179
180 // Set the CMS
181 // This is mostly sympbolic, since nothing we do during the install
182 // really requires CIVICRM_UF to be defined.
183 $installTypeToUF = array(
184 'wordpress' => 'WordPress',
185 'drupal' => 'Drupal',
186 'backdrop' => 'Backdrop',
187 );
188
189 $uf = (isset($installTypeToUF[$installType]) ? $installTypeToUF[$installType] : 'Drupal');
190 define('CIVICRM_UF', $uf);
191
192 // Set the Locale (required by CRM_Core_Config)
193 global $tsLocale;
194
195 $tsLocale = 'en_US';
196 $seedLanguage = 'en_US';
197
198 // CRM-16801 This validates that seedLanguage is valid by looking in $langs.
199 // NB: the variable is initial a $_REQUEST for the initial page reload,
200 // then becomes a $_POST when the installation form is submitted.
201 if (isset($_REQUEST['seedLanguage']) and isset($langs[$_REQUEST['seedLanguage']])) {
202 $seedLanguage = $_REQUEST['seedLanguage'];
203 $tsLocale = $_REQUEST['seedLanguage'];
204 }
205
206 $config = CRM_Core_Config::singleton(FALSE);
207 $GLOBALS['civicrm_default_error_scope'] = NULL;
208
209 // The translation files are in the parent directory (l10n)
210 $i18n = CRM_Core_I18n::singleton();
211
212 // Support for Arabic, Hebrew, Farsi, etc.
213 // Used in the template.html
214 $short_lang_code = CRM_Core_I18n_PseudoConstant::shortForLong($tsLocale);
215 $text_direction = (CRM_Core_I18n::isLanguageRTL($tsLocale) ? 'rtl' : 'ltr');
216
217 global $cmsPath;
218 if ($installType == 'drupal') {
219 //CRM-6840 -don't force to install in sites/all/modules/
220 $object = new CRM_Utils_System_Drupal();
221 $cmsPath = $object->cmsRootPath();
222
223 $siteDir = getSiteDir($cmsPath, $_SERVER['SCRIPT_FILENAME']);
224 $alreadyInstalled = file_exists($cmsPath . CIVICRM_DIRECTORY_SEPARATOR .
225 'sites' . CIVICRM_DIRECTORY_SEPARATOR .
226 $siteDir . CIVICRM_DIRECTORY_SEPARATOR .
227 'civicrm.settings.php'
228 );
229 }
230 elseif ($installType == 'backdrop') {
231 $object = new CRM_Utils_System_Backdrop();
232 $cmsPath = $object->cmsRootPath();
233 $siteDir = getSiteDir($cmsPath, $_SERVER['SCRIPT_FILENAME']);
234 $alreadyInstalled = file_exists($cmsPath . CIVICRM_DIRECTORY_SEPARATOR . 'civicrm.settings.php');
235 }
236 elseif ($installType == 'wordpress') {
237 $cmsPath = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'civicrm';
238 $upload_dir = wp_upload_dir();
239 $files_dirname = $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'civicrm';
240 $wp_civi_settings = $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'civicrm' . DIRECTORY_SEPARATOR . 'civicrm.settings.php';
241 $wp_civi_settings_deprectated = CIVICRM_PLUGIN_DIR . 'civicrm.settings.php';
242 if (file_exists($wp_civi_settings_deprectated)) {
243 $alreadyInstalled = $wp_civi_settings_deprectated;
244 }
245 elseif (file_exists($wp_civi_settings)) {
246 $alreadyInstalled = $wp_civi_settings;
247 }
248 }
249
250 if ($installType == 'drupal') {
251 // Lets check only /modules/.
252 $pattern = '/' . preg_quote(CIVICRM_DIRECTORY_SEPARATOR . 'modules', CIVICRM_DIRECTORY_SEPARATOR) . '/';
253
254 if (!preg_match($pattern, str_replace("\\", "/", $_SERVER['SCRIPT_FILENAME']))) {
255 $directory = implode(CIVICRM_DIRECTORY_SEPARATOR, array('sites', 'all', 'modules'));
256 $errorTitle = ts("Oops! Please correct your install location");
257 $errorMsg = ts("Please untar (uncompress) your downloaded copy of CiviCRM in the <strong>%1</strong> directory below your Drupal root directory.", array(1 => $directory));
258 errorDisplayPage($errorTitle, $errorMsg);
259 }
260 }
261
262 if ($installType == 'backdrop') {
263 // Lets check only /modules/.
264 $pattern = '/' . preg_quote(CIVICRM_DIRECTORY_SEPARATOR . 'modules', CIVICRM_DIRECTORY_SEPARATOR) . '/';
265
266 if (!preg_match($pattern, str_replace("\\", "/", $_SERVER['SCRIPT_FILENAME']))) {
267 $directory = 'modules';
268 $errorTitle = ts("Oops! Please correct your install location");
269 $errorMsg = ts("Please untar (uncompress) your downloaded copy of CiviCRM in the <strong>%1</strong> directory below your Drupal root directory.", array(1 => $directory));
270 errorDisplayPage($errorTitle, $errorMsg);
271 }
272 }
273
274 // Exit with error if CiviCRM has already been installed.
275 if ($alreadyInstalled) {
276 $errorTitle = ts("Oops! CiviCRM is already installed");
277 $settings_directory = $cmsPath;
278
279 if ($installType == 'drupal') {
280 $settings_directory = implode(CIVICRM_DIRECTORY_SEPARATOR, array(
281 ts('[your Drupal root directory]'),
282 'sites',
283 $siteDir,
284 ));
285 }
286 if ($installType == 'backdrop') {
287 $settings_directory = implode(CIVICRM_DIRECTORY_SEPARATOR, array(
288 ts('[your Backdrop root directory]'),
289 $siteDir,
290 ));
291 }
292
293 $docLink = CRM_Utils_System::docURL2('Installation and Upgrades', FALSE, ts('Installation Guide'), NULL, NULL, "wiki");
294 $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));
295 errorDisplayPage($errorTitle, $errorMsg, FALSE);
296 }
297
298 $versionFile = $crmPath . CIVICRM_DIRECTORY_SEPARATOR . 'civicrm-version.php';
299 if (file_exists($versionFile)) {
300 require_once $versionFile;
301 $civicrm_version = civicrmVersion();
302 }
303 else {
304 $civicrm_version = 'unknown';
305 }
306
307 if ($installType == 'drupal') {
308 // Ensure that they have downloaded the correct version of CiviCRM
309 if ($civicrm_version['cms'] != 'Drupal' && $civicrm_version['cms'] != 'Drupal6') {
310 $errorTitle = ts("Oops! Incorrect CiviCRM version");
311 $errorMsg = ts("This installer can only be used for the Drupal version of CiviCRM.");
312 errorDisplayPage($errorTitle, $errorMsg);
313 }
314
315 define('DRUPAL_ROOT', $cmsPath);
316 $drupalVersionFiles = array(
317 // D6
318 implode(CIVICRM_DIRECTORY_SEPARATOR, array($cmsPath, 'modules', 'system', 'system.module')),
319 // D7
320 implode(CIVICRM_DIRECTORY_SEPARATOR, array($cmsPath, 'includes', 'bootstrap.inc')),
321 );
322 foreach ($drupalVersionFiles as $drupalVersionFile) {
323 if (file_exists($drupalVersionFile)) {
324 require_once $drupalVersionFile;
325 }
326 }
327
328 if (!defined('VERSION') or version_compare(VERSION, '6.0') < 0) {
329 $errorTitle = ts("Oops! Incorrect Drupal version");
330 $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)));
331 errorDisplayPage($errorTitle, $errorMsg);
332 }
333 }
334 elseif ($installType == 'backdrop') {
335 // Ensure that they have downloaded the correct version of CiviCRM
336 if ($civicrm_version['cms'] != 'Backdrop') {
337 $errorTitle = ts("Oops! Incorrect CiviCRM version");
338 $errorMsg = ts("This installer can only be used for the Backdrop version of CiviCRM.");
339 errorDisplayPage($errorTitle, $errorMsg);
340 }
341
342 define('BACKDROP_ROOT', $cmsPath);
343
344 $backdropVersionFiles = array(
345 // Backdrop
346 implode(CIVICRM_DIRECTORY_SEPARATOR, array($cmsPath, 'core', 'includes', 'bootstrap.inc')),
347 );
348 foreach ($backdropVersionFiles as $backdropVersionFile) {
349 if (file_exists($backdropVersionFile)) {
350 require_once $backdropVersionFile;
351 }
352 }
353 if (!defined('BACKDROP_VERSION') or version_compare(BACKDROP_VERSION, '1.0') < 0) {
354 $errorTitle = ts("Oops! Incorrect Backdrop version");
355 $errorMsg = ts("This version of CiviCRM can only be used with Backdrop 1.x. Please ensure that '%1' exists if you are running Backdrop 1.0 and over.", array(1 => implode("' or '", $backdropVersionFiles)));
356 errorDisplayPage($errorTitle, $errorMsg);
357 }
358 }
359 elseif ($installType == 'wordpress') {
360 //HACK for now
361 $civicrm_version['cms'] = 'WordPress';
362
363 // Ensure that they have downloaded the correct version of CiviCRM
364 if ($civicrm_version['cms'] != 'WordPress') {
365 $errorTitle = ts("Oops! Incorrect CiviCRM version");
366 $errorMsg = ts("This installer can only be used for the WordPress version of CiviCRM.");
367 errorDisplayPage($errorTitle, $errorMsg);
368 }
369 }
370
371 // Check requirements
372 $req = new InstallRequirements();
373 $req->check();
374
375 if ($req->hasErrors()) {
376 $hasErrorOtherThanDatabase = TRUE;
377 }
378
379 if ($databaseConfig) {
380 $dbReq = new InstallRequirements();
381 $dbReq->checkdatabase($databaseConfig, 'CiviCRM');
382 if ($installType == 'drupal') {
383 $dbReq->checkdatabase($drupalConfig, 'Drupal');
384 }
385 if ($installType == 'backdrop') {
386 $dbReq->checkdatabase($backdropConfig, 'Backdrop');
387 }
388 }
389
390 // Actual processor
391 if (isset($_POST['go']) && !$req->hasErrors() && !$dbReq->hasErrors()) {
392 // Confirm before reinstalling
393 if (!isset($_POST['force_reinstall']) && $alreadyInstalled) {
394 include $installDirPath . 'template.html';
395 }
396 else {
397 $inst = new Installer();
398 $inst->install($_POST);
399 }
400
401 // Show the config form
402 }
403 else {
404 include $installDirPath . 'template.html';
405 }
406
407 /**
408 * This class checks requirements
409 * Each of the requireXXX functions takes an argument which gives a user description of the test. It's an array
410 * of 3 parts:
411 * $description[0] - The test category
412 * $description[1] - The test title
413 * $description[2] - The test error to show, if it goes wrong
414 */
415 class InstallRequirements {
416 var $errors, $warnings, $tests, $conn;
417
418 // @see CRM_Upgrade_Form::MINIMUM_THREAD_STACK
419 const MINIMUM_THREAD_STACK = 192;
420
421 /**
422 * Just check that the database configuration is okay.
423 * @param $databaseConfig
424 * @param $dbName
425 */
426 public function checkdatabase($databaseConfig, $dbName) {
427 if ($this->requireFunction('mysqli_connect',
428 array(
429 ts("PHP Configuration"),
430 ts("MySQL support"),
431 ts("MySQL support not included in PHP."),
432 )
433 )
434 ) {
435 $this->requireMySQLServer($databaseConfig['server'],
436 array(
437 ts("MySQL %1 Configuration", array(1 => $dbName)),
438 ts("Does the server exist?"),
439 ts("Can't find the a MySQL server on '%1'.", array(1 => $databaseConfig['server'])),
440 $databaseConfig['server'],
441 )
442 );
443 if ($this->requireMysqlConnection($databaseConfig['server'],
444 $databaseConfig['username'],
445 $databaseConfig['password'],
446 array(
447 ts("MySQL %1 Configuration", array(1 => $dbName)),
448 ts("Are the access credentials correct?"),
449 ts("That username/password doesn't work"),
450 )
451 )
452 ) {
453 @$this->requireMySQLVersion("5.1",
454 array(
455 ts("MySQL %1 Configuration", array(1 => $dbName)),
456 ts("MySQL version at least %1", array(1 => '5.1')),
457 ts("MySQL version %1 or higher is required, you are running MySQL %2.", array(1 => '5.1', 2 => mysqli_get_server_info($this->conn))),
458 ts("MySQL %1", array(1 => mysqli_get_server_info($this->conn))),
459 )
460 );
461 $this->requireMySQLAutoIncrementIncrementOne($databaseConfig['server'],
462 $databaseConfig['username'],
463 $databaseConfig['password'],
464 array(
465 ts("MySQL %1 Configuration", array(1 => $dbName)),
466 ts("Is auto_increment_increment set to 1"),
467 ts("An auto_increment_increment value greater than 1 is not currently supported. Please see issue CRM-7923 for further details and potential workaround."),
468 )
469 );
470 $testDetails = array(
471 ts("MySQL %1 Configuration", array(1 => $dbName)),
472 ts("Is the provided database name valid?"),
473 ts("The database name provided is not valid. Please use only 0-9, a-z, A-Z, _ and - as characters in the name."),
474 );
475 if (!CRM_Core_DAO::requireSafeDBName($databaseConfig['database'])) {
476 $this->error($testDetails);
477 return FALSE;
478 }
479 else {
480 $this->testing($testDetails);
481 }
482 $this->requireMySQLThreadStack($databaseConfig['server'],
483 $databaseConfig['username'],
484 $databaseConfig['password'],
485 $databaseConfig['database'],
486 self::MINIMUM_THREAD_STACK,
487 array(
488 ts("MySQL %1 Configuration", array(1 => $dbName)),
489 ts("Does MySQL thread_stack meet minimum (%1k)", array(1 => self::MINIMUM_THREAD_STACK)),
490 "",
491 // "The MySQL thread_stack does not meet minimum " . CRM_Upgrade_Form::MINIMUM_THREAD_STACK . "k. Please update thread_stack in my.cnf.",
492 )
493 );
494 }
495 $onlyRequire = ($dbName == 'Drupal' || $dbName == 'Backdrop') ? TRUE : FALSE;
496 $this->requireDatabaseOrCreatePermissions(
497 $databaseConfig['server'],
498 $databaseConfig['username'],
499 $databaseConfig['password'],
500 $databaseConfig['database'],
501 array(
502 ts("MySQL %1 Configuration", array(1 => $dbName)),
503 ts("Can I access/create the database?"),
504 ts("I can't create new databases and the database '%1' doesn't exist.", array(1 => $databaseConfig['database'])),
505 ),
506 $onlyRequire
507 );
508 if ($dbName != 'Drupal' && $dbName != 'Backdrop') {
509 $this->requireMySQLInnoDB($databaseConfig['server'],
510 $databaseConfig['username'],
511 $databaseConfig['password'],
512 $databaseConfig['database'],
513 array(
514 ts("MySQL %1 Configuration", array(1 => $dbName)),
515 ts("Can I access/create InnoDB tables in the database?"),
516 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."),
517 )
518 );
519 $this->requireMySQLTempTables($databaseConfig['server'],
520 $databaseConfig['username'],
521 $databaseConfig['password'],
522 $databaseConfig['database'],
523 array(
524 ts("MySQL %1 Configuration", array(1 => $dbName)),
525 ts('Can I create temporary tables in the database?'),
526 ts('Unable to create temporary tables. This MySQL user is missing the CREATE TEMPORARY TABLES privilege.'),
527 )
528 );
529 $this->requireMySQLLockTables($databaseConfig['server'],
530 $databaseConfig['username'],
531 $databaseConfig['password'],
532 $databaseConfig['database'],
533 array(
534 ts("MySQL %1 Configuration", array(1 => $dbName)),
535 ts('Can I create lock tables in the database?'),
536 ts('Unable to lock tables. This MySQL user is missing the LOCK TABLES privilege.'),
537 )
538 );
539 $this->requireMySQLTrigger($databaseConfig['server'],
540 $databaseConfig['username'],
541 $databaseConfig['password'],
542 $databaseConfig['database'],
543 array(
544 ts("MySQL %1 Configuration", array(1 => $dbName)),
545 ts('Can I create triggers in the database?'),
546 ts('Unable to create triggers. This MySQL user is missing the CREATE TRIGGERS privilege.'),
547 )
548 );
549 }
550 }
551 }
552
553 /**
554 * Connect via mysqli.
555 *
556 * This is exactly the same as mysqli_connect(), except that it accepts
557 * the port as part of the `$host`.
558 *
559 * @param string $host
560 * Ex: 'localhost', 'localhost:3307', '127.0.0.1:3307', '[::1]', '[::1]:3307'.
561 * @param string $username
562 * @param string $password
563 * @param string $database
564 * @return \mysqli
565 */
566 protected function connect($host, $username, $password, $database = '') {
567 $hostParts = explode(':', $host);
568 if (count($hostParts) > 1 && strrpos($host, ']') !== strlen($host) - 1) {
569 $port = array_pop($hostParts);
570 $host = implode(':', $hostParts);
571 }
572 else {
573 $port = NULL;
574 }
575 $conn = @mysqli_connect($host, $username, $password, $database, $port);
576 return $conn;
577 }
578
579 /**
580 * Check everything except the database.
581 */
582 public function check() {
583 global $crmPath, $installType;
584
585 $this->errors = NULL;
586
587 // See also: CRM_Upgrade_Incremental_General::MIN_INSTALL_PHP_VER
588 $this->requirePHPVersion('5.3.4', array(
589 ts("PHP Configuration"),
590 ts("PHP5 installed"),
591 NULL,
592 ts("PHP version %1", array(1 => phpversion())),
593 ));
594
595 // Check that we can identify the root folder successfully
596 $this->requireFile($crmPath . CIVICRM_DIRECTORY_SEPARATOR . 'README.md',
597 array(
598 ts("File permissions"),
599 ts("Does the webserver know where files are stored?"),
600 ts("The webserver isn't letting me identify where files are stored."),
601 $this->getBaseDir(),
602 ),
603 TRUE
604 );
605
606 // CRM-6485: make sure the path does not contain PATH_SEPARATOR, as we don’t know how to escape it
607 $this->requireNoPathSeparator(
608 array(
609 ts("File permissions"),
610 ts('Does the CiviCRM path contain PATH_SEPARATOR?'),
611 ts('The path %1 contains PATH_SEPARATOR (the %2 character).', array(1 => $this->getBaseDir(), 2 => PATH_SEPARATOR)),
612 $this->getBaseDir(),
613 )
614 );
615
616 $requiredDirectories = array('CRM', 'packages', 'templates', 'js', 'api', 'i', 'sql');
617 foreach ($requiredDirectories as $dir) {
618 $this->requireFile($crmPath . CIVICRM_DIRECTORY_SEPARATOR . $dir,
619 array(
620 ts("File permissions"),
621 ts("Folder '%1' exists?", array(1 => $dir)),
622 ts("There is no '%1' folder.", array(1 => $dir)),
623 ), TRUE
624 );
625 }
626
627 $configIDSiniDir = NULL;
628 global $cmsPath;
629 $siteDir = getSiteDir($cmsPath, $_SERVER['SCRIPT_FILENAME']);
630 if ($installType == 'drupal') {
631
632 // make sure that we can write to sites/default and files/
633 $writableDirectories = array(
634 $cmsPath . CIVICRM_DIRECTORY_SEPARATOR .
635 'sites' . CIVICRM_DIRECTORY_SEPARATOR .
636 $siteDir . CIVICRM_DIRECTORY_SEPARATOR .
637 'files',
638 $cmsPath . CIVICRM_DIRECTORY_SEPARATOR .
639 'sites' . CIVICRM_DIRECTORY_SEPARATOR .
640 $siteDir,
641 );
642 }
643 elseif ($installType == 'backdrop') {
644
645 // make sure that we can write to sites/default and files/
646 $writableDirectories = array(
647 $cmsPath . CIVICRM_DIRECTORY_SEPARATOR .
648 'files',
649 $cmsPath,
650 );
651 }
652 elseif ($installType == 'wordpress') {
653 // make sure that we can write to uploads/civicrm/
654 $upload_dir = wp_upload_dir();
655 $files_dirname = $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'civicrm';
656 if (!file_exists($files_dirname)) {
657 wp_mkdir_p($files_dirname);
658 }
659 $writableDirectories = array($files_dirname);
660 }
661
662 foreach ($writableDirectories as $dir) {
663 $dirName = CIVICRM_WINDOWS ? $dir : CIVICRM_DIRECTORY_SEPARATOR . $dir;
664 $testDetails = array(
665 ts("File permissions"),
666 ts("Is the %1 folder writeable?", array(1 => $dir)),
667 NULL,
668 );
669 $this->requireWriteable($dirName, $testDetails, TRUE);
670 }
671
672 //check for Config.IDS.ini, file may exist in re-install
673 $configIDSiniDir = array($cmsPath, 'sites', $siteDir, 'files', 'civicrm', 'upload', 'Config.IDS.ini');
674
675 if (is_array($configIDSiniDir) && !empty($configIDSiniDir)) {
676 $configIDSiniFile = implode(CIVICRM_DIRECTORY_SEPARATOR, $configIDSiniDir);
677 if (file_exists($configIDSiniFile)) {
678 unlink($configIDSiniFile);
679 }
680 }
681
682 // Check for rewriting
683 if (isset($_SERVER['SERVER_SOFTWARE'])) {
684 $webserver = strip_tags(trim($_SERVER['SERVER_SOFTWARE']));
685 }
686 elseif (isset($_SERVER['SERVER_SIGNATURE'])) {
687 $webserver = strip_tags(trim($_SERVER['SERVER_SIGNATURE']));
688 }
689
690 if ($webserver == '') {
691 $webserver = ts("I can't tell what webserver you are running");
692 }
693
694 // Check for $_SERVER configuration
695 $this->requireServerVariables(array('SCRIPT_NAME', 'HTTP_HOST', 'SCRIPT_FILENAME'), array(
696 ts("Webserver config"),
697 ts("Recognised webserver"),
698 ts("You seem to be using an unsupported webserver. The server variables SCRIPT_NAME, HTTP_HOST, SCRIPT_FILENAME need to be set."),
699 ));
700
701 // Check for MySQL support
702 $this->requireFunction('mysqli_connect', array(
703 ts("PHP Configuration"),
704 ts("MySQL support"),
705 ts("MySQL support not included in PHP."),
706 ));
707
708 // Check for JSON support
709 $this->requireFunction('json_encode', array(
710 ts("PHP Configuration"),
711 ts("JSON support"),
712 ts("JSON support not included in PHP."),
713 ));
714
715 // Check for xcache_isset and emit warning if exists
716 $this->checkXCache(array(
717 ts("PHP Configuration"),
718 ts("XCache compatibility"),
719 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."),
720 ));
721
722 // Check memory allocation
723 $this->requireMemory(32 * 1024 * 1024,
724 64 * 1024 * 1024,
725 array(
726 ts("PHP Configuration"),
727 ts("Memory allocated (PHP config option 'memory_limit')"),
728 ts("CiviCRM needs a minimum of %1 MB allocated to PHP, but recommends %2 MB.", array(1 => 32, 2 => 64)),
729 ini_get("memory_limit"),
730 )
731 );
732
733 return $this->errors;
734 }
735
736 /**
737 * @param $min
738 * @param $recommended
739 * @param $testDetails
740 */
741 public function requireMemory($min, $recommended, $testDetails) {
742 $this->testing($testDetails);
743 $mem = $this->getPHPMemory();
744
745 if ($mem < $min && $mem > 0) {
746 $testDetails[2] .= " " . ts("You only have %1 allocated", array(1 => ini_get("memory_limit")));
747 $this->error($testDetails);
748 }
749 elseif ($mem < $recommended && $mem > 0) {
750 $testDetails[2] .= " " . ts("You only have %1 allocated", array(1 => ini_get("memory_limit")));
751 $this->warning($testDetails);
752 }
753 elseif ($mem == 0) {
754 $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));
755 $this->warning($testDetails);
756 }
757 }
758
759 /**
760 * @return float
761 */
762 public function getPHPMemory() {
763 $memString = ini_get("memory_limit");
764
765 switch (strtolower(substr($memString, -1))) {
766 case "k":
767 return round(substr($memString, 0, -1) * 1024);
768
769 case "m":
770 return round(substr($memString, 0, -1) * 1024 * 1024);
771
772 case "g":
773 return round(substr($memString, 0, -1) * 1024 * 1024 * 1024);
774
775 default:
776 return round($memString);
777 }
778 }
779
780 public function listErrors() {
781 if ($this->errors) {
782 echo "<p>" . ts("The following problems are preventing me from installing CiviCRM:") . "</p>";
783 foreach ($this->errors as $error) {
784 echo "<li>" . htmlentities($error) . "</li>";
785 }
786 }
787 }
788
789 /**
790 * @param null $section
791 */
792 public function showTable($section = NULL) {
793 if ($section) {
794 $tests = $this->tests[$section];
795 echo "<table class=\"testResults\" width=\"100%\">";
796 foreach ($tests as $test => $result) {
797 echo "<tr class=\"$result[0]\"><td>$test</td><td>" . nl2br(htmlentities($result[1])) . "</td></tr>";
798 }
799 echo "</table>";
800 }
801 else {
802 foreach ($this->tests as $section => $tests) {
803 echo "<h3>$section</h3>";
804 echo "<table class=\"testResults\" width=\"100%\">";
805
806 foreach ($tests as $test => $result) {
807 echo "<tr class=\"$result[0]\"><td>$test</td><td>" . nl2br(htmlentities($result[1])) . "</td></tr>";
808 }
809 echo "</table>";
810 }
811 }
812 }
813
814 /**
815 * @param string $funcName
816 * @param $testDetails
817 *
818 * @return bool
819 */
820 public function requireFunction($funcName, $testDetails) {
821 $this->testing($testDetails);
822
823 if (!function_exists($funcName)) {
824 $this->error($testDetails);
825 return FALSE;
826 }
827 else {
828 return TRUE;
829 }
830 }
831
832 /**
833 * @param $testDetails
834 */
835 public function checkXCache($testDetails) {
836 if (function_exists('xcache_isset') &&
837 ini_get('xcache.size') > 0
838 ) {
839 $this->testing($testDetails);
840 $this->warning($testDetails);
841 }
842 }
843
844 /**
845 * @param $minVersion
846 * @param $testDetails
847 * @param null $maxVersion
848 */
849 public function requirePHPVersion($minVersion, $testDetails, $maxVersion = NULL) {
850
851 $this->testing($testDetails);
852
853 $phpVersion = phpversion();
854 $aboveMinVersion = version_compare($phpVersion, $minVersion) >= 0;
855 $belowMaxVersion = $maxVersion ? version_compare($phpVersion, $maxVersion) < 0 : TRUE;
856
857 if ($aboveMinVersion && $belowMaxVersion) {
858 if (version_compare(phpversion(), CRM_Upgrade_Incremental_General::MIN_RECOMMENDED_PHP_VER) < 0) {
859 $testDetails[2] = ts('This webserver is running an outdated version of PHP (%1). It is strongly recommended to upgrade to PHP %2 or later, as older versions can present a security risk.', array(
860 1 => phpversion(),
861 2 => CRM_Upgrade_Incremental_General::MIN_RECOMMENDED_PHP_VER,
862 ));
863 $this->warning($testDetails);
864 }
865 return TRUE;
866 }
867
868 if (!$testDetails[2]) {
869 if (!$aboveMinVersion) {
870 $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));
871 }
872 else {
873 $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));
874 }
875 }
876
877 $this->error($testDetails);
878 }
879
880 /**
881 * @param string $filename
882 * @param $testDetails
883 * @param bool $absolute
884 */
885 public function requireFile($filename, $testDetails, $absolute = FALSE) {
886 $this->testing($testDetails);
887 if (!$absolute) {
888 $filename = $this->getBaseDir() . $filename;
889 }
890 if (!file_exists($filename)) {
891 $testDetails[2] .= " (" . ts("file '%1' not found", array(1 => $filename)) . ')';
892 $this->error($testDetails);
893 }
894 }
895
896 /**
897 * @param $testDetails
898 */
899 public function requireNoPathSeparator($testDetails) {
900 $this->testing($testDetails);
901 if (substr_count($this->getBaseDir(), PATH_SEPARATOR)) {
902 $this->error($testDetails);
903 }
904 }
905
906 /**
907 * @param string $filename
908 * @param $testDetails
909 */
910 public function requireNoFile($filename, $testDetails) {
911 $this->testing($testDetails);
912 $filename = $this->getBaseDir() . $filename;
913 if (file_exists($filename)) {
914 $testDetails[2] .= " (" . ts("file '%1' found", array(1 => $filename)) . ")";
915 $this->error($testDetails);
916 }
917 }
918
919 /**
920 * @param string $filename
921 * @param $testDetails
922 */
923 public function moveFileOutOfTheWay($filename, $testDetails) {
924 $this->testing($testDetails);
925 $filename = $this->getBaseDir() . $filename;
926 if (file_exists($filename)) {
927 if (file_exists("$filename.bak")) {
928 rm("$filename.bak");
929 }
930 rename($filename, "$filename.bak");
931 }
932 }
933
934 /**
935 * @param string $filename
936 * @param $testDetails
937 * @param bool $absolute
938 */
939 public function requireWriteable($filename, $testDetails, $absolute = FALSE) {
940 $this->testing($testDetails);
941 if (!$absolute) {
942 $filename = $this->getBaseDir() . $filename;
943 }
944
945 if (!is_writable($filename)) {
946 $name = NULL;
947 if (function_exists('posix_getpwuid')) {
948 $user = posix_getpwuid(posix_geteuid());
949 $name = '- ' . $user['name'] . ' -';
950 }
951
952 if (!isset($testDetails[2])) {
953 $testDetails[2] = NULL;
954 }
955 $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";
956 $this->error($testDetails);
957 }
958 }
959
960 /**
961 * @param string $moduleName
962 * @param $testDetails
963 */
964 public function requireApacheModule($moduleName, $testDetails) {
965 $this->testing($testDetails);
966 if (!in_array($moduleName, apache_get_modules())) {
967 $this->error($testDetails);
968 }
969 }
970
971 /**
972 * @param $server
973 * @param string $username
974 * @param $password
975 * @param $testDetails
976 */
977 public function requireMysqlConnection($server, $username, $password, $testDetails) {
978 $this->testing($testDetails);
979 $this->conn = $this->connect($server, $username, $password);
980
981 if ($this->conn) {
982 return TRUE;
983 }
984 else {
985 $testDetails[2] .= ": " . mysqli_connect_error();
986 $this->error($testDetails);
987 }
988 }
989
990 /**
991 * @param $server
992 * @param $testDetails
993 */
994 public function requireMySQLServer($server, $testDetails) {
995 $this->testing($testDetails);
996 $conn = $this->connect($server, NULL, NULL);
997
998 if ($conn || mysqli_connect_errno() < 2000) {
999 return TRUE;
1000 }
1001 else {
1002 $testDetails[2] .= ": " . mysqli_connect_error();
1003 $this->error($testDetails);
1004 }
1005 }
1006
1007 /**
1008 * @param $version
1009 * @param $testDetails
1010 */
1011 public function requireMySQLVersion($version, $testDetails) {
1012 $this->testing($testDetails);
1013
1014 if (!mysqli_get_server_info($this->conn)) {
1015 $testDetails[2] = ts('Cannot determine the version of MySQL installed. Please ensure at least version %1 is installed.', array(1 => $version));
1016 $this->warning($testDetails);
1017 }
1018 else {
1019 list($majorRequested, $minorRequested) = explode('.', $version);
1020 list($majorHas, $minorHas) = explode('.', mysqli_get_server_info($this->conn));
1021
1022 if (($majorHas > $majorRequested) || ($majorHas == $majorRequested && $minorHas >= $minorRequested)) {
1023 return TRUE;
1024 }
1025 else {
1026 $testDetails[2] .= "{$majorHas}.{$minorHas}.";
1027 $this->error($testDetails);
1028 }
1029 }
1030 }
1031
1032 /**
1033 * @param $server
1034 * @param string $username
1035 * @param $password
1036 * @param $database
1037 * @param $testDetails
1038 */
1039 public function requireMySQLInnoDB($server, $username, $password, $database, $testDetails) {
1040 $this->testing($testDetails);
1041 $conn = $this->connect($server, $username, $password);
1042 if (!$conn) {
1043 $testDetails[2] .= ' ' . ts("Could not determine if MySQL has InnoDB support. Assuming no.");
1044 $this->error($testDetails);
1045 return;
1046 }
1047
1048 $innodb_support = FALSE;
1049 $result = mysqli_query($conn, "SHOW ENGINES");
1050 while ($values = mysqli_fetch_array($result)) {
1051 if ($values['Engine'] == 'InnoDB') {
1052 if (strtolower($values['Support']) == 'yes' ||
1053 strtolower($values['Support']) == 'default'
1054 ) {
1055 $innodb_support = TRUE;
1056 }
1057 }
1058 }
1059 if ($innodb_support) {
1060 $testDetails[3] = ts('MySQL server does have InnoDB support');
1061 }
1062 else {
1063 $testDetails[2] .= ' ' . ts('Could not determine if MySQL has InnoDB support. Assuming no');
1064 }
1065 }
1066
1067 /**
1068 * @param $server
1069 * @param string $username
1070 * @param $password
1071 * @param $database
1072 * @param $testDetails
1073 */
1074 public function requireMySQLTempTables($server, $username, $password, $database, $testDetails) {
1075 $this->testing($testDetails);
1076 $conn = $this->connect($server, $username, $password);
1077 if (!$conn) {
1078 $testDetails[2] = ts('Could not login to the database.');
1079 $this->error($testDetails);
1080 return;
1081 }
1082
1083 if (!@mysqli_select_db($conn, $database)) {
1084 $testDetails[2] = ts('Could not select the database.');
1085 $this->error($testDetails);
1086 return;
1087 }
1088
1089 $result = mysqli_query($conn, 'CREATE TEMPORARY TABLE civicrm_install_temp_table_test (test text)');
1090 if (!$result) {
1091 $testDetails[2] = ts('Could not create a temp table.');
1092 $this->error($testDetails);
1093 }
1094 $result = mysqli_query($conn, 'DROP TEMPORARY TABLE civicrm_install_temp_table_test');
1095 }
1096
1097 /**
1098 * @param $server
1099 * @param string $username
1100 * @param $password
1101 * @param $database
1102 * @param $testDetails
1103 */
1104 public function requireMySQLTrigger($server, $username, $password, $database, $testDetails) {
1105 $this->testing($testDetails);
1106 $conn = $this->connect($server, $username, $password);
1107 if (!$conn) {
1108 $testDetails[2] = ts('Could not login to the database.');
1109 $this->error($testDetails);
1110 return;
1111 }
1112
1113 if (!@mysqli_select_db($conn, $database)) {
1114 $testDetails[2] = ts('Could not select the database.');
1115 $this->error($testDetails);
1116 return;
1117 }
1118
1119 $result = mysqli_query($conn, 'CREATE TABLE civicrm_install_temp_table_test (test text)');
1120 if (!$result) {
1121 $testDetails[2] = ts('Could not create a table in the database.');
1122 $this->error($testDetails);
1123 }
1124
1125 $result = mysqli_query($conn, 'CREATE TRIGGER civicrm_install_temp_table_test_trigger BEFORE INSERT ON civicrm_install_temp_table_test FOR EACH ROW BEGIN END');
1126 if (!$result) {
1127 mysqli_query($conn, 'DROP TABLE civicrm_install_temp_table_test');
1128 $testDetails[2] = ts('Could not create a database trigger.');
1129 $this->error($testDetails);
1130 }
1131
1132 mysqli_query($conn, 'DROP TRIGGER civicrm_install_temp_table_test_trigger');
1133 mysqli_query($conn, 'DROP TABLE civicrm_install_temp_table_test');
1134 }
1135
1136
1137 /**
1138 * @param $server
1139 * @param string $username
1140 * @param $password
1141 * @param $database
1142 * @param $testDetails
1143 */
1144 public function requireMySQLLockTables($server, $username, $password, $database, $testDetails) {
1145 $this->testing($testDetails);
1146 $conn = $this->connect($server, $username, $password);
1147 if (!$conn) {
1148 $testDetails[2] = ts('Could not connect to the database server.');
1149 $this->error($testDetails);
1150 return;
1151 }
1152
1153 if (!@mysqli_select_db($conn, $database)) {
1154 $testDetails[2] = ts('Could not select the database.');
1155 $this->error($testDetails);
1156 return;
1157 }
1158
1159 $result = mysqli_query($conn, 'CREATE TEMPORARY TABLE civicrm_install_temp_table_test (test text)');
1160 if (!$result) {
1161 $testDetails[2] = ts('Could not create a table in the database.');
1162 $this->error($testDetails);
1163 return;
1164 }
1165
1166 $result = mysqli_query($conn, 'LOCK TABLES civicrm_install_temp_table_test WRITE');
1167 if (!$result) {
1168 $testDetails[2] = ts('Could not obtain a write lock for the database table.');
1169 $this->error($testDetails);
1170 $result = mysqli_query($conn, 'DROP TEMPORARY TABLE civicrm_install_temp_table_test');
1171 return;
1172 }
1173
1174 $result = mysqli_query($conn, 'UNLOCK TABLES');
1175 if (!$result) {
1176 $testDetails[2] = ts('Could not release the lock for the database table.');
1177 $this->error($testDetails);
1178 $result = mysqli_query($conn, 'DROP TEMPORARY TABLE civicrm_install_temp_table_test');
1179 return;
1180 }
1181
1182 $result = mysqli_query($conn, 'DROP TEMPORARY TABLE civicrm_install_temp_table_test');
1183 }
1184
1185 /**
1186 * @param $server
1187 * @param string $username
1188 * @param $password
1189 * @param $testDetails
1190 */
1191 public function requireMySQLAutoIncrementIncrementOne($server, $username, $password, $testDetails) {
1192 $this->testing($testDetails);
1193 $conn = $this->connect($server, $username, $password);
1194 if (!$conn) {
1195 $testDetails[2] = ts('Could not connect to the database server.');
1196 $this->error($testDetails);
1197 return;
1198 }
1199
1200 $result = mysqli_query($conn, "SHOW variables like 'auto_increment_increment'");
1201 if (!$result) {
1202 $testDetails[2] = ts('Could not query database server variables.');
1203 $this->error($testDetails);
1204 return;
1205 }
1206 else {
1207 $values = mysqli_fetch_row($result);
1208 if ($values[1] == 1) {
1209 $testDetails[3] = ts('MySQL server auto_increment_increment is 1');
1210 }
1211 else {
1212 $this->error($testDetails);
1213 }
1214 }
1215 }
1216
1217 /**
1218 * @param $server
1219 * @param string $username
1220 * @param $password
1221 * @param $database
1222 * @param $minValueKB
1223 * @param $testDetails
1224 */
1225 public function requireMySQLThreadStack($server, $username, $password, $database, $minValueKB, $testDetails) {
1226 $this->testing($testDetails);
1227 $conn = $this->connect($server, $username, $password);
1228 if (!$conn) {
1229 $testDetails[2] = ts('Could not connect to the database server.');
1230 $this->error($testDetails);
1231 return;
1232 }
1233
1234 if (!@mysqli_select_db($conn, $database)) {
1235 $testDetails[2] = ts('Could not select the database.');
1236 $this->error($testDetails);
1237 return;
1238 }
1239
1240 $result = mysqli_query($conn, "SHOW VARIABLES LIKE 'thread_stack'"); // bytes => kb
1241 if (!$result) {
1242 $testDetails[2] = ts('Could not get information about the thread_stack of the database.');
1243 $this->error($testDetails);
1244 }
1245 else {
1246 $values = mysqli_fetch_row($result);
1247 if ($values[1] < (1024 * $minValueKB)) {
1248 $testDetails[2] = ts('MySQL "thread_stack" is %1 kb', array(1 => ($values[1] / 1024)));
1249 $this->error($testDetails);
1250 }
1251 }
1252 }
1253
1254 /**
1255 * @param $server
1256 * @param string $username
1257 * @param $password
1258 * @param $database
1259 * @param $testDetails
1260 * @param bool $onlyRequire
1261 */
1262 public function requireDatabaseOrCreatePermissions(
1263 $server,
1264 $username,
1265 $password,
1266 $database,
1267 $testDetails,
1268 $onlyRequire = FALSE
1269 ) {
1270 $this->testing($testDetails);
1271 $conn = $this->connect($server, $username, $password);
1272
1273 $okay = NULL;
1274 if (@mysqli_select_db($conn, $database)) {
1275 $okay = "Database '$database' exists";
1276 }
1277 elseif ($onlyRequire) {
1278 $testDetails[2] = ts("The database: '%1' does not exist.", array(1 => $database));
1279 $this->error($testDetails);
1280 return;
1281 }
1282 else {
1283 $query = sprintf("CREATE DATABASE %s", mysqli_real_escape_string($conn, $database));
1284 if (@mysqli_query($conn, $query)) {
1285 $okay = ts("Able to create a new database.");
1286 }
1287 else {
1288 $testDetails[2] .= " (" . ts("user '%1' doesn't have CREATE DATABASE permissions.", array(1 => $username)) . ")";
1289 $this->error($testDetails);
1290 return;
1291 }
1292 }
1293
1294 if ($okay) {
1295 $testDetails[3] = $okay;
1296 $this->testing($testDetails);
1297 }
1298 }
1299
1300 /**
1301 * @param $varNames
1302 * @param $errorMessage
1303 */
1304 public function requireServerVariables($varNames, $errorMessage) {
1305 //$this->testing($testDetails);
1306 foreach ($varNames as $varName) {
1307 if (!$_SERVER[$varName]) {
1308 $missing[] = '$_SERVER[' . $varName . ']';
1309 }
1310 }
1311 if (!isset($missing)) {
1312 return TRUE;
1313 }
1314 else {
1315 $testDetails[2] = " (" . ts('the following PHP variables are missing: %1', array(1 => implode(", ", $missing))) . ")";
1316 $this->error($testDetails);
1317 }
1318 }
1319
1320 /**
1321 * @param $testDetails
1322 *
1323 * @return bool
1324 */
1325 public function isRunningApache($testDetails) {
1326 $this->testing($testDetails);
1327 if (function_exists('apache_get_modules') || stristr($_SERVER['SERVER_SIGNATURE'], 'Apache')) {
1328 return TRUE;
1329 }
1330
1331 $this->warning($testDetails);
1332 return FALSE;
1333 }
1334
1335 /**
1336 * @return string
1337 */
1338 public function getBaseDir() {
1339 return dirname($_SERVER['SCRIPT_FILENAME']) . CIVICRM_DIRECTORY_SEPARATOR;
1340 }
1341
1342 /**
1343 * @param $testDetails
1344 */
1345 public function testing($testDetails) {
1346 if (!$testDetails) {
1347 return;
1348 }
1349
1350 $section = $testDetails[0];
1351 $test = $testDetails[1];
1352
1353 $message = ts("OK");
1354 if (isset($testDetails[3])) {
1355 $message .= " ($testDetails[3])";
1356 }
1357
1358 $this->tests[$section][$test] = array("good", $message);
1359 }
1360
1361 /**
1362 * @param $testDetails
1363 */
1364 public function error($testDetails) {
1365 $section = $testDetails[0];
1366 $test = $testDetails[1];
1367
1368 $this->tests[$section][$test] = array("error", $testDetails[2]);
1369 $this->errors[] = $testDetails;
1370 }
1371
1372 /**
1373 * @param $testDetails
1374 */
1375 public function warning($testDetails) {
1376 $section = $testDetails[0];
1377 $test = $testDetails[1];
1378
1379 $this->tests[$section][$test] = array("warning", $testDetails[2]);
1380 $this->warnings[] = $testDetails;
1381 }
1382
1383 /**
1384 * @return int
1385 */
1386 public function hasErrors() {
1387 return count($this->errors);
1388 }
1389
1390 /**
1391 * @return int
1392 */
1393 public function hasWarnings() {
1394 return count($this->warnings);
1395 }
1396
1397 }
1398
1399 /**
1400 * Class Installer
1401 */
1402 class Installer extends InstallRequirements {
1403 /**
1404 * @param $server
1405 * @param $username
1406 * @param $password
1407 * @param $database
1408 */
1409 public function createDatabaseIfNotExists($server, $username, $password, $database) {
1410 $conn = $this->connect($server, $username, $password);
1411
1412 if (@mysqli_select_db($conn, $database)) {
1413 // skip if database already present
1414 return;
1415 }
1416 $query = sprintf("CREATE DATABASE %s", mysqli_real_escape_string($conn, $database));
1417 if (@mysqli_query($conn, $query)) {
1418 }
1419 else {
1420 $errorTitle = ts("Oops! Could not create database %1", array(1 => $database));
1421 $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.");
1422 errorDisplayPage($errorTitle, $errorMsg);
1423 }
1424 }
1425
1426 /**
1427 * @param $config
1428 *
1429 * @return mixed
1430 */
1431 public function install($config) {
1432 global $installDirPath;
1433
1434 // create database if does not exists
1435 $this->createDatabaseIfNotExists($config['mysql']['server'],
1436 $config['mysql']['username'],
1437 $config['mysql']['password'],
1438 $config['mysql']['database']
1439 );
1440
1441 global $installDirPath;
1442
1443 // Build database
1444 require_once $installDirPath . 'civicrm.php';
1445 civicrm_main($config);
1446
1447 if (!$this->errors) {
1448 global $installType, $installURLPath;
1449
1450 $registerSiteURL = "https://civicrm.org/register-site";
1451 $commonOutputMessage
1452 = "<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>"
1453 . "<li>" . ts("We have integrated KCFinder with CKEditor and TinyMCE. This allows a user to upload images. All uploaded images are public.") . "</li>";
1454
1455 $output = NULL;
1456
1457 if (
1458 $installType == 'drupal' &&
1459 version_compare(VERSION, '7.0-rc1') >= 0
1460 ) {
1461
1462 // clean output
1463 @ob_clean();
1464
1465 $output .= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
1466 $output .= '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">';
1467 $output .= '<head>';
1468 $output .= '<title>' . ts('CiviCRM Installed') . '</title>';
1469 $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
1470 $output .= '<link rel="stylesheet" type="text/css" href="template.css" />';
1471 $output .= '</head>';
1472 $output .= '<body>';
1473 $output .= '<div style="padding: 1em;"><p class="good">' . ts('CiviCRM has been successfully installed') . '</p>';
1474 $output .= '<ul>';
1475
1476 $drupalURL = civicrm_cms_base();
1477 $drupalPermissionsURL = "{$drupalURL}index.php?q=admin/people/permissions";
1478 $drupalURL .= "index.php?q=civicrm/admin/configtask&reset=1";
1479
1480 $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>";
1481 $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>";
1482 $output .= $commonOutputMessage;
1483
1484 // automatically enable CiviCRM module once it is installed successfully.
1485 // so we need to Bootstrap Drupal, so that we can call drupal hooks.
1486 global $cmsPath, $crmPath;
1487
1488 // relative / abosolute paths are not working for drupal, hence using chdir()
1489 chdir($cmsPath);
1490
1491 // Force the re-initialisation of the config singleton on the next call
1492 // since so far, we had used the Config object without loading the DB.
1493 $c = CRM_Core_Config::singleton(FALSE);
1494 $c->free();
1495
1496 include_once "./includes/bootstrap.inc";
1497 include_once "./includes/unicode.inc";
1498
1499 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
1500
1501 // prevent session information from being saved.
1502 drupal_save_session(FALSE);
1503
1504 // Force the current user to anonymous.
1505 $original_user = $GLOBALS['user'];
1506 $GLOBALS['user'] = drupal_anonymous_user();
1507
1508 // explicitly setting error reporting, since we cannot handle drupal related notices
1509 error_reporting(1);
1510
1511 // rebuild modules, so that civicrm is added
1512 system_rebuild_module_data();
1513
1514 // now enable civicrm module.
1515 module_enable(array('civicrm', 'civicrmtheme'));
1516
1517 // SystemInstallEvent will be called from here with the first call of CRM_Core_Config,
1518 // which calls Core_BAO_ConfigSetting::applyLocale(), who will default to calling
1519 // Civi::settings()->get('lcMessages');
1520 // Therefore, we need to pass the seedLanguage before that.
1521 global $civicrm_setting;
1522 $civicrm_setting['domain']['lcMessages'] = $config['seedLanguage'];
1523
1524 // clear block, page, theme, and hook caches
1525 drupal_flush_all_caches();
1526
1527 //add basic drupal permissions
1528 civicrm_install_set_drupal_perms();
1529
1530 // restore the user.
1531 $GLOBALS['user'] = $original_user;
1532 drupal_save_session(TRUE);
1533
1534 $output .= '</ul>';
1535 $output .= '</div>';
1536 $output .= '</body>';
1537 $output .= '</html>';
1538 echo $output;
1539 }
1540 elseif (
1541 $installType == 'backdrop'
1542 ) {
1543
1544 // clean output
1545 @ob_clean();
1546
1547 $output .= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
1548 $output .= '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">';
1549 $output .= '<head>';
1550 $output .= '<title>' . ts('CiviCRM Installed') . '</title>';
1551 $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
1552 $output .= '<link rel="stylesheet" type="text/css" href="template.css" />';
1553 $output .= '</head>';
1554 $output .= '<body>';
1555 $output .= '<div style="padding: 1em;"><p class="good">' . ts('CiviCRM has been successfully installed') . '</p>';
1556 $output .= '<ul>';
1557
1558 $backdropURL = civicrm_cms_base();
1559 $backdropPermissionsURL = "{$backdropURL}index.php?q=admin/config/people/permissions";
1560 $backdropURL .= "index.php?q=civicrm/admin/configtask&reset=1";
1561
1562 $output .= "<li>" . ts("Backdrop 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='{$backdropPermissionsURL}'", 2 => "target='_blank' href='http://wiki.civicrm.org/confluence/display/CRMDOC/Default+Permissions+and+Roles'")) . "</li>";
1563 $output .= "<li>" . ts("Use the <a %1>Configuration Checklist</a> to review and configure settings for your new site", array(1 => "target='_blank' href='$backdropURL'")) . "</li>";
1564 $output .= $commonOutputMessage;
1565
1566 // automatically enable CiviCRM module once it is installed successfully.
1567 // so we need to Bootstrap Drupal, so that we can call drupal hooks.
1568 global $cmsPath, $crmPath;
1569
1570 // relative / abosolute paths are not working for drupal, hence using chdir()
1571 chdir($cmsPath);
1572
1573 // Force the re-initialisation of the config singleton on the next call
1574 // since so far, we had used the Config object without loading the DB.
1575 $c = CRM_Core_Config::singleton(FALSE);
1576 $c->free();
1577
1578 include_once "./core/includes/bootstrap.inc";
1579 include_once "./core/includes/unicode.inc";
1580 include_once "./core/includes/config.inc";
1581
1582 backdrop_bootstrap(BACKDROP_BOOTSTRAP_FULL);
1583
1584 // prevent session information from being saved.
1585 backdrop_save_session(FALSE);
1586
1587 // Force the current user to anonymous.
1588 $original_user = $GLOBALS['user'];
1589 $GLOBALS['user'] = backdrop_anonymous_user();
1590
1591 // explicitly setting error reporting, since we cannot handle drupal related notices
1592 error_reporting(1);
1593
1594 // rebuild modules, so that civicrm is added
1595 system_rebuild_module_data();
1596
1597 // now enable civicrm module.
1598 module_enable(array('civicrm', 'civicrmtheme'));
1599
1600 // clear block, page, theme, and hook caches
1601 backdrop_flush_all_caches();
1602
1603 //add basic backdrop permissions
1604 civicrm_install_set_backdrop_perms();
1605
1606 // restore the user.
1607 $GLOBALS['user'] = $original_user;
1608 backdrop_save_session(TRUE);
1609
1610 //change the default language to one chosen
1611 if (isset($config['seedLanguage']) && $config['seedLanguage'] != 'en_US') {
1612 civicrm_api3('Setting', 'create', array(
1613 'domain_id' => 'current_domain',
1614 'lcMessages' => $config['seedLanguage'],
1615 )
1616 );
1617 }
1618
1619 $output .= '</ul>';
1620 $output .= '</div>';
1621 $output .= '</body>';
1622 $output .= '</html>';
1623 echo $output;
1624 }
1625 elseif ($installType == 'drupal' && version_compare(VERSION, '6.0') >= 0) {
1626 // clean output
1627 @ob_clean();
1628
1629 $output .= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
1630 $output .= '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">';
1631 $output .= '<head>';
1632 $output .= '<title>' . ts('CiviCRM Installed') . '</title>';
1633 $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
1634 $output .= '<link rel="stylesheet" type="text/css" href="template.css" />';
1635 $output .= '</head>';
1636 $output .= '<body>';
1637 $output .= '<div style="padding: 1em;"><p class="good">' . ts("CiviCRM has been successfully installed") . '</p>';
1638 $output .= '<ul>';
1639
1640 $drupalURL = civicrm_cms_base();
1641 $drupalPermissionsURL = "{$drupalURL}index.php?q=admin/user/permissions";
1642 $drupalURL .= "index.php?q=civicrm/admin/configtask&reset=1";
1643
1644 $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>";
1645 $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>";
1646 $output .= $commonOutputMessage;
1647
1648 // explicitly setting error reporting, since we cannot handle drupal related notices
1649 error_reporting(1);
1650
1651 // automatically enable CiviCRM module once it is installed successfully.
1652 // so we need to Bootstrap Drupal, so that we can call drupal hooks.
1653 global $cmsPath, $crmPath;
1654
1655 // relative / abosolute paths are not working for drupal, hence using chdir()
1656 chdir($cmsPath);
1657
1658 // Force the re-initialisation of the config singleton on the next call
1659 // since so far, we had used the Config object without loading the DB.
1660 $c = CRM_Core_Config::singleton(FALSE);
1661 $c->free();
1662
1663 include_once "./includes/bootstrap.inc";
1664 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
1665
1666 // rebuild modules, so that civicrm is added
1667 module_rebuild_cache();
1668
1669 // now enable civicrm module.
1670 module_enable(array('civicrm'));
1671
1672 // clear block, page, theme, and hook caches
1673 drupal_flush_all_caches();
1674
1675 //add basic drupal permissions
1676 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)');
1677
1678 echo $output;
1679 }
1680 elseif ($installType == 'wordpress') {
1681 echo '<h1>' . ts('CiviCRM Installed') . '</h1>';
1682 echo '<div style="padding: 1em;"><p style="background-color: #0C0; border: 1px #070 solid; color: white;">' . ts("CiviCRM has been successfully installed") . '</p>';
1683 echo '<ul>';
1684
1685 $cmsURL = civicrm_cms_base();
1686 $cmsURL .= "wp-admin/admin.php?page=CiviCRM&q=civicrm/admin/configtask&reset=1";
1687 $wpPermissionsURL = "wp-admin/admin.php?page=CiviCRM&q=civicrm/admin/access/wp-permissions&reset=1";
1688
1689 $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>";
1690 $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>";
1691 $output .= $commonOutputMessage;
1692
1693 echo '</ul>';
1694 echo '</div>';
1695
1696 $c = CRM_Core_Config::singleton(FALSE);
1697 $c->free();
1698 $wpInstallRedirect = admin_url("?page=CiviCRM&q=civicrm&reset=1");
1699 echo "<script>
1700 window.location = '$wpInstallRedirect';
1701 </script>";
1702 }
1703 }
1704
1705 return $this->errors;
1706 }
1707
1708 }
1709
1710 function civicrm_install_set_drupal_perms() {
1711 if (!function_exists('db_select')) {
1712 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)');
1713 }
1714 else {
1715 $perms = array(
1716 'access all custom data',
1717 'access uploaded files',
1718 'make online contributions',
1719 'profile create',
1720 'profile edit',
1721 'profile view',
1722 'register for events',
1723 'view event info',
1724 'view event participants',
1725 'access CiviMail subscribe/unsubscribe pages',
1726 );
1727
1728 // Adding a permission that has not yet been assigned to a module by
1729 // a hook_permission implementation results in a database error.
1730 // CRM-9042
1731 $allPerms = array_keys(module_invoke_all('permission'));
1732 foreach (array_diff($perms, $allPerms) as $perm) {
1733 watchdog('civicrm',
1734 'Cannot grant the %perm permission because it does not yet exist.',
1735 array('%perm' => $perm),
1736 WATCHDOG_ERROR
1737 );
1738 }
1739 $perms = array_intersect($perms, $allPerms);
1740 user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, $perms);
1741 user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, $perms);
1742 }
1743 }
1744
1745 function civicrm_install_set_backdrop_perms() {
1746 $perms = array(
1747 'access all custom data',
1748 'access uploaded files',
1749 'make online contributions',
1750 'profile create',
1751 'profile edit',
1752 'profile view',
1753 'register for events',
1754 'view event info',
1755 'view event participants',
1756 'access CiviMail subscribe/unsubscribe pages',
1757 );
1758
1759 // Adding a permission that has not yet been assigned to a module by
1760 // a hook_permission implementation results in a database error.
1761 // CRM-9042
1762 $allPerms = array_keys(module_invoke_all('permission'));
1763 foreach (array_diff($perms, $allPerms) as $perm) {
1764 watchdog('civicrm',
1765 'Cannot grant the %perm permission because it does not yet exist.',
1766 array('%perm' => $perm),
1767 WATCHDOG_ERROR
1768 );
1769 }
1770 $perms = array_intersect($perms, $allPerms);
1771 user_role_grant_permissions(BACKDROP_AUTHENTICATED_ROLE, $perms);
1772 user_role_grant_permissions(BACKDROP_ANONYMOUS_ROLE, $perms);
1773 }
1774
1775 /**
1776 * @param $cmsPath
1777 * @param $str
1778 *
1779 * @return string
1780 */
1781 function getSiteDir($cmsPath, $str) {
1782 static $siteDir = '';
1783
1784 if ($siteDir) {
1785 return $siteDir;
1786 }
1787
1788 $sites = CIVICRM_DIRECTORY_SEPARATOR . 'sites' . CIVICRM_DIRECTORY_SEPARATOR;
1789 $modules = CIVICRM_DIRECTORY_SEPARATOR . 'modules' . CIVICRM_DIRECTORY_SEPARATOR;
1790 preg_match("/" . preg_quote($sites, CIVICRM_DIRECTORY_SEPARATOR) .
1791 "([\-a-zA-Z0-9_.]+)" .
1792 preg_quote($modules, CIVICRM_DIRECTORY_SEPARATOR) . "/",
1793 $_SERVER['SCRIPT_FILENAME'], $matches
1794 );
1795 $siteDir = isset($matches[1]) ? $matches[1] : 'default';
1796
1797 if (strtolower($siteDir) == 'all') {
1798 // For this case - use drupal's way of finding out multi-site directory
1799 $uri = explode(CIVICRM_DIRECTORY_SEPARATOR, $_SERVER['SCRIPT_FILENAME']);
1800 $server = explode('.', implode('.', array_reverse(explode(':', rtrim($_SERVER['HTTP_HOST'], '.')))));
1801 for ($i = count($uri) - 1; $i > 0; $i--) {
1802 for ($j = count($server); $j > 0; $j--) {
1803 $dir = implode('.', array_slice($server, -$j)) . implode('.', array_slice($uri, 0, $i));
1804 if (file_exists($cmsPath . CIVICRM_DIRECTORY_SEPARATOR .
1805 'sites' . CIVICRM_DIRECTORY_SEPARATOR . $dir
1806 )) {
1807 $siteDir = $dir;
1808 return $siteDir;
1809 }
1810 }
1811 }
1812 $siteDir = 'default';
1813 }
1814
1815 return $siteDir;
1816 }
1817
1818 /**
1819 * @param $errorTitle
1820 * @param $errorMsg
1821 * @param $showRefer
1822 */
1823 function errorDisplayPage($errorTitle, $errorMsg, $showRefer = TRUE) {
1824 if ($showRefer) {
1825 $docLink = CRM_Utils_System::docURL2('Installation and Upgrades', FALSE, 'Installation Guide', NULL, NULL, "wiki");
1826
1827 if (function_exists('ts')) {
1828 $errorMsg .= '<p>' . ts("<a %1>Refer to the online documentation for more information</a>", array(1 => "href='$docLink'")) . '</p>';
1829 }
1830 else {
1831 $errorMsg .= '<p>' . sprintf("<a %s>Refer to the online documentation for more information</a>", "href='$docLink'") . '</p>';
1832 }
1833 }
1834
1835 include 'error.html';
1836 exit();
1837 }