ElggInstaller   F
last analyzed

Complexity

Total Complexity 187

Size/Duplication

Total Lines 1618
Duplicated Lines 6.98 %

Coupling/Cohesion

Components 1
Dependencies 19

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
dl 113
loc 1618
ccs 0
cts 924
cp 0
rs 0.8
c 0
b 0
f 0
wmc 187
lcom 1
cbo 19

47 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 23 3
A getElggRoot() 0 3 1
A run() 0 26 2
A setAutoLogin() 0 3 1
C batchInstall() 10 72 12
A render() 0 17 1
A welcome() 0 3 1
A requirements() 0 37 2
C database() 0 75 10
B settings() 20 63 5
B admin() 16 58 5
A complete() 0 11 2
A getSteps() 0 3 1
A continueToNextStep() 0 4 1
A getNextStep() 0 8 2
A getNextStepUrl() 0 4 1
C setInstallStatus() 0 62 12
A checkInstallCompletion() 0 8 3
A resumeInstall() 0 21 5
A bootstrapEngine() 0 5 1
B finishBootstraping() 0 85 8
A bootstrapConfig() 0 18 1
A isHttps() 0 4 3
B getBaseUrl() 0 18 7
A loadSettingsFile() 0 7 2
A getPostVariables() 0 7 2
A makeFormSticky() 0 6 2
A checkEngineDir() 0 16 2
A checkSettingsFile() 0 18 3
A checkPHP() 0 24 3
A checkPhpExtensions() 16 29 5
B checkPhpDirectives() 0 38 6
A checkRewriteRules() 0 7 1
A processRewriteTest() 0 6 2
A countNumConditions() 0 12 4
A validateDatabaseVars() 7 27 5
A checkDatabaseSettings() 0 32 4
A createSettingsFile() 0 23 4
A connectToDatabase() 0 22 4
A installDatabase() 0 16 3
B createDataDirectory() 0 25 7
C validateSettingsVars() 32 62 14
A saveSiteSettings() 0 53 2
A setSubtypeClasses() 0 7 1
A enablePlugins() 0 14 5
B validateAdminVars() 12 35 9
B createAdminAccount() 0 56 7

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ElggInstaller often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ElggInstaller, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Elgg Installer.
5
 * Controller for installing Elgg. Supports both web-based on CLI installation.
6
 *
7
 * This controller steps the user through the install process. The method for
8
 * each step handles both the GET and POST requests. There is no XSS/CSRF protection
9
 * on the POST processing since the installer is only run once by the administrator.
10
 *
11
 * The installation process can be resumed by hitting the first page. The installer
12
 * will try to figure out where to pick up again.
13
 *
14
 * All the logic for the installation process is in this class, but it depends on
15
 * the core libraries. To do this, we selectively load a subset of the core libraries
16
 * for the first few steps and then load the entire engine once the database and
17
 * site settings are configured. In addition, this controller does its own session
18
 * handling until the database is setup.
19
 *
20
 * There is an aborted attempt in the code at creating the data directory for
21
 * users as a subdirectory of Elgg's root. The idea was to protect this directory
22
 * through a .htaccess file. The problem is that a malicious user can upload a
23
 * .htaccess of his own that overrides the protection for his user directory. The
24
 * best solution is server level configuration that turns off AllowOverride for the
25
 * data directory. See ticket #3453 for discussion on this.
26
 *
27
 * @package    Elgg.Core
28
 * @subpackage Installer
29
 */
30
class ElggInstaller {
31
	
32
	protected $steps = array(
33
		'welcome',
34
		'requirements',
35
		'database',
36
		'settings',
37
		'admin',
38
		'complete',
39
		);
40
41
	protected $status = array(
42
		'config' => FALSE,
43
		'database' => FALSE,
44
		'settings' => FALSE,
45
		'admin' => FALSE,
46
	);
47
48
	protected $isAction = FALSE;
49
50
	protected $autoLogin = TRUE;
51
52
	/**
53
	 * Global Elgg configuration
54
	 * 
55
	 * @var \stdClass
56
	 */
57
	private $CONFIG;
58
59
	/**
60
	 * Constructor bootstraps the Elgg engine
61
	 */
62
	public function __construct() {
63
		global $CONFIG;
64
		if (!isset($CONFIG)) {
65
			$CONFIG = new stdClass;
66
		}
67
68
		$this->CONFIG = $CONFIG;
69
70
		$this->isAction = isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'POST';
71
72
		$this->bootstrapConfig();
73
74
		$this->bootstrapEngine();
75
76
		_elgg_services()->setValue('session', \ElggSession::getMock());
77
78
		elgg_set_viewtype('installation');
79
80
		set_error_handler('_elgg_php_error_handler');
81
		set_exception_handler('_elgg_php_exception_handler');
82
83
		_elgg_services()->translator->registerTranslations("{$this->getElggRoot()}/install/languages/", TRUE);
84
	}
85
	
86
	/**
87
	 * @return string The absolute path to Elgg's root directory
88
	 */
89
	private function getElggRoot() {
90
		return dirname(dirname(__DIR__));
91
	}
92
93
	/**
94
	 * Dispatches a request to one of the step controllers
95
	 *
96
	 * @param string $step The installation step to run
97
	 *
98
	 * @return void
99
	 * @throws InstallationException
100
	 */
101
	public function run($step) {
102
		global $CONFIG;
103
104
		// language needs to be set before the first call to elgg_echo()
105
		$CONFIG->language = 'en';
106
107
		// check if this is a URL rewrite test coming in
108
		$this->processRewriteTest();
109
110
		if (!in_array($step, $this->getSteps())) {
111
			$msg = _elgg_services()->translator->translate('InstallationException:UnknownStep', array($step));
112
			throw new InstallationException($msg);
113
		}
114
115
		$this->setInstallStatus();
116
117
		$this->checkInstallCompletion($step);
118
119
		// check if this is an install being resumed
120
		$this->resumeInstall($step);
121
122
		$this->finishBootstraping($step);
123
124
		$params = $this->getPostVariables();
125
		$this->$step($params);
126
	}
127
128
	/**
129
	 * Set the auto login flag
130
	 *
131
	 * @param bool $flag Auto login
132
	 *
133
	 * @return void
134
	 */
135
	public function setAutoLogin($flag) {
136
		$this->autoLogin = (bool) $flag;
137
	}
138
139
	/**
140
	 * A batch install of Elgg
141
	 *
142
	 * All required parameters must be passed in as an associative array. See
143
	 * $requiredParams for a list of them. This creates the necessary files,
144
	 * loads the database, configures the site settings, and creates the admin
145
	 * account. If it fails, an exception is thrown. It does not check any of
146
	 * the requirements as the multiple step web installer does.
147
	 *
148
	 * If the settings.php file exists, it will use that rather than the parameters
149
	 * passed to this function.
150
	 *
151
	 * @param array $params         Array of key value pairs
152
	 * @param bool  $createHtaccess Should .htaccess be created
153
	 *
154
	 * @return void
155
	 * @throws InstallationException
156
	 */
157
	public function batchInstall(array $params, $createHtaccess = FALSE) {
158
		
159
160
		restore_error_handler();
161
		restore_exception_handler();
162
163
		$defaults = array(
164
			'dbhost' => 'localhost',
165
			'dbprefix' => 'elgg_',
166
			'language' => 'en',
167
			'siteaccess' => ACCESS_PUBLIC,
168
		);
169
		$params = array_merge($defaults, $params);
170
171
		$requiredParams = array(
172
			'dbuser',
173
			'dbpassword',
174
			'dbname',
175
			'sitename',
176
			'wwwroot',
177
			'dataroot',
178
			'displayname',
179
			'email',
180
			'username',
181
			'password',
182
		);
183
		foreach ($requiredParams as $key) {
184
			if (empty($params[$key])) {
185
				$msg = _elgg_services()->translator->translate('install:error:requiredfield', array($key));
186
				throw new InstallationException($msg);
187
			}
188
		}
189
190
		// password is passed in once
191
		$params['password1'] = $params['password2'] = $params['password'];
192
193
		if ($createHtaccess) {
194
			$rewriteTester = new ElggRewriteTester();
195
			if (!$rewriteTester->createHtaccess($params['wwwroot'], $this->CONFIG->path)) {
196
				throw new InstallationException(_elgg_services()->translator->translate('install:error:htaccess'));
197
			}
198
		}
199
200
		$this->setInstallStatus();
201
202 View Code Duplication
		if (!$this->status['config']) {
203
			if (!$this->createSettingsFile($params)) {
204
				throw new InstallationException(_elgg_services()->translator->translate('install:error:settings'));
205
			}
206
		}
207
208
		if (!$this->connectToDatabase()) {
209
			throw new InstallationException(_elgg_services()->translator->translate('install:error:databasesettings'));
210
		}
211
212 View Code Duplication
		if (!$this->status['database']) {
213
			if (!$this->installDatabase()) {
214
				throw new InstallationException(_elgg_services()->translator->translate('install:error:cannotloadtables'));
215
			}
216
		}
217
218
		// load remaining core libraries
219
		$this->finishBootstraping('settings');
220
221
		if (!$this->saveSiteSettings($params)) {
222
			throw new InstallationException(_elgg_services()->translator->translate('install:error:savesitesettings'));
223
		}
224
225
		if (!$this->createAdminAccount($params)) {
226
			throw new InstallationException(_elgg_services()->translator->translate('install:admin:cannot_create'));
227
		}
228
	}
229
230
	/**
231
	 * Renders the data passed by a controller
232
	 *
233
	 * @param string $step The current step
234
	 * @param array  $vars Array of vars to pass to the view
235
	 *
236
	 * @return void
237
	 */
238
	protected function render($step, $vars = array()) {
239
240
		$vars['next_step'] = $this->getNextStep($step);
241
242
		$title = _elgg_services()->translator->translate("install:$step");
243
		$body = elgg_view("install/pages/$step", $vars);
244
		echo elgg_view_page(
245
				$title,
246
				$body,
247
				'default',
248
				array(
249
					'step' => $step,
250
					'steps' => $this->getSteps(),
251
					)
252
				);
253
		exit;
254
	}
255
256
	/**
257
	 * Step controllers
258
	 */
259
260
	/**
261
	 * Welcome controller
262
	 *
263
	 * @param array $vars Not used
264
	 *
265
	 * @return void
266
	 */
267
	protected function welcome($vars) {
268
		$this->render('welcome');
269
	}
270
271
	/**
272
	 * Requirements controller
273
	 *
274
	 * Checks version of php, libraries, permissions, and rewrite rules
275
	 *
276
	 * @param array $vars Vars
277
	 *
278
	 * @return void
279
	 */
280
	protected function requirements($vars) {
281
282
		$report = array();
283
284
		// check PHP parameters and libraries
285
		$this->checkPHP($report);
286
287
		// check URL rewriting
288
		$this->checkRewriteRules($report);
289
290
		// check for existence of settings file
291
		if ($this->checkSettingsFile($report) != TRUE) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison !== instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
292
			// no file, so check permissions on engine directory
293
			$this->checkEngineDir($report);
294
		}
295
296
		// check the database later
297
		$report['database'] = array(array(
298
			'severity' => 'info',
299
			'message' => _elgg_services()->translator->translate('install:check:database')
300
		));
301
302
		// any failures?
303
		$numFailures = $this->countNumConditions($report, 'failure');
304
305
		// any warnings
306
		$numWarnings = $this->countNumConditions($report, 'warning');
307
308
309
		$params = array(
310
			'report' => $report,
311
			'num_failures' => $numFailures,
312
			'num_warnings' => $numWarnings,
313
		);
314
315
		$this->render('requirements', $params);
316
	}
317
318
	/**
319
	 * Database set up controller
320
	 *
321
	 * Creates the settings.php file and creates the database tables
322
	 *
323
	 * @param array $submissionVars Submitted form variables
324
	 *
325
	 * @return void
326
	 */
327
	protected function database($submissionVars) {
328
329
		$formVars = array(
330
			'dbuser' => array(
331
				'type' => 'text',
332
				'value' => '',
333
				'required' => TRUE,
334
				),
335
			'dbpassword' => array(
336
				'type' => 'password',
337
				'value' => '',
338
				'required' => FALSE,
339
				),
340
			'dbname' => array(
341
				'type' => 'text',
342
				'value' => '',
343
				'required' => TRUE,
344
				),
345
			'dbhost' => array(
346
				'type' => 'text',
347
				'value' => 'localhost',
348
				'required' => TRUE,
349
				),
350
			'dbprefix' => array(
351
				'type' => 'text',
352
				'value' => 'elgg_',
353
				'required' => TRUE,
354
				),
355
		);
356
357
		if ($this->checkSettingsFile()) {
358
			// user manually created settings file so we fake out action test
359
			$this->isAction = TRUE;
360
		}
361
362
		if ($this->isAction) {
363
			do {
364
				// only create settings file if it doesn't exist
365
				if (!$this->checkSettingsFile()) {
366
					if (!$this->validateDatabaseVars($submissionVars, $formVars)) {
367
						// error so we break out of action and serve same page
368
						break;
369
					}
370
371
					if (!$this->createSettingsFile($submissionVars)) {
372
						break;
373
					}
374
				}
375
376
				// check db version and connect
377
				if (!$this->connectToDatabase()) {
378
					break;
379
				}
380
381
				if (!$this->installDatabase()) {
382
					break;
383
				}
384
385
				system_message(_elgg_services()->translator->translate('install:success:database'));
386
387
				$this->continueToNextStep('database');
388
			} while (FALSE);  // PHP doesn't support breaking out of if statements
389
		}
390
391
		$formVars = $this->makeFormSticky($formVars, $submissionVars);
392
393
		$params = array('variables' => $formVars,);
394
395
		if ($this->checkSettingsFile()) {
396
			// settings file exists and we're here so failed to create database
397
			$params['failure'] = TRUE;
398
		}
399
400
		$this->render('database', $params);
401
	}
402
403
	/**
404
	 * Site settings controller
405
	 *
406
	 * Sets the site name, URL, data directory, etc.
407
	 *
408
	 * @param array $submissionVars Submitted vars
409
	 *
410
	 * @return void
411
	 */
412
	protected function settings($submissionVars) {
413
		
414
415
		$formVars = array(
416
			'sitename' => array(
417
				'type' => 'text',
418
				'value' => 'My New Community',
419
				'required' => TRUE,
420
				),
421
			'siteemail' => array(
422
				'type' => 'email',
423
				'value' => '',
424
				'required' => FALSE,
425
				),
426
			'wwwroot' => array(
427
				'type' => 'url',
428
				'value' => _elgg_services()->config->getSiteUrl(),
429
				'required' => TRUE,
430
				),
431
			'dataroot' => array(
432
				'type' => 'text',
433
				'value' => '',
434
				'required' => TRUE,
435
				),
436
			'siteaccess' => array(
437
				'type' => 'access',
438
				'value' => ACCESS_PUBLIC,
439
				'required' => TRUE,
440
				),
441
		);
442
443
		// if Apache, we give user option of having Elgg create data directory
444
		//if (ElggRewriteTester::guessWebServer() == 'apache') {
445
		//	$formVars['dataroot']['type'] = 'combo';
446
		//	$this->CONFIG->translations['en']['install:settings:help:dataroot'] =
447
		//			$this->CONFIG->translations['en']['install:settings:help:dataroot:apache'];
448
		//}
449
450 View Code Duplication
		if ($this->isAction) {
451
			do {
452
				//if (!$this->createDataDirectory($submissionVars, $formVars)) {
453
				//	break;
454
				//}
455
456
				if (!$this->validateSettingsVars($submissionVars, $formVars)) {
457
					break;
458
				}
459
460
				if (!$this->saveSiteSettings($submissionVars)) {
461
					break;
462
				}
463
464
				system_message(_elgg_services()->translator->translate('install:success:settings'));
465
466
				$this->continueToNextStep('settings');
467
468
			} while (FALSE);  // PHP doesn't support breaking out of if statements
469
		}
470
471
		$formVars = $this->makeFormSticky($formVars, $submissionVars);
472
473
		$this->render('settings', array('variables' => $formVars));
474
	}
475
476
	/**
477
	 * Admin account controller
478
	 *
479
	 * Creates an admin user account
480
	 *
481
	 * @param array $submissionVars Submitted vars
482
	 *
483
	 * @return void
484
	 */
485
	protected function admin($submissionVars) {
486
		$formVars = array(
487
			'displayname' => array(
488
				'type' => 'text',
489
				'value' => '',
490
				'required' => TRUE,
491
				),
492
			'email' => array(
493
				'type' => 'email',
494
				'value' => '',
495
				'required' => TRUE,
496
				),
497
			'username' => array(
498
				'type' => 'text',
499
				'value' => '',
500
				'required' => TRUE,
501
				),
502
			'password1' => array(
503
				'type' => 'password',
504
				'value' => '',
505
				'required' => TRUE,
506
				'pattern' => '.{6,}',
507
				),
508
			'password2' => array(
509
				'type' => 'password',
510
				'value' => '',
511
				'required' => TRUE,
512
				),
513
		);
514
		
515 View Code Duplication
		if ($this->isAction) {
516
			do {
517
				if (!$this->validateAdminVars($submissionVars, $formVars)) {
518
					break;
519
				}
520
521
				if (!$this->createAdminAccount($submissionVars, $this->autoLogin)) {
522
					break;
523
				}
524
525
				system_message(_elgg_services()->translator->translate('install:success:admin'));
526
527
				$this->continueToNextStep('admin');
528
529
			} while (FALSE);  // PHP doesn't support breaking out of if statements
530
		}
531
532
		// bit of a hack to get the password help to show right number of characters
533
		
534
		$lang = _elgg_services()->translator->getCurrentLanguage();
535
		$this->CONFIG->translations[$lang]['install:admin:help:password1'] =
536
				sprintf($this->CONFIG->translations[$lang]['install:admin:help:password1'],
537
				$this->CONFIG->min_password_length);
538
539
		$formVars = $this->makeFormSticky($formVars, $submissionVars);
540
541
		$this->render('admin', array('variables' => $formVars));
542
	}
543
544
	/**
545
	 * Controller for last step
546
	 *
547
	 * @return void
548
	 */
549
	protected function complete() {
550
551
		$params = array();
552
		if ($this->autoLogin) {
553
			$params['destination'] = 'admin';
554
		} else {
555
			$params['destination'] = 'index.php';
556
		}
557
558
		$this->render('complete', $params);
559
	}
560
561
	/**
562
	 * Step management
563
	 */
564
565
	/**
566
	 * Get an array of steps
567
	 *
568
	 * @return array
569
	 */
570
	protected function getSteps() {
571
		return $this->steps;
572
	}
573
574
	/**
575
	 * Forwards the browser to the next step
576
	 *
577
	 * @param string $currentStep Current installation step
578
	 *
579
	 * @return void
580
	 */
581
	protected function continueToNextStep($currentStep) {
582
		$this->isAction = FALSE;
583
		forward($this->getNextStepUrl($currentStep));
584
	}
585
586
	/**
587
	 * Get the next step as a string
588
	 *
589
	 * @param string $currentStep Current installation step
590
	 *
591
	 * @return string
592
	 */
593
	protected function getNextStep($currentStep) {
594
		$index = 1 + array_search($currentStep, $this->steps);
595
		if (isset($this->steps[$index])) {
596
			return $this->steps[$index];
597
		} else {
598
			return null;
599
		}
600
	}
601
602
	/**
603
	 * Get the URL of the next step
604
	 *
605
	 * @param string $currentStep Current installation step
606
	 *
607
	 * @return string
608
	 */
609
	protected function getNextStepUrl($currentStep) {
610
		$nextStep = $this->getNextStep($currentStep);
611
		return _elgg_services()->config->getSiteUrl() . "install.php?step=$nextStep";
612
	}
613
614
	/**
615
	 * Check the different install steps for completion
616
	 *
617
	 * @return void
618
	 * @throws InstallationException
619
	 */
620
	protected function setInstallStatus() {
621
		
622
623
		if (!is_readable("{$this->CONFIG->path}engine/settings.php")) {
624
			return;
625
		}
626
627
		$this->loadSettingsFile();
628
629
		$this->status['config'] = TRUE;
630
631
		// must be able to connect to database to jump install steps
632
		$dbSettingsPass = $this->checkDatabaseSettings(
633
				$this->CONFIG->dbuser,
634
				$this->CONFIG->dbpass,
635
				$this->CONFIG->dbname,
636
				$this->CONFIG->dbhost
637
				);
638
		if ($dbSettingsPass == FALSE) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
639
			return;
640
		}
641
642
		if (!include_once("{$this->CONFIG->path}engine/lib/database.php")) {
643
			throw new InstallationException(_elgg_services()->translator->translate('InstallationException:MissingLibrary', array('database.php')));
644
		}
645
646
		// check that the config table has been created
647
		$query = "show tables";
648
		$result = _elgg_services()->db->getData($query);
649
		if ($result) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
650
			foreach ($result as $table) {
651
				$table = (array) $table;
652
				if (in_array("{$this->CONFIG->dbprefix}config", $table)) {
653
					$this->status['database'] = TRUE;
654
				}
655
			}
656
			if ($this->status['database'] == FALSE) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
657
				return;
658
			}
659
		} else {
660
			// no tables
661
			return;
662
		}
663
664
		// check that the config table has entries
665
		$query = "SELECT COUNT(*) AS total FROM {$this->CONFIG->dbprefix}config";
666
		$result = _elgg_services()->db->getData($query);
667
		if ($result && $result[0]->total > 0) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
668
			$this->status['settings'] = TRUE;
669
		} else {
670
			return;
671
		}
672
673
		// check that the users entity table has an entry
674
		$query = "SELECT COUNT(*) AS total FROM {$this->CONFIG->dbprefix}users_entity";
675
		$result = _elgg_services()->db->getData($query);
676
		if ($result && $result[0]->total > 0) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
677
			$this->status['admin'] = TRUE;
678
		} else {
679
			return;
680
		}
681
	}
682
683
	/**
684
	 * Security check to ensure the installer cannot be run after installation
685
	 * has finished. If this is detected, the viewer is sent to the front page.
686
	 *
687
	 * @param string $step Installation step to check against
688
	 *
689
	 * @return void
690
	 */
691
	protected function checkInstallCompletion($step) {
692
		if ($step != 'complete') {
693
			if (!in_array(FALSE, $this->status)) {
694
				// install complete but someone is trying to view an install page
695
				forward();
696
			}
697
		}
698
	}
699
700
	/**
701
	 * Check if this is a case of a install being resumed and figure
702
	 * out where to continue from. Returns the best guess on the step.
703
	 *
704
	 * @param string $step Installation step to resume from
705
	 *
706
	 * @return string
707
	 */
708
	protected function resumeInstall($step) {
709
		// only do a resume from the first step
710
		if ($step !== 'welcome') {
711
			return;
712
		}
713
714
		if ($this->status['database'] == FALSE) {
715
			return;
716
		}
717
718
		if ($this->status['settings'] == FALSE) {
719
			forward("install.php?step=settings");
720
		}
721
722
		if ($this->status['admin'] == FALSE) {
723
			forward("install.php?step=admin");
724
		}
725
726
		// everything appears to be set up
727
		forward("install.php?step=complete");
728
	}
729
730
	/**
731
	 * Bootstraping
732
	 */
733
734
	/**
735
	 * Load the essential libraries of the engine
736
	 *
737
	 * @return void
738
	 */
739
	protected function bootstrapEngine() {
740
		
741
742
		require_once $this->CONFIG->path . 'engine/load.php';
743
	}
744
745
	/**
746
	 * Load remaining engine libraries and complete bootstraping (see start.php)
747
	 *
748
	 * @param string $step Which step to boot strap for. Required because
749
	 *                     boot strapping is different until the DB is populated.
750
	 *
751
	 * @return void
752
	 * @throws InstallationException
753
	 */
754
	protected function finishBootstraping($step) {
755
756
		$dbIndex = array_search('database', $this->getSteps());
757
		$settingsIndex = array_search('settings', $this->getSteps());
758
		$adminIndex = array_search('admin', $this->getSteps());
759
		$completeIndex = array_search('complete', $this->getSteps());
760
		$stepIndex = array_search($step, $this->getSteps());
761
762
		// To log in the user, we need to use the Elgg core session handling.
763
		// Otherwise, use default php session handling
764
		$useElggSession = ($stepIndex == $adminIndex && $this->isAction) ||
765
				$stepIndex == $completeIndex;
766
		if (!$useElggSession) {
767
			session_name('Elgg_install');
768
			session_start();
769
			_elgg_services()->events->unregisterHandler('boot', 'system', 'session_init');
770
		}
771
772
		if ($stepIndex > $dbIndex) {
773
			// once the database has been created, load rest of engine
774
			
775
			$lib_dir = $this->CONFIG->path . 'engine/lib/';
776
777
			$this->loadSettingsFile();
778
779
			$lib_files = array(
780
				// these want to be loaded first apparently?
781
				'autoloader.php',
782
				'database.php',
783
				'actions.php',
784
785
				'admin.php',
786
				'annotations.php',
787
				'cron.php',
788
				'entities.php',
789
				'extender.php',
790
				'filestore.php',
791
				'group.php',
792
				'mb_wrapper.php',
793
				'memcache.php',
794
				'metadata.php',
795
				'metastrings.php',
796
				'navigation.php',
797
				'notification.php',
798
				'objects.php',
799
				'pagehandler.php',
800
				'pam.php',
801
				'plugins.php',
802
				'private_settings.php',
803
				'relationships.php',
804
				'river.php',
805
				'sites.php',
806
				'statistics.php',
807
				'tags.php',
808
				'user_settings.php',
809
				'users.php',
810
				'upgrade.php',
811
				'widgets.php',
812
				'deprecated-1.7.php',
813
				'deprecated-1.8.php',
814
				'deprecated-1.9.php',
815
			);
816
817
			foreach ($lib_files as $file) {
818
				$path = $lib_dir . $file;
819
				if (!include_once($path)) {
820
					throw new InstallationException('InstallationException:MissingLibrary', array($file));
821
				}
822
			}
823
824
			_elgg_services()->db->setupConnections();
825
			_elgg_services()->translator->registerTranslations("{$this->getElggRoot()}/languages/");
826
			$this->CONFIG->language = 'en';
827
828
			if ($stepIndex > $settingsIndex) {
829
				$this->CONFIG->site_guid = (int) _elgg_services()->datalist->get('default_site');
830
				$this->CONFIG->site_id = $this->CONFIG->site_guid;
831
				$this->CONFIG->site = get_entity($this->CONFIG->site_guid);
832
				$this->CONFIG->dataroot = _elgg_services()->datalist->get('dataroot');
833
				_elgg_session_boot();
834
			}
835
836
			_elgg_services()->events->trigger('init', 'system');
837
		}
838
	}
839
840
	/**
841
	 * Set up configuration variables
842
	 *
843
	 * @return void
844
	 */
845
	protected function bootstrapConfig() {
846
		$this->CONFIG->installer_running = true;
847
848
		$this->CONFIG->wwwroot = $this->getBaseUrl();
849
		$this->CONFIG->url = $this->CONFIG->wwwroot;
850
		$this->CONFIG->path = "{$this->getElggRoot()}/";
851
		$this->CONFIG->viewpath =	$this->CONFIG->path . 'views/';
852
		$this->CONFIG->pluginspath = $this->CONFIG->path . 'mod/';
853
		$this->CONFIG->context = array();
854
		$this->CONFIG->entity_types = array('group', 'object', 'site', 'user');
855
856
		// required by elgg_view_page()
857
		$this->CONFIG->sitename = '';
858
		$this->CONFIG->sitedescription = '';
859
860
		// required by Elgg\Config::get
861
		$this->CONFIG->site_guid = 1;
862
	}
863
	
864
	/**
865
	 * @return bool Whether the install process is encrypted.
866
	 */
867
	private function isHttps() {
868
		return (!empty($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on") ||
869
			$_SERVER['SERVER_PORT'] == 443;
870
	}
871
872
	/**
873
	 * Get the best guess at the base URL
874
	 *
875
	 * @note Cannot use current_page_url() because it depends on $this->CONFIG->wwwroot
876
	 * @todo Should this be a core function?
877
	 *
878
	 * @return string
879
	 */
880
	protected function getBaseUrl() {
881
		$protocol = $this->isHttps() ? 'https' : 'http';
882
		
883
		if (isset($_SERVER["SERVER_PORT"])) {
884
			$port = ':' . $_SERVER["SERVER_PORT"];
885
		} else {
886
			$port = '';
887
		}
888
		if ($port == ':80' || $port == ':443') {
889
			$port = '';
890
		}
891
		$uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
892
		$cutoff = strpos($uri, 'install.php');
893
		$uri = substr($uri, 0, $cutoff);
894
		$serverName = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '';
895
896
		return "$protocol://{$serverName}$port{$uri}";
897
	}
898
899
	/**
900
	 * Load settings.php
901
	 *
902
	 * @return void
903
	 * @throws InstallationException
904
	 */
905
	protected function loadSettingsFile() {
906
		
907
908
		if (!include_once("{$this->CONFIG->path}engine/settings.php")) {
909
			throw new InstallationException(_elgg_services()->translator->translate('InstallationException:CannotLoadSettings'));
910
		}
911
	}
912
913
	/**
914
	 * Action handling methods
915
	 */
916
917
	/**
918
	 * Return an associative array of post variables
919
	 * (could be selective based on expected variables)
920
	 *
921
	 * Does not filter as person installing the site should not be attempting
922
	 * XSS attacks. If filtering is added, it should not be done for passwords.
923
	 *
924
	 * @return array
925
	 */
926
	protected function getPostVariables() {
927
		$vars = array();
928
		foreach ($_POST as $k => $v) {
929
			$vars[$k] = $v;
930
		}
931
		return $vars;
932
	}
933
934
	/**
935
	 * If form is reshown, remember previously submitted variables
936
	 *
937
	 * @param array $formVars       Vars int he form
938
	 * @param array $submissionVars Submitted vars
939
	 *
940
	 * @return array
941
	 */
942
	protected function makeFormSticky($formVars, $submissionVars) {
943
		foreach ($submissionVars as $field => $value) {
944
			$formVars[$field]['value'] = $value;
945
		}
946
		return $formVars;
947
	}
948
949
	/**
950
	 * Requirement checks support methods
951
	 */
952
953
	/**
954
	 * Check that the engine dir is writable
955
	 *
956
	 * @param array &$report The requirements report object
957
	 *
958
	 * @return bool
959
	 */
960
	protected function checkEngineDir(&$report) {
961
		
962
963
		$writable = is_writable("{$this->CONFIG->path}engine");
964
		if (!$writable) {
965
			$report['settings'] = array(
966
				array(
967
					'severity' => 'failure',
968
					'message' => _elgg_services()->translator->translate('install:check:enginedir'),
969
				)
970
			);
971
			return FALSE;
972
		}
973
974
		return TRUE;
975
	}
976
977
	/**
978
	 * Check that the settings file exists
979
	 *
980
	 * @param array &$report The requirements report array
981
	 *
982
	 * @return bool
983
	 */
984
	protected function checkSettingsFile(&$report = array()) {
985
		
986
987
		if (!file_exists("{$this->CONFIG->path}engine/settings.php")) {
988
			return FALSE;
989
		}
990
991
		if (!is_readable("{$this->CONFIG->path}engine/settings.php")) {
992
			$report['settings'] = array(
993
				array(
994
					'severity' => 'failure',
995
					'message' => _elgg_services()->translator->translate('install:check:readsettings'),
996
				)
997
			);
998
		}
999
1000
		return TRUE;
1001
	}
1002
1003
	/**
1004
	 * Check version of PHP, extensions, and variables
1005
	 *
1006
	 * @param array &$report The requirements report array
1007
	 *
1008
	 * @return void
1009
	 */
1010
	protected function checkPHP(&$report) {
1011
		$phpReport = array();
1012
1013
		$min_php_version = '5.4.0';
1014
		if (version_compare(PHP_VERSION, $min_php_version, '<')) {
1015
			$phpReport[] = array(
1016
				'severity' => 'failure',
1017
				'message' => _elgg_services()->translator->translate('install:check:php:version', array($min_php_version, PHP_VERSION))
1018
			);
1019
		}
1020
1021
		$this->checkPhpExtensions($phpReport);
1022
1023
		$this->checkPhpDirectives($phpReport);
1024
1025
		if (count($phpReport) == 0) {
1026
			$phpReport[] = array(
1027
				'severity' => 'pass',
1028
				'message' => _elgg_services()->translator->translate('install:check:php:success')
1029
			);
1030
		}
1031
1032
		$report['php'] = $phpReport;
1033
	}
1034
1035
	/**
1036
	 * Check the server's PHP extensions
1037
	 *
1038
	 * @param array &$phpReport The PHP requirements report array
1039
	 *
1040
	 * @return void
1041
	 */
1042
	protected function checkPhpExtensions(&$phpReport) {
1043
		$extensions = get_loaded_extensions();
1044
		$requiredExtensions = array(
1045
			'mysql',
1046
			'json',
1047
			'xml',
1048
			'gd',
1049
		);
1050 View Code Duplication
		foreach ($requiredExtensions as $extension) {
1051
			if (!in_array($extension, $extensions)) {
1052
				$phpReport[] = array(
1053
					'severity' => 'failure',
1054
					'message' => _elgg_services()->translator->translate('install:check:php:extension', array($extension))
1055
				);
1056
			}
1057
		}
1058
1059
		$recommendedExtensions = array(
1060
			'mbstring',
1061
		);
1062 View Code Duplication
		foreach ($recommendedExtensions as $extension) {
1063
			if (!in_array($extension, $extensions)) {
1064
				$phpReport[] = array(
1065
					'severity' => 'warning',
1066
					'message' => _elgg_services()->translator->translate('install:check:php:extension:recommend', array($extension))
1067
				);
1068
			}
1069
		}
1070
	}
1071
1072
	/**
1073
	 * Check PHP parameters
1074
	 *
1075
	 * @param array &$phpReport The PHP requirements report array
1076
	 *
1077
	 * @return void
1078
	 */
1079
	protected function checkPhpDirectives(&$phpReport) {
1080
		if (ini_get('open_basedir')) {
1081
			$phpReport[] = array(
1082
				'severity' => 'warning',
1083
				'message' => _elgg_services()->translator->translate("install:check:php:open_basedir")
1084
			);
1085
		}
1086
1087
		if (ini_get('safe_mode')) {
1088
			$phpReport[] = array(
1089
				'severity' => 'warning',
1090
				'message' => _elgg_services()->translator->translate("install:check:php:safe_mode")
1091
			);
1092
		}
1093
1094
		if (ini_get('arg_separator.output') !== '&') {
1095
			$separator = htmlspecialchars(ini_get('arg_separator.output'));
1096
			$msg = _elgg_services()->translator->translate("install:check:php:arg_separator", array($separator));
1097
			$phpReport[] = array(
1098
				'severity' => 'failure',
1099
				'message' => $msg,
1100
			);
1101
		}
1102
1103
		if (ini_get('register_globals')) {
1104
			$phpReport[] = array(
1105
				'severity' => 'failure',
1106
				'message' => _elgg_services()->translator->translate("install:check:php:register_globals")
1107
			);
1108
		}
1109
1110
		if (ini_get('session.auto_start')) {
1111
			$phpReport[] = array(
1112
				'severity' => 'failure',
1113
				'message' => _elgg_services()->translator->translate("install:check:php:session.auto_start")
1114
			);
1115
		}
1116
	}
1117
1118
	/**
1119
	 * Confirm that the rewrite rules are firing
1120
	 *
1121
	 * @param array &$report The requirements report array
1122
	 *
1123
	 * @return void
1124
	 */
1125
	protected function checkRewriteRules(&$report) {
1126
		
1127
1128
		$tester = new ElggRewriteTester();
1129
		$url = _elgg_services()->config->getSiteUrl() . "rewrite.php";
1130
		$report['rewrite'] = array($tester->run($url, $this->CONFIG->path));
1131
	}
1132
1133
	/**
1134
	 * Check if the request is coming from the URL rewrite test on the
1135
	 * requirements page.
1136
	 *
1137
	 * @return void
1138
	 */
1139
	protected function processRewriteTest() {
1140
		if (strpos($_SERVER['REQUEST_URI'], 'rewrite.php') !== FALSE) {
1141
			echo 'success';
1142
			exit;
1143
		}
1144
	}
1145
1146
	/**
1147
	 * Count the number of failures in the requirements report
1148
	 *
1149
	 * @param array  $report    The requirements report array
1150
	 * @param string $condition 'failure' or 'warning'
1151
	 *
1152
	 * @return int
1153
	 */
1154
	protected function countNumConditions($report, $condition) {
1155
		$count = 0;
1156
		foreach ($report as $category => $checks) {
1157
			foreach ($checks as $check) {
1158
				if ($check['severity'] === $condition) {
1159
					$count++;
1160
				}
1161
			}
1162
		}
1163
1164
		return $count;
1165
	}
1166
1167
1168
	/**
1169
	 * Database support methods
1170
	 */
1171
1172
	/**
1173
	 * Validate the variables for the database step
1174
	 *
1175
	 * @param array $submissionVars Submitted vars
1176
	 * @param array $formVars       Vars in the form
1177
	 *
1178
	 * @return bool
1179
	 */
1180
	protected function validateDatabaseVars($submissionVars, $formVars) {
1181
1182 View Code Duplication
		foreach ($formVars as $field => $info) {
1183
			if ($info['required'] == TRUE && !$submissionVars[$field]) {
1184
				$name = _elgg_services()->translator->translate("install:database:label:$field");
1185
				register_error(_elgg_services()->translator->translate('install:error:requiredfield', array($name)));
1186
				return FALSE;
1187
			}
1188
		}
1189
1190
		// according to postgres documentation: SQL identifiers and key words must
1191
		// begin with a letter (a-z, but also letters with diacritical marks and
1192
		// non-Latin letters) or an underscore (_). Subsequent characters in an
1193
		// identifier or key word can be letters, underscores, digits (0-9), or dollar signs ($).
1194
		// Refs #4994
1195
		if (!preg_match("/^[a-zA-Z_][\w]*$/", $submissionVars['dbprefix'])) {
1196
			register_error(_elgg_services()->translator->translate('install:error:database_prefix'));
1197
			return FALSE;
1198
		}
1199
1200
		return $this->checkDatabaseSettings(
1201
					$submissionVars['dbuser'],
1202
					$submissionVars['dbpassword'],
1203
					$submissionVars['dbname'],
1204
					$submissionVars['dbhost']
1205
				);
1206
	}
1207
1208
	/**
1209
	 * Confirm the settings for the database
1210
	 *
1211
	 * @param string $user     Username
1212
	 * @param string $password Password
1213
	 * @param string $dbname   Database name
1214
	 * @param string $host     Host
1215
	 *
1216
	 * @return bool
1217
	 */
1218
	protected function checkDatabaseSettings($user, $password, $dbname, $host) {
1219
		$config = new \Elgg\Database\Config((object)[
1220
			'dbhost' => $host,
1221
			'dbuser' => $user,
1222
			'dbpass' => $password,
1223
			'dbname' => $dbname,
1224
		]);
1225
		$logger = new \Elgg\Logger(new \Elgg\PluginHooksService());
1226
		$db = new \Elgg\Database($config, $logger);
1227
1228
		try {
1229
			$db->getDataRow("SELECT 1");
1230
		} catch (DatabaseException $e) {
1231
			if (0 === strpos($e->getMessage(), "Elgg couldn't connect")) {
1232
				register_error(_elgg_services()->translator->translate('install:error:databasesettings'));
1233
			} else {
1234
				register_error(_elgg_services()->translator->translate('install:error:nodatabase', array($dbname)));
1235
			}
1236
			return FALSE;
1237
		}
1238
1239
		// check MySQL version - must be 5.0 or >
1240
		$version = $db->getServerVersion(\Elgg\Database\Config::READ_WRITE);
1241
		$required_version = 5.0;
1242
		$points = explode('.', $version);
1243
		if ($points[0] < $required_version) {
1244
			register_error(_elgg_services()->translator->translate('install:error:oldmysql', array($version)));
1245
			return FALSE;
1246
		}
1247
1248
		return TRUE;
1249
	}
1250
1251
	/**
1252
	 * Writes the settings file to the engine directory
1253
	 *
1254
	 * @param array $params Array of inputted params from the user
1255
	 *
1256
	 * @return bool
1257
	 */
1258
	protected function createSettingsFile($params) {
1259
		
1260
1261
		$templateFile = "{$this->CONFIG->path}engine/settings.example.php";
1262
		$template = file_get_contents($templateFile);
1263
		if (!$template) {
1264
			register_error(_elgg_services()->translator->translate('install:error:readsettingsphp'));
1265
			return FALSE;
1266
		}
1267
1268
		foreach ($params as $k => $v) {
1269
			$template = str_replace("{{" . $k . "}}", $v, $template);
1270
		}
1271
1272
		$settingsFilename = "{$this->CONFIG->path}engine/settings.php";
1273
		$result = file_put_contents($settingsFilename, $template);
1274
		if (!$result) {
1275
			register_error(_elgg_services()->translator->translate('install:error:writesettingphp'));
1276
			return FALSE;
1277
		}
1278
1279
		return TRUE;
1280
	}
1281
1282
	/**
1283
	 * Bootstrap database connection before entire engine is available
1284
	 *
1285
	 * @return bool
1286
	 */
1287
	protected function connectToDatabase() {
1288
		
1289
1290
		if (!include_once("{$this->CONFIG->path}engine/settings.php")) {
1291
			register_error('Elgg could not load the settings file. It does not exist or there is a file permissions issue.');
1292
			return FALSE;
1293
		}
1294
1295
		if (!include_once("{$this->CONFIG->path}engine/lib/database.php")) {
1296
			register_error('Could not load database.php');
1297
			return FALSE;
1298
		}
1299
1300
		try  {
1301
			_elgg_services()->db->setupConnections();
1302
		} catch (DatabaseException $e) {
1303
			register_error($e->getMessage());
1304
			return FALSE;
1305
		}
1306
1307
		return TRUE;
1308
	}
1309
1310
	/**
1311
	 * Create the database tables
1312
	 *
1313
	 * @return bool
1314
	 */
1315
	protected function installDatabase() {
1316
		
1317
1318
		try {
1319
			_elgg_services()->db->runSqlScript("{$this->CONFIG->path}engine/schema/mysql.sql");
1320
		} catch (Exception $e) {
1321
			$msg = $e->getMessage();
1322
			if (strpos($msg, 'already exists')) {
1323
				$msg = _elgg_services()->translator->translate('install:error:tables_exist');
1324
			}
1325
			register_error($msg);
1326
			return FALSE;
1327
		}
1328
1329
		return TRUE;
1330
	}
1331
1332
	/**
1333
	 * Site settings support methods
1334
	 */
1335
1336
	/**
1337
	 * Create the data directory if requested
1338
	 *
1339
	 * @param array &$submissionVars Submitted vars
1340
	 * @param array $formVars        Variables in the form
1341
	 * 
1342
	 * @return bool
1343
	 */
1344
	protected function createDataDirectory(&$submissionVars, $formVars) {
1345
		// did the user have option of Elgg creating the data directory
1346
		if ($formVars['dataroot']['type'] != 'combo') {
1347
			return TRUE;
1348
		}
1349
1350
		// did the user select the option
1351
		if ($submissionVars['dataroot'] != 'dataroot-checkbox') {
1352
			return TRUE;
1353
		}
1354
1355
		$dir = sanitise_filepath($submissionVars['path']) . 'data';
1356
		if (file_exists($dir) || mkdir($dir, 0700)) {
1357
			$submissionVars['dataroot'] = $dir;
1358
			if (!file_exists("$dir/.htaccess")) {
1359
				$htaccess = "Order Deny,Allow\nDeny from All\n";
1360
				if (!file_put_contents("$dir/.htaccess", $htaccess)) {
1361
					return FALSE;
1362
				}
1363
			}
1364
			return TRUE;
1365
		}
1366
1367
		return FALSE;
1368
	}
1369
1370
	/**
1371
	 * Validate the site settings form variables
1372
	 *
1373
	 * @param array $submissionVars Submitted vars
1374
	 * @param array $formVars       Vars in the form
1375
	 *
1376
	 * @return bool
1377
	 */
1378
	protected function validateSettingsVars($submissionVars, $formVars) {
1379
		
1380
1381
		foreach ($formVars as $field => $info) {
1382
			$submissionVars[$field] = trim($submissionVars[$field]);
1383
			if ($info['required'] == TRUE && $submissionVars[$field] === '') {
1384
				$name = _elgg_services()->translator->translate("install:settings:label:$field");
1385
				register_error(_elgg_services()->translator->translate('install:error:requiredfield', array($name)));
1386
				return FALSE;
1387
			}
1388
		}
1389
1390
		// check that data root is absolute path
1391
		if (stripos(PHP_OS, 'win') === 0) {
1392 View Code Duplication
			if (strpos($submissionVars['dataroot'], ':') !== 1) {
1393
				$msg = _elgg_services()->translator->translate('install:error:relative_path', array($submissionVars['dataroot']));
1394
				register_error($msg);
1395
				return FALSE;
1396
			}
1397 View Code Duplication
		} else {
1398
			if (strpos($submissionVars['dataroot'], '/') !== 0) {
1399
				$msg = _elgg_services()->translator->translate('install:error:relative_path', array($submissionVars['dataroot']));
1400
				register_error($msg);
1401
				return FALSE;
1402
			}
1403
		}
1404
1405
		// check that data root exists
1406 View Code Duplication
		if (!file_exists($submissionVars['dataroot'])) {
1407
			$msg = _elgg_services()->translator->translate('install:error:datadirectoryexists', array($submissionVars['dataroot']));
1408
			register_error($msg);
1409
			return FALSE;
1410
		}
1411
1412
		// check that data root is writable
1413 View Code Duplication
		if (!is_writable($submissionVars['dataroot'])) {
1414
			$msg = _elgg_services()->translator->translate('install:error:writedatadirectory', array($submissionVars['dataroot']));
1415
			register_error($msg);
1416
			return FALSE;
1417
		}
1418
1419
		if (!isset($this->CONFIG->data_dir_override) || !$this->CONFIG->data_dir_override) {
1420
			// check that data root is not subdirectory of Elgg root
1421 View Code Duplication
			if (stripos($submissionVars['dataroot'], $submissionVars['path']) === 0) {
1422
				$msg = _elgg_services()->translator->translate('install:error:locationdatadirectory', array($submissionVars['dataroot']));
1423
				register_error($msg);
1424
				return FALSE;
1425
			}
1426
		}
1427
1428
		// check that email address is email address
1429 View Code Duplication
		if ($submissionVars['siteemail'] && !is_email_address($submissionVars['siteemail'])) {
1430
			$msg = _elgg_services()->translator->translate('install:error:emailaddress', array($submissionVars['siteemail']));
1431
			register_error($msg);
1432
			return FALSE;
1433
		}
1434
1435
		// @todo check that url is a url
1436
		// @note filter_var cannot be used because it doesn't work on international urls
1437
1438
		return TRUE;
1439
	}
1440
1441
	/**
1442
	 * Initialize the site including site entity, plugins, and configuration
1443
	 *
1444
	 * @param array $submissionVars Submitted vars
1445
	 *
1446
	 * @return bool
1447
	 */
1448
	protected function saveSiteSettings($submissionVars) {
1449
		
1450
1451
		// ensure that file path, data path, and www root end in /
1452
		$submissionVars['dataroot'] = sanitise_filepath($submissionVars['dataroot']);
1453
		$submissionVars['wwwroot'] = sanitise_filepath($submissionVars['wwwroot']);
1454
1455
		$site = new ElggSite();
1456
		$site->name = strip_tags($submissionVars['sitename']);
1457
		$site->url = $submissionVars['wwwroot'];
1458
		$site->access_id = ACCESS_PUBLIC;
1459
		$site->email = $submissionVars['siteemail'];
1460
		$guid = $site->save();
1461
1462
		if (!$guid) {
1463
			register_error(_elgg_services()->translator->translate('install:error:createsite'));
1464
			return FALSE;
1465
		}
1466
1467
		// bootstrap site info
1468
		$this->CONFIG->site_guid = $guid;
1469
		$this->CONFIG->site_id = $guid;
1470
		$this->CONFIG->site = $site;
1471
1472
		_elgg_services()->datalist->set('installed', time());
1473
		_elgg_services()->datalist->set('dataroot', $submissionVars['dataroot']);
1474
		_elgg_services()->datalist->set('default_site', $site->getGUID());
1475
		_elgg_services()->datalist->set('version', elgg_get_version());
0 ignored issues
show
Security Bug introduced by
It seems like elgg_get_version() can also be of type false; however, Elgg\Database\Datalist::set() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
1476
		_elgg_services()->datalist->set('simplecache_enabled', 1);
1477
		_elgg_services()->datalist->set('system_cache_enabled', 1);
1478
		_elgg_services()->datalist->set('simplecache_lastupdate', time());
1479
1480
		// @todo plugins might use this, but core doesn't. remove in 2.0
1481
		_elgg_services()->datalist->set('path', $this->CONFIG->path);
1482
1483
		// new installations have run all the upgrades
1484
		$upgrades = elgg_get_upgrade_files("{$this->CONFIG->path}engine/lib/upgrades/");
1485
		_elgg_services()->datalist->set('processed_upgrades', serialize($upgrades));
1486
1487
		_elgg_services()->configTable->set('view', 'default', $site->getGUID());
1488
		_elgg_services()->configTable->set('language', 'en', $site->getGUID());
1489
		_elgg_services()->configTable->set('default_access', $submissionVars['siteaccess'], $site->getGUID());
1490
		_elgg_services()->configTable->set('allow_registration', TRUE, $site->getGUID());
1491
		_elgg_services()->configTable->set('walled_garden', FALSE, $site->getGUID());
1492
		_elgg_services()->configTable->set('allow_user_default_access', '', $site->getGUID());
1493
		_elgg_services()->configTable->set('default_limit', 10, $site->getGUID());
1494
1495
		$this->setSubtypeClasses();
1496
1497
		$this->enablePlugins();
1498
1499
		return TRUE;
1500
	}
1501
1502
	/**
1503
	 * Register classes for core objects
1504
	 *
1505
	 * @return void
1506
	 */
1507
	protected function setSubtypeClasses() {
1508
		add_subtype("object", "plugin", "ElggPlugin");
1509
		add_subtype("object", "file", "ElggFile");
1510
		add_subtype("object", "widget", "ElggWidget");
1511
		add_subtype("object", "comment", "ElggComment");
1512
		add_subtype("object", "elgg_upgrade", 'ElggUpgrade');
1513
	}
1514
1515
	/**
1516
	 * Enable a set of default plugins
1517
	 *
1518
	 * @return void
1519
	 */
1520
	protected function enablePlugins() {
1521
		_elgg_generate_plugin_entities();
1522
		$plugins = elgg_get_plugins('any');
1523
		foreach ($plugins as $plugin) {
1524
			if ($plugin->getManifest()) {
1525
				if ($plugin->getManifest()->getActivateOnInstall()) {
1526
					$plugin->activate();
1527
				}
1528
				if (in_array('theme', $plugin->getManifest()->getCategories())) {
1529
					$plugin->setPriority('last');
1530
				}
1531
			}
1532
		}
1533
	}
1534
1535
	/**
1536
	 * Admin account support methods
1537
	 */
1538
1539
	/**
1540
	 * Validate account form variables
1541
	 *
1542
	 * @param array $submissionVars Submitted vars
1543
	 * @param array $formVars       Form vars
1544
	 *
1545
	 * @return bool
1546
	 */
1547
	protected function validateAdminVars($submissionVars, $formVars) {
1548
1549 View Code Duplication
		foreach ($formVars as $field => $info) {
1550
			if ($info['required'] == TRUE && !$submissionVars[$field]) {
1551
				$name = _elgg_services()->translator->translate("install:admin:label:$field");
1552
				register_error(_elgg_services()->translator->translate('install:error:requiredfield', array($name)));
1553
				return FALSE;
1554
			}
1555
		}
1556
1557
		if ($submissionVars['password1'] !== $submissionVars['password2']) {
1558
			register_error(_elgg_services()->translator->translate('install:admin:password:mismatch'));
1559
			return FALSE;
1560
		}
1561
1562
		if (trim($submissionVars['password1']) == "") {
1563
			register_error(_elgg_services()->translator->translate('install:admin:password:empty'));
1564
			return FALSE;
1565
		}
1566
1567
		$minLength = _elgg_services()->configTable->get('min_password_length');
1568
		if (strlen($submissionVars['password1']) < $minLength) {
1569
			register_error(_elgg_services()->translator->translate('install:admin:password:tooshort'));
1570
			return FALSE;
1571
		}
1572
1573
		// check that email address is email address
1574 View Code Duplication
		if ($submissionVars['email'] && !is_email_address($submissionVars['email'])) {
1575
			$msg = _elgg_services()->translator->translate('install:error:emailaddress', array($submissionVars['email']));
1576
			register_error($msg);
1577
			return FALSE;
1578
		}
1579
1580
		return TRUE;
1581
	}
1582
1583
	/**
1584
	 * Create a user account for the admin
1585
	 *
1586
	 * @param array $submissionVars Submitted vars
1587
	 * @param bool  $login          Login in the admin user?
1588
	 *
1589
	 * @return bool
1590
	 */
1591
	protected function createAdminAccount($submissionVars, $login = FALSE) {
1592
		try {
1593
			$guid = register_user(
1594
					$submissionVars['username'],
1595
					$submissionVars['password1'],
1596
					$submissionVars['displayname'],
1597
					$submissionVars['email']
1598
					);
1599
		} catch (Exception $e) {
1600
			register_error($e->getMessage());
1601
			return false;
1602
		}
1603
1604
		if (!$guid) {
1605
			register_error(_elgg_services()->translator->translate('install:admin:cannot_create'));
1606
			return false;
1607
		}
1608
1609
		$user = get_entity($guid);
1610
		if (!$user instanceof ElggUser) {
1611
			register_error(_elgg_services()->translator->translate('install:error:loadadmin'));
1612
			return false;
1613
		}
1614
1615
		elgg_set_ignore_access(TRUE);
1616
		if ($user->makeAdmin() == FALSE) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1617
			register_error(_elgg_services()->translator->translate('install:error:adminaccess'));
1618
		} else {
1619
			_elgg_services()->datalist->set('admin_registered', 1);
1620
		}
1621
		elgg_set_ignore_access(false);
1622
1623
		// add validation data to satisfy user validation plugins
1624
		create_metadata($guid, 'validated', TRUE, '', 0, ACCESS_PUBLIC);
0 ignored issues
show
Documentation introduced by
TRUE is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1625
		create_metadata($guid, 'validated_method', 'admin_user', '', 0, ACCESS_PUBLIC);
1626
1627
		if ($login) {
1628
			$handler = new Elgg\Http\DatabaseSessionHandler(_elgg_services()->db);
1629
1630
			// session.cache_limiter is unfortunately set to "" by the NativeSessionStorage constructor,
1631
			// so we must capture and inject it directly.
1632
			$options = [
1633
				'cache_limiter' => session_cache_limiter(),
1634
			];
1635
			$storage = new Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage($options, $handler);
1636
1637
			$session = new ElggSession(new Symfony\Component\HttpFoundation\Session\Session($storage));
1638
			$session->setName('Elgg');
1639
			_elgg_services()->setValue('session', $session);
1640
			if (login($user) == FALSE) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1641
				register_error(_elgg_services()->translator->translate('install:error:adminlogin'));
1642
			}
1643
		}
1644
1645
		return TRUE;
1646
	}
1647
}
1648