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