Test Failed
Push — master ( 8c47c2...3acf9f )
by Steve
12:37
created

engine/classes/ElggInstaller.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
use Elgg\Filesystem\Directory;
4
5
/**
6
 * Elgg Installer.
7
 * Controller for installing Elgg. Supports both web-based on CLI installation.
8
 *
9
 * This controller steps the user through the install process. The method for
10
 * each step handles both the GET and POST requests. There is no XSS/CSRF protection
11
 * on the POST processing since the installer is only run once by the administrator.
12
 *
13
 * The installation process can be resumed by hitting the first page. The installer
14
 * will try to figure out where to pick up again.
15
 *
16
 * All the logic for the installation process is in this class, but it depends on
17
 * the core libraries. To do this, we selectively load a subset of the core libraries
18
 * for the first few steps and then load the entire engine once the database and
19
 * site settings are configured. In addition, this controller does its own session
20
 * handling until the database is setup.
21
 *
22
 * There is an aborted attempt in the code at creating the data directory for
23
 * users as a subdirectory of Elgg's root. The idea was to protect this directory
24
 * through a .htaccess file. The problem is that a malicious user can upload a
25
 * .htaccess of his own that overrides the protection for his user directory. The
26
 * best solution is server level configuration that turns off AllowOverride for the
27
 * data directory. See ticket #3453 for discussion on this.
28
 *
29
 * @package    Elgg.Core
30
 * @subpackage Installer
31
 */
32
class ElggInstaller {
33
	
34
	protected $steps = [
35
		'welcome',
36
		'requirements',
37
		'database',
38
		'settings',
39
		'admin',
40
		'complete',
41
		];
42
43
	protected $status = [
44
		'config' => false,
45
		'database' => false,
46
		'settings' => false,
47
		'admin' => false,
48
	];
49
50
	protected $isAction = false;
51
52
	protected $autoLogin = true;
53
54
	private $view_path = '';
55
56
	/**
57
	 * Global Elgg configuration
58
	 *
59
	 * @var \stdClass
60
	 */
61
	private $CONFIG;
62
63
	/**
64
	 * Constructor bootstraps the Elgg engine
65
	 */
66
	public function __construct() {
67
		global $CONFIG;
68
		if (!isset($CONFIG)) {
69
			$CONFIG = new stdClass;
70
		}
71
		
72
		global $_ELGG;
73
		if (!isset($_ELGG)) {
74
			$_ELGG = new stdClass;
75
		}
76
77
		$this->CONFIG = $CONFIG;
78
79
		$this->isAction = isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'POST';
80
81
		$this->bootstrapConfig();
82
83
		$this->bootstrapEngine();
84
85
		_elgg_services()->views->view_path = $this->view_path;
86
		
87
		_elgg_services()->setValue('session', \ElggSession::getMock());
88
89
		elgg_set_viewtype('installation');
90
91
		set_error_handler('_elgg_php_error_handler');
92
		set_exception_handler('_elgg_php_exception_handler');
93
94
		_elgg_services()->config->set('simplecache_enabled', false);
95
		_elgg_services()->translator->registerTranslations(\Elgg\Application::elggDir()->getPath("/install/languages/"), true);
96
		_elgg_services()->views->registerPluginViews(\Elgg\Application::elggDir()->getPath("/"));
97
	}
98
	
99
	/**
100
	 * Dispatches a request to one of the step controllers
101
	 *
102
	 * @param string $step The installation step to run
103
	 *
104
	 * @return void
105
	 * @throws InstallationException
106
	 */
107
	public function run($step) {
108
		global $CONFIG;
109
		
110
		// language needs to be set before the first call to elgg_echo()
111
		$CONFIG->language = 'en';
112
113
		// check if this is a URL rewrite test coming in
114
		$this->processRewriteTest();
115
116
		if (!in_array($step, $this->getSteps())) {
117
			$msg = _elgg_services()->translator->translate('InstallationException:UnknownStep', [$step]);
118
			throw new InstallationException($msg);
119
		}
120
121
		$this->setInstallStatus();
122
	
123
		$this->checkInstallCompletion($step);
124
125
		// check if this is an install being resumed
126
		$this->resumeInstall($step);
127
128
		$this->finishBootstrapping($step);
129
130
		$params = $this->getPostVariables();
131
132
		$this->$step($params);
133
	}
134
135
	/**
136
	 * Set the auto login flag
137
	 *
138
	 * @param bool $flag Auto login
139
	 *
140
	 * @return void
141
	 */
142
	public function setAutoLogin($flag) {
143
		$this->autoLogin = (bool) $flag;
144
	}
145
146
	/**
147
	 * A batch install of Elgg
148
	 *
149
	 * All required parameters must be passed in as an associative array. See
150
	 * $requiredParams for a list of them. This creates the necessary files,
151
	 * loads the database, configures the site settings, and creates the admin
152
	 * account. If it fails, an exception is thrown. It does not check any of
153
	 * the requirements as the multiple step web installer does.
154
	 *
155
	 * If the settings.php file exists, it will use that rather than the parameters
156
	 * passed to this function.
157
	 *
158
	 * @param array $params         Array of key value pairs
159
	 * @param bool  $createHtaccess Should .htaccess be created
160
	 *
161
	 * @return void
162
	 * @throws InstallationException
163
	 */
164
	public function batchInstall(array $params, $createHtaccess = false) {
165
		
166
167
		restore_error_handler();
168
		restore_exception_handler();
169
170
		$defaults = [
171
			'dbhost' => 'localhost',
172
			'dbprefix' => 'elgg_',
173
			'language' => 'en',
174
			'siteaccess' => ACCESS_PUBLIC,
175
			'site_guid' => 1,
176
		];
177
		$params = array_merge($defaults, $params);
178
179
		$requiredParams = [
180
			'dbuser',
181
			'dbpassword',
182
			'dbname',
183
			'sitename',
184
			'wwwroot',
185
			'dataroot',
186
			'displayname',
187
			'email',
188
			'username',
189
			'password',
190
		];
191
		foreach ($requiredParams as $key) {
192
			if (empty($params[$key])) {
193
				$msg = _elgg_services()->translator->translate('install:error:requiredfield', [$key]);
194
				throw new InstallationException($msg);
195
			}
196
		}
197
198
		// password is passed in once
199
		$params['password1'] = $params['password2'] = $params['password'];
200
201
		if ($createHtaccess) {
202
			$rewriteTester = new ElggRewriteTester();
203
			if (!$rewriteTester->createHtaccess($params['wwwroot'], Directory\Local::root()->getPath())) {
0 ignored issues
show
The call to ElggRewriteTester::createHtaccess() has too many arguments starting with \Elgg\Filesystem\Directo...ocal::root()->getPath().

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
204
				throw new InstallationException(_elgg_services()->translator->translate('install:error:htaccess'));
205
			}
206
		}
207
208
		$this->setInstallStatus();
209
210 View Code Duplication
		if (!$this->status['config']) {
211
			if (!$this->createSettingsFile($params)) {
212
				throw new InstallationException(_elgg_services()->translator->translate('install:error:settings'));
213
			}
214
		}
215
216
		if (!$this->connectToDatabase()) {
217
			throw new InstallationException(_elgg_services()->translator->translate('install:error:databasesettings'));
218
		}
219
220 View Code Duplication
		if (!$this->status['database']) {
221
			if (!$this->installDatabase()) {
222
				throw new InstallationException(_elgg_services()->translator->translate('install:error:cannotloadtables'));
223
			}
224
		}
225
226
		// load remaining core libraries
227
		$this->finishBootstrapping('settings');
228
229
		if (!$this->saveSiteSettings($params)) {
230
			throw new InstallationException(_elgg_services()->translator->translate('install:error:savesitesettings'));
231
		}
232
233
		if (!$this->createAdminAccount($params)) {
234
			throw new InstallationException(_elgg_services()->translator->translate('install:admin:cannot_create'));
235
		}
236
	}
237
238
	/**
239
	 * Renders the data passed by a controller
240
	 *
241
	 * @param string $step The current step
242
	 * @param array  $vars Array of vars to pass to the view
243
	 *
244
	 * @return void
245
	 */
246
	protected function render($step, $vars = []) {
247
		$vars['next_step'] = $this->getNextStep($step);
248
249
		$title = _elgg_services()->translator->translate("install:$step");
250
		$body = elgg_view("install/pages/$step", $vars);
251
				
252
		echo elgg_view_page(
253
				$title,
254
				$body,
255
				'default',
256
				[
257
					'step' => $step,
258
					'steps' => $this->getSteps(),
259
					]
260
				);
261
		exit;
262
	}
263
264
	/**
265
	 * Step controllers
266
	 */
267
268
	/**
269
	 * Welcome controller
270
	 *
271
	 * @param array $vars Not used
272
	 *
273
	 * @return void
274
	 */
275
	protected function welcome($vars) {
276
		$this->render('welcome');
277
	}
278
279
	/**
280
	 * Requirements controller
281
	 *
282
	 * Checks version of php, libraries, permissions, and rewrite rules
283
	 *
284
	 * @param array $vars Vars
285
	 *
286
	 * @return void
287
	 */
288
	protected function requirements($vars) {
0 ignored issues
show
The parameter $vars is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
289
290
		$report = [];
291
292
		// check PHP parameters and libraries
293
		$this->checkPHP($report);
294
295
		// check URL rewriting
296
		$this->checkRewriteRules($report);
297
298
		// check for existence of settings file
299
		if ($this->checkSettingsFile($report) != true) {
300
			// no file, so check permissions on engine directory
301
			$this->isInstallDirWritable($report);
302
		}
303
304
		// check the database later
305
		$report['database'] = [[
306
			'severity' => 'info',
307
			'message' => _elgg_services()->translator->translate('install:check:database')
308
		]];
309
310
		// any failures?
311
		$numFailures = $this->countNumConditions($report, 'failure');
312
313
		// any warnings
314
		$numWarnings = $this->countNumConditions($report, 'warning');
315
316
317
		$params = [
318
			'report' => $report,
319
			'num_failures' => $numFailures,
320
			'num_warnings' => $numWarnings,
321
		];
322
323
		$this->render('requirements', $params);
324
	}
325
326
	/**
327
	 * Database set up controller
328
	 *
329
	 * Creates the settings.php file and creates the database tables
330
	 *
331
	 * @param array $submissionVars Submitted form variables
332
	 *
333
	 * @return void
334
	 */
335
	protected function database($submissionVars) {
336
337
		$formVars = [
338
			'dbuser' => [
339
				'type' => 'text',
340
				'value' => '',
341
				'required' => true,
342
				],
343
			'dbpassword' => [
344
				'type' => 'password',
345
				'value' => '',
346
				'required' => false,
347
				],
348
			'dbname' => [
349
				'type' => 'text',
350
				'value' => '',
351
				'required' => true,
352
				],
353
			'dbhost' => [
354
				'type' => 'text',
355
				'value' => 'localhost',
356
				'required' => true,
357
				],
358
			'dbprefix' => [
359
				'type' => 'text',
360
				'value' => 'elgg_',
361
				'required' => true,
362
				],
363
			'dataroot' => [
364
				'type' => 'text',
365
				'value' => '',
366
				'required' => true,
367
			],
368
			'wwwroot' => [
369
				'type' => 'url',
370
				'value' => _elgg_services()->config->wwwroot,
371
				'required' => true,
372
			],
373
			'timezone' => [
374
				'type' => 'dropdown',
375
				'value' => 'UTC',
376
				'options' => \DateTimeZone::listIdentifiers(),
377
				'required' => true
378
			]
379
		];
380
381
		if ($this->checkSettingsFile()) {
382
			// user manually created settings file so we fake out action test
383
			$this->isAction = true;
384
		}
385
386
		if ($this->isAction) {
387
			do {
388
				// only create settings file if it doesn't exist
389
				if (!$this->checkSettingsFile()) {
390
					if (!$this->validateDatabaseVars($submissionVars, $formVars)) {
391
						// error so we break out of action and serve same page
392
						break;
393
					}
394
395
					if (!$this->createSettingsFile($submissionVars)) {
396
						break;
397
					}
398
				}
399
400
				// check db version and connect
401
				if (!$this->connectToDatabase()) {
402
					break;
403
				}
404
405
				if (!$this->installDatabase()) {
406
					break;
407
				}
408
409
				system_message(_elgg_services()->translator->translate('install:success:database'));
410
411
				$this->continueToNextStep('database');
412
			} while (false);  // PHP doesn't support breaking out of if statements
413
		}
414
415
		$formVars = $this->makeFormSticky($formVars, $submissionVars);
416
417
		$params = ['variables' => $formVars,];
418
419
		if ($this->checkSettingsFile()) {
420
			// settings file exists and we're here so failed to create database
421
			$params['failure'] = true;
422
		}
423
424
		$this->render('database', $params);
425
	}
426
427
	/**
428
	 * Site settings controller
429
	 *
430
	 * Sets the site name, URL, data directory, etc.
431
	 *
432
	 * @param array $submissionVars Submitted vars
433
	 *
434
	 * @return void
435
	 */
436
	protected function settings($submissionVars) {
437
		$formVars = [
438
			'sitename' => [
439
				'type' => 'text',
440
				'value' => 'My New Community',
441
				'required' => true,
442
				],
443
			'siteemail' => [
444
				'type' => 'email',
445
				'value' => '',
446
				'required' => false,
447
				],
448
			'siteaccess' => [
449
				'type' => 'access',
450
				'value' => ACCESS_PUBLIC,
451
				'required' => true,
452
				],
453
		];
454
455
		// if Apache, we give user option of having Elgg create data directory
456
		//if (ElggRewriteTester::guessWebServer() == 'apache') {
457
		//	$formVars['dataroot']['type'] = 'combo';
458
		//	$GLOBALS['_ELGG']->translations['en']['install:settings:help:dataroot'] =
459
		//			$GLOBALS['_ELGG']->translations['en']['install:settings:help:dataroot:apache'];
460
		//}
461
462
		if ($this->isAction) {
463
			do {
464
				//if (!$this->createDataDirectory($submissionVars, $formVars)) {
465
				//	break;
466
				//}
467
468
				if (!$this->validateSettingsVars($submissionVars, $formVars)) {
469
					break;
470
				}
471
472
				if (!$this->saveSiteSettings($submissionVars)) {
473
					break;
474
				}
475
476
				system_message(_elgg_services()->translator->translate('install:success:settings'));
477
478
				$this->continueToNextStep('settings');
479
			} while (false);  // PHP doesn't support breaking out of if statements
480
		}
481
482
		$formVars = $this->makeFormSticky($formVars, $submissionVars);
483
484
		$this->render('settings', ['variables' => $formVars]);
485
	}
486
487
	/**
488
	 * Admin account controller
489
	 *
490
	 * Creates an admin user account
491
	 *
492
	 * @param array $submissionVars Submitted vars
493
	 *
494
	 * @return void
495
	 */
496
	protected function admin($submissionVars) {
497
		$formVars = [
498
			'displayname' => [
499
				'type' => 'text',
500
				'value' => '',
501
				'required' => true,
502
				],
503
			'email' => [
504
				'type' => 'email',
505
				'value' => '',
506
				'required' => true,
507
				],
508
			'username' => [
509
				'type' => 'text',
510
				'value' => '',
511
				'required' => true,
512
				],
513
			'password1' => [
514
				'type' => 'password',
515
				'value' => '',
516
				'required' => true,
517
				'pattern' => '.{6,}',
518
				],
519
			'password2' => [
520
				'type' => 'password',
521
				'value' => '',
522
				'required' => true,
523
				],
524
		];
525
		
526
		if ($this->isAction) {
527
			call_user_func(function () use ($submissionVars, $formVars) {
528
				if (!$this->validateAdminVars($submissionVars, $formVars)) {
529
					return;
530
				}
531
532
				if (!$this->createAdminAccount($submissionVars, $this->autoLogin)) {
533
					return;
534
				}
535
536
				system_message(_elgg_services()->translator->translate('install:success:admin'));
537
538
				$this->continueToNextStep('admin');
539
			});
540
		}
541
542
		// bit of a hack to get the password help to show right number of characters
543
		
544
		$lang = _elgg_services()->translator->getCurrentLanguage();
545
		$GLOBALS['_ELGG']->translations[$lang]['install:admin:help:password1'] =
546
				sprintf($GLOBALS['_ELGG']->translations[$lang]['install:admin:help:password1'],
547
				$this->CONFIG->min_password_length);
548
549
		$formVars = $this->makeFormSticky($formVars, $submissionVars);
550
551
		$this->render('admin', ['variables' => $formVars]);
552
	}
553
554
	/**
555
	 * Controller for last step
556
	 *
557
	 * @return void
558
	 */
559
	protected function complete() {
560
561
		// nudge to check out settings
562
		$link = elgg_format_element([
563
			'#tag_name' => 'a',
564
			'#text' => _elgg_services()->translator->translate('install:complete:admin_notice:link_text'),
565
			'href' => elgg_normalize_url('admin/settings/basic'),
566
		]);
567
		$notice = _elgg_services()->translator->translate('install:complete:admin_notice', [$link]);
568
		elgg_add_admin_notice('fresh_install', $notice);
569
570
		$this->render('complete');
571
	}
572
573
	/**
574
	 * Step management
575
	 */
576
577
	/**
578
	 * Get an array of steps
579
	 *
580
	 * @return array
581
	 */
582
	protected function getSteps() {
583
		return $this->steps;
584
	}
585
586
	/**
587
	 * Forwards the browser to the next step
588
	 *
589
	 * @param string $currentStep Current installation step
590
	 *
591
	 * @return void
592
	 */
593
	protected function continueToNextStep($currentStep) {
594
		$this->isAction = false;
595
		forward($this->getNextStepUrl($currentStep));
596
	}
597
598
	/**
599
	 * Get the next step as a string
600
	 *
601
	 * @param string $currentStep Current installation step
602
	 *
603
	 * @return string
604
	 */
605
	protected function getNextStep($currentStep) {
606
		$index = 1 + array_search($currentStep, $this->steps);
607
		if (isset($this->steps[$index])) {
608
			return $this->steps[$index];
609
		} else {
610
			return null;
611
		}
612
	}
613
614
	/**
615
	 * Get the URL of the next step
616
	 *
617
	 * @param string $currentStep Current installation step
618
	 *
619
	 * @return string
620
	 */
621
	protected function getNextStepUrl($currentStep) {
622
		$nextStep = $this->getNextStep($currentStep);
623
		return _elgg_services()->config->wwwroot . "install.php?step=$nextStep";
624
	}
625
626
	/**
627
	 * Check the different install steps for completion
628
	 *
629
	 * @return void
630
	 * @throws InstallationException
631
	 */
632
	protected function setInstallStatus() {
633
		$settings_found = false;
634
		foreach (_elgg_services()->config->getSettingsPaths() as $path) {
635
			if (is_file($path) && is_readable($path)) {
636
				$settings_found = true;
637
				break;
638
			}
639
		}
640
641
		if (!$settings_found) {
642
			return;
643
		}
644
645
		$this->loadSettingsFile();
646
647
		$this->status['config'] = true;
648
649
		// must be able to connect to database to jump install steps
650
		$dbSettingsPass = $this->checkDatabaseSettings(
651
				$this->CONFIG->dbuser,
652
				$this->CONFIG->dbpass,
653
				$this->CONFIG->dbname,
654
				$this->CONFIG->dbhost
655
				);
656
657
		if ($dbSettingsPass == false) {
658
			return;
659
		}
660
661
		if (!include_once(\Elgg\Application::elggDir()->getPath("engine/lib/database.php"))) {
662
			throw new InstallationException(_elgg_services()->translator->translate('InstallationException:MissingLibrary', ['database.php']));
663
		}
664
665
		// check that the config table has been created
666
		$query = "show tables";
667
		$result = _elgg_services()->db->getData($query);
668
		if ($result) {
669
			foreach ($result as $table) {
670
				$table = (array) $table;
671
				if (in_array("{$this->CONFIG->dbprefix}config", $table)) {
672
					$this->status['database'] = true;
673
				}
674
			}
675
			if ($this->status['database'] == false) {
676
				return;
677
			}
678
		} else {
679
			// no tables
680
			return;
681
		}
682
683
		// check that the config table has entries
684
		$query = "SELECT COUNT(*) AS total FROM {$this->CONFIG->dbprefix}config";
685
		$result = _elgg_services()->db->getData($query);
686
		if ($result && $result[0]->total > 0) {
687
			$this->status['settings'] = true;
688
		} else {
689
			return;
690
		}
691
692
		// check that the users entity table has an entry
693
		$query = "SELECT COUNT(*) AS total FROM {$this->CONFIG->dbprefix}users_entity";
694
		$result = _elgg_services()->db->getData($query);
695
		if ($result && $result[0]->total > 0) {
696
			$this->status['admin'] = true;
697
		} else {
698
			return;
699
		}
700
	}
701
702
	/**
703
	 * Security check to ensure the installer cannot be run after installation
704
	 * has finished. If this is detected, the viewer is sent to the front page.
705
	 *
706
	 * @param string $step Installation step to check against
707
	 *
708
	 * @return void
709
	 */
710
	protected function checkInstallCompletion($step) {
711
		if ($step != 'complete') {
712
			if (!in_array(false, $this->status)) {
713
				// install complete but someone is trying to view an install page
714
				forward();
715
			}
716
		}
717
	}
718
719
	/**
720
	 * Check if this is a case of a install being resumed and figure
721
	 * out where to continue from. Returns the best guess on the step.
722
	 *
723
	 * @param string $step Installation step to resume from
724
	 *
725
	 * @return string
726
	 */
727
	protected function resumeInstall($step) {
728
		// only do a resume from the first step
729
		if ($step !== 'welcome') {
730
			return;
731
		}
732
733
		if ($this->status['database'] == false) {
734
			return;
735
		}
736
737
		if ($this->status['settings'] == false) {
738
			forward("install.php?step=settings");
739
		}
740
741
		if ($this->status['admin'] == false) {
742
			forward("install.php?step=admin");
743
		}
744
745
		// everything appears to be set up
746
		forward("install.php?step=complete");
747
	}
748
749
	/**
750
	 * Bootstraping
751
	 */
752
753
	/**
754
	 * Load the essential libraries of the engine
755
	 *
756
	 * @return void
757
	 */
758
	protected function bootstrapEngine() {
759
		$config = new \Elgg\Config($this->CONFIG);
760
		$services = new \Elgg\Di\ServiceProvider($config);
761
		(new \Elgg\Application($services))->loadCore();
762
	}
763
764
	/**
765
	 * Load remaining engine libraries and complete bootstrapping
766
	 *
767
	 * @param string $step Which step to boot strap for. Required because
768
	 *                     boot strapping is different until the DB is populated.
769
	 *
770
	 * @return void
771
	 * @throws InstallationException
772
	 */
773
	protected function finishBootstrapping($step) {
774
775
		$dbIndex = array_search('database', $this->getSteps());
776
		$settingsIndex = array_search('settings', $this->getSteps());
777
		$adminIndex = array_search('admin', $this->getSteps());
778
		$completeIndex = array_search('complete', $this->getSteps());
779
		$stepIndex = array_search($step, $this->getSteps());
780
781
		// To log in the user, we need to use the Elgg core session handling.
782
		// Otherwise, use default php session handling
783
		$useElggSession = ($stepIndex == $adminIndex && $this->isAction) ||
784
				$stepIndex == $completeIndex;
785
		if (!$useElggSession) {
786
			session_name('Elgg_install');
787
			session_start();
788
		}
789
790
		if ($stepIndex > $dbIndex) {
791
			// once the database has been created, load rest of engine
792
			
793
			$lib_dir = \Elgg\Application::elggDir()->chroot('/engine/lib/');
794
795
			$this->loadSettingsFile();
796
797
			$lib_files = [
798
				// these want to be loaded first apparently?
799
				'autoloader.php',
800
				'database.php',
801
				'actions.php',
802
803
				'admin.php',
804
				'annotations.php',
805
				'cron.php',
806
				'entities.php',
807
				'filestore.php',
808
				'group.php',
809
				'mb_wrapper.php',
810
				'memcache.php',
811
				'metadata.php',
812
				'metastrings.php',
813
				'navigation.php',
814
				'notification.php',
815
				'objects.php',
816
				'pagehandler.php',
817
				'pam.php',
818
				'plugins.php',
819
				'private_settings.php',
820
				'relationships.php',
821
				'river.php',
822
				'sites.php',
823
				'statistics.php',
824
				'tags.php',
825
				'user_settings.php',
826
				'users.php',
827
				'upgrade.php',
828
				'widgets.php',
829
			];
830
831
			foreach ($lib_files as $file) {
832
				if (!include_once($lib_dir->getPath($file))) {
833
					throw new InstallationException('InstallationException:MissingLibrary', [$file]);
834
				}
835
			}
836
837
			_elgg_services()->db->setupConnections();
838
			_elgg_services()->translator->registerTranslations(\Elgg\Application::elggDir()->getPath("/languages/"));
839
			$this->CONFIG->language = 'en';
840
841
			if ($stepIndex > $settingsIndex) {
842
				$this->CONFIG->site_guid = 1;
843
				$this->CONFIG->site = get_entity(1);
844
				_elgg_services()->config->getCookieConfig();
845
				_elgg_session_boot();
846
			}
847
848
			_elgg_services()->events->trigger('init', 'system');
849
		}
850
	}
851
852
	/**
853
	 * Set up configuration variables
854
	 *
855
	 * @return void
856
	 */
857
	protected function bootstrapConfig() {
858
		$this->CONFIG->installer_running = true;
859
860
		if (empty($this->CONFIG->dbencoding)) {
861
			$this->CONFIG->dbencoding = 'utf8mb4';
862
		}
863
		$this->CONFIG->wwwroot = $this->getBaseUrl();
864
		$this->CONFIG->url = $this->CONFIG->wwwroot;
865
		$this->CONFIG->path = Directory\Local::root()->getPath('/');
866
		$this->view_path = $this->CONFIG->path . 'views/';
867
		$this->CONFIG->pluginspath = $this->CONFIG->path . 'mod/';
868
		$this->CONFIG->context = [];
869
		$this->CONFIG->entity_types = ['group', 'object', 'site', 'user'];
870
871
		// required by elgg_view_page()
872
		$this->CONFIG->sitename = '';
873
		$this->CONFIG->sitedescription = '';
874
875
		// required by Elgg\Config::get
876
		$this->CONFIG->site_guid = 1;
877
	}
878
	
879
	/**
880
	 * @return bool Whether the install process is encrypted.
881
	 */
882
	private function isHttps() {
883
		return (!empty($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on") ||
884
			(!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443);
885
	}
886
887
	/**
888
	 * Get the best guess at the base URL
889
	 *
890
	 * @note Cannot use current_page_url() because it depends on $this->CONFIG->wwwroot
891
	 * @todo Should this be a core function?
892
	 *
893
	 * @return string
894
	 */
895
	protected function getBaseUrl() {
896
		$protocol = $this->isHttps() ? 'https' : 'http';
897
		
898
		if (isset($_SERVER["SERVER_PORT"])) {
899
			$port = ':' . $_SERVER["SERVER_PORT"];
900
		} else {
901
			$port = '';
902
		}
903
		if ($port == ':80' || $port == ':443') {
904
			$port = '';
905
		}
906
		$uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
907
		$cutoff = strpos($uri, 'install.php');
908
		$uri = substr($uri, 0, $cutoff);
909
		$serverName = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '';
910
911
		return "$protocol://{$serverName}$port{$uri}";
912
	}
913
914
	/**
915
	 * Load settings.php
916
	 *
917
	 * @return void
918
	 * @throws InstallationException
919
	 */
920
	protected function loadSettingsFile() {
921
		try {
922
			_elgg_services()->config->loadSettingsFile();
923
		} catch (\Exception $e) {
924
			$msg = _elgg_services()->translator->translate('InstallationException:CannotLoadSettings');
925
			throw new InstallationException($msg, 0, $e);
926
		}
927
	}
928
929
	/**
930
	 * Action handling methods
931
	 */
932
933
	/**
934
	 * Return an associative array of post variables
935
	 * (could be selective based on expected variables)
936
	 *
937
	 * Does not filter as person installing the site should not be attempting
938
	 * XSS attacks. If filtering is added, it should not be done for passwords.
939
	 *
940
	 * @return array
941
	 */
942
	protected function getPostVariables() {
943
		$vars = [];
944
		foreach ($_POST as $k => $v) {
945
			$vars[$k] = $v;
946
		}
947
		return $vars;
948
	}
949
950
	/**
951
	 * If form is reshown, remember previously submitted variables
952
	 *
953
	 * @param array $formVars       Vars int he form
954
	 * @param array $submissionVars Submitted vars
955
	 *
956
	 * @return array
957
	 */
958
	protected function makeFormSticky($formVars, $submissionVars) {
959
		foreach ($submissionVars as $field => $value) {
960
			$formVars[$field]['value'] = $value;
961
		}
962
		return $formVars;
963
	}
964
965
	/* Requirement checks support methods */
966
967
	/**
968
	 * Indicates whether the webserver can add settings.php on its own or not.
969
	 *
970
	 * @param array $report The requirements report object
971
	 *
972
	 * @return bool
973
	 */
974
	protected function isInstallDirWritable(&$report) {
975
		$root = Directory\Local::root()->getPath();
976
		$abs_path = \Elgg\Application::elggDir()->getPath('elgg-config');
977
978
		if (0 === strpos($abs_path, $root)) {
979
			$relative_path = substr($abs_path, strlen($root));
980
		} else {
981
			$relative_path = $abs_path;
982
		}
983
		$relative_path = rtrim($relative_path, '/\\');
984
985
		$writable = is_writable(Directory\Local::root()->getPath('elgg-config'));
986
		if (!$writable) {
987
			$report['settings'] = [
988
				[
989
					'severity' => 'failure',
990
					'message' => _elgg_services()->translator->translate('install:check:installdir', [$relative_path]),
991
				]
992
			];
993
			return false;
994
		}
995
996
		return true;
997
	}
998
999
	/**
1000
	 * Check that the settings file exists
1001
	 *
1002
	 * @param array $report The requirements report array
1003
	 *
1004
	 * @return bool
1005
	 */
1006
	protected function checkSettingsFile(&$report = []) {
1007
		if (!is_file($this->getSettingsPath())) {
1008
			return false;
1009
		}
1010
1011
		if (!is_readable($this->getSettingsPath())) {
1012
			$report['settings'] = [
1013
				[
1014
					'severity' => 'failure',
1015
					'message' => _elgg_services()->translator->translate('install:check:readsettings'),
1016
				]
1017
			];
1018
		}
1019
		
1020
		return true;
1021
	}
1022
	
1023
	/**
1024
	 * Returns the path to the root settings.php file.
1025
	 *
1026
	 * @return string
1027
	 */
1028
	private function getSettingsPath() {
1029
		return Directory\Local::root()->getPath("elgg-config/settings.php");
1030
	}
1031
1032
	/**
1033
	 * Check version of PHP, extensions, and variables
1034
	 *
1035
	 * @param array $report The requirements report array
1036
	 *
1037
	 * @return void
1038
	 */
1039
	protected function checkPHP(&$report) {
1040
		$phpReport = [];
1041
1042
		$min_php_version = '5.6.0';
1043
		if (version_compare(PHP_VERSION, $min_php_version, '<')) {
1044
			$phpReport[] = [
1045
				'severity' => 'failure',
1046
				'message' => _elgg_services()->translator->translate('install:check:php:version', [$min_php_version, PHP_VERSION])
1047
			];
1048
		}
1049
1050
		$this->checkPhpExtensions($phpReport);
1051
1052
		$this->checkPhpDirectives($phpReport);
1053
1054
		if (count($phpReport) == 0) {
1055
			$phpReport[] = [
1056
				'severity' => 'pass',
1057
				'message' => _elgg_services()->translator->translate('install:check:php:success')
1058
			];
1059
		}
1060
1061
		$report['php'] = $phpReport;
1062
	}
1063
1064
	/**
1065
	 * Check the server's PHP extensions
1066
	 *
1067
	 * @param array $phpReport The PHP requirements report array
1068
	 *
1069
	 * @return void
1070
	 */
1071
	protected function checkPhpExtensions(&$phpReport) {
1072
		$extensions = get_loaded_extensions();
1073
		$requiredExtensions = [
1074
			'pdo_mysql',
1075
			'json',
1076
			'xml',
1077
			'gd',
1078
		];
1079 View Code Duplication
		foreach ($requiredExtensions as $extension) {
1080
			if (!in_array($extension, $extensions)) {
1081
				$phpReport[] = [
1082
					'severity' => 'failure',
1083
					'message' => _elgg_services()->translator->translate('install:check:php:extension', [$extension])
1084
				];
1085
			}
1086
		}
1087
1088
		$recommendedExtensions = [
1089
			'mbstring',
1090
		];
1091 View Code Duplication
		foreach ($recommendedExtensions as $extension) {
1092
			if (!in_array($extension, $extensions)) {
1093
				$phpReport[] = [
1094
					'severity' => 'warning',
1095
					'message' => _elgg_services()->translator->translate('install:check:php:extension:recommend', [$extension])
1096
				];
1097
			}
1098
		}
1099
	}
1100
1101
	/**
1102
	 * Check PHP parameters
1103
	 *
1104
	 * @param array $phpReport The PHP requirements report array
1105
	 *
1106
	 * @return void
1107
	 */
1108
	protected function checkPhpDirectives(&$phpReport) {
1109
		if (ini_get('open_basedir')) {
1110
			$phpReport[] = [
1111
				'severity' => 'warning',
1112
				'message' => _elgg_services()->translator->translate("install:check:php:open_basedir")
1113
			];
1114
		}
1115
1116
		if (ini_get('safe_mode')) {
1117
			$phpReport[] = [
1118
				'severity' => 'warning',
1119
				'message' => _elgg_services()->translator->translate("install:check:php:safe_mode")
1120
			];
1121
		}
1122
1123
		if (ini_get('arg_separator.output') !== '&') {
1124
			$separator = htmlspecialchars(ini_get('arg_separator.output'));
1125
			$msg = _elgg_services()->translator->translate("install:check:php:arg_separator", [$separator]);
1126
			$phpReport[] = [
1127
				'severity' => 'failure',
1128
				'message' => $msg,
1129
			];
1130
		}
1131
1132
		if (ini_get('register_globals')) {
1133
			$phpReport[] = [
1134
				'severity' => 'failure',
1135
				'message' => _elgg_services()->translator->translate("install:check:php:register_globals")
1136
			];
1137
		}
1138
1139
		if (ini_get('session.auto_start')) {
1140
			$phpReport[] = [
1141
				'severity' => 'failure',
1142
				'message' => _elgg_services()->translator->translate("install:check:php:session.auto_start")
1143
			];
1144
		}
1145
	}
1146
1147
	/**
1148
	 * Confirm that the rewrite rules are firing
1149
	 *
1150
	 * @param array $report The requirements report array
1151
	 *
1152
	 * @return void
1153
	 */
1154
	protected function checkRewriteRules(&$report) {
1155
		$tester = new ElggRewriteTester();
1156
		$url = _elgg_services()->config->wwwroot . "rewrite.php";
1157
		$report['rewrite'] = [$tester->run($url, Directory\Local::root()->getPath())];
1158
	}
1159
1160
	/**
1161
	 * Check if the request is coming from the URL rewrite test on the
1162
	 * requirements page.
1163
	 *
1164
	 * @return void
1165
	 */
1166
	protected function processRewriteTest() {
1167
		if (strpos($_SERVER['REQUEST_URI'], 'rewrite.php') !== false) {
1168
			echo \Elgg\Application::REWRITE_TEST_OUTPUT;
1169
			exit;
1170
		}
1171
	}
1172
1173
	/**
1174
	 * Count the number of failures in the requirements report
1175
	 *
1176
	 * @param array  $report    The requirements report array
1177
	 * @param string $condition 'failure' or 'warning'
1178
	 *
1179
	 * @return int
1180
	 */
1181
	protected function countNumConditions($report, $condition) {
1182
		$count = 0;
1183
		foreach ($report as $category => $checks) {
1184
			foreach ($checks as $check) {
1185
				if ($check['severity'] === $condition) {
1186
					$count++;
1187
				}
1188
			}
1189
		}
1190
1191
		return $count;
1192
	}
1193
1194
1195
	/**
1196
	 * Database support methods
1197
	 */
1198
1199
	/**
1200
	 * Validate the variables for the database step
1201
	 *
1202
	 * @param array $submissionVars Submitted vars
1203
	 * @param array $formVars       Vars in the form
1204
	 *
1205
	 * @return bool
1206
	 */
1207
	protected function validateDatabaseVars($submissionVars, $formVars) {
1208
1209 View Code Duplication
		foreach ($formVars as $field => $info) {
1210
			if ($info['required'] == true && !$submissionVars[$field]) {
1211
				$name = _elgg_services()->translator->translate("install:database:label:$field");
1212
				register_error(_elgg_services()->translator->translate('install:error:requiredfield', [$name]));
1213
				return false;
1214
			}
1215
		}
1216
1217
		// check that data root is absolute path
1218
		if (stripos(PHP_OS, 'win') === 0) {
1219 View Code Duplication
			if (strpos($submissionVars['dataroot'], ':') !== 1) {
1220
				$msg = _elgg_services()->translator->translate('install:error:relative_path', [$submissionVars['dataroot']]);
1221
				register_error($msg);
1222
				return false;
1223
			}
1224 View Code Duplication
		} else {
1225
			if (strpos($submissionVars['dataroot'], '/') !== 0) {
1226
				$msg = _elgg_services()->translator->translate('install:error:relative_path', [$submissionVars['dataroot']]);
1227
				register_error($msg);
1228
				return false;
1229
			}
1230
		}
1231
1232
		// check that data root exists
1233 View Code Duplication
		if (!is_dir($submissionVars['dataroot'])) {
1234
			$msg = _elgg_services()->translator->translate('install:error:datadirectoryexists', [$submissionVars['dataroot']]);
1235
			register_error($msg);
1236
			return false;
1237
		}
1238
1239
		// check that data root is writable
1240 View Code Duplication
		if (!is_writable($submissionVars['dataroot'])) {
1241
			$msg = _elgg_services()->translator->translate('install:error:writedatadirectory', [$submissionVars['dataroot']]);
1242
			register_error($msg);
1243
			return false;
1244
		}
1245
1246
		if (!isset($this->CONFIG->data_dir_override) || !$this->CONFIG->data_dir_override) {
1247
			// check that data root is not subdirectory of Elgg root
1248
			if (stripos($submissionVars['dataroot'], $this->CONFIG->path) === 0) {
1249
				$msg = _elgg_services()->translator->translate('install:error:locationdatadirectory', [$submissionVars['dataroot']]);
1250
				register_error($msg);
1251
				return false;
1252
			}
1253
		}
1254
1255
		// according to postgres documentation: SQL identifiers and key words must
1256
		// begin with a letter (a-z, but also letters with diacritical marks and
1257
		// non-Latin letters) or an underscore (_). Subsequent characters in an
1258
		// identifier or key word can be letters, underscores, digits (0-9), or dollar signs ($).
1259
		// Refs #4994
1260
		if (!preg_match("/^[a-zA-Z_][\w]*$/", $submissionVars['dbprefix'])) {
1261
			register_error(_elgg_services()->translator->translate('install:error:database_prefix'));
1262
			return false;
1263
		}
1264
1265
		return $this->checkDatabaseSettings(
1266
					$submissionVars['dbuser'],
1267
					$submissionVars['dbpassword'],
1268
					$submissionVars['dbname'],
1269
					$submissionVars['dbhost']
1270
				);
1271
	}
1272
1273
	/**
1274
	 * Confirm the settings for the database
1275
	 *
1276
	 * @param string $user     Username
1277
	 * @param string $password Password
1278
	 * @param string $dbname   Database name
1279
	 * @param string $host     Host
1280
	 *
1281
	 * @return bool
1282
	 */
1283
	protected function checkDatabaseSettings($user, $password, $dbname, $host) {
1284
		$config = new \Elgg\Database\Config((object) [
1285
			'dbhost' => $host,
1286
			'dbuser' => $user,
1287
			'dbpass' => $password,
1288
			'dbname' => $dbname,
1289
			'dbencoding' => 'utf8mb4',
1290
		]);
1291
		$db = new \Elgg\Database($config);
1292
1293
		try {
1294
			$db->getDataRow("SELECT 1");
1295
		} catch (DatabaseException $e) {
1296
			if (0 === strpos($e->getMessage(), "Elgg couldn't connect")) {
1297
				register_error(_elgg_services()->translator->translate('install:error:databasesettings'));
1298
			} else {
1299
				register_error(_elgg_services()->translator->translate('install:error:nodatabase', [$dbname]));
1300
			}
1301
			return false;
1302
		}
1303
1304
		// check MySQL version
1305
		$version = $db->getServerVersion(\Elgg\Database\Config::READ_WRITE);
1306
		if (version_compare($version, '5.5.3', '<')) {
1307
			register_error(_elgg_services()->translator->translate('install:error:oldmysql2', [$version]));
1308
			return false;
1309
		}
1310
1311
		return true;
1312
	}
1313
1314
	/**
1315
	 * Writes the settings file to the engine directory
1316
	 *
1317
	 * @param array $params Array of inputted params from the user
1318
	 *
1319
	 * @return bool
1320
	 */
1321
	protected function createSettingsFile($params) {
1322
		$template = \Elgg\Application::elggDir()->getContents("elgg-config/settings.example.php");
1323
		if (!$template) {
1324
			register_error(_elgg_services()->translator->translate('install:error:readsettingsphp'));
1325
			return false;
1326
		}
1327
1328
		foreach ($params as $k => $v) {
1329
			$template = str_replace("{{" . $k . "}}", $v, $template);
1330
		}
1331
1332
		$result = file_put_contents($this->getSettingsPath(), $template);
1333
		if (!$result) {
1334
			register_error(_elgg_services()->translator->translate('install:error:writesettingphp'));
1335
			return false;
1336
		}
1337
1338
		return true;
1339
	}
1340
1341
	/**
1342
	 * Bootstrap database connection before entire engine is available
1343
	 *
1344
	 * @return bool
1345
	 */
1346
	protected function connectToDatabase() {
1347
		if (!include_once($this->getSettingsPath())) {
1348
			register_error('Elgg could not load the settings file. It does not exist or there is a file permissions issue.');
1349
			return false;
1350
		}
1351
1352
		if (!include_once(\Elgg\Application::elggDir()->getPath("engine/lib/database.php"))) {
1353
			register_error('Could not load database.php');
1354
			return false;
1355
		}
1356
1357
		try {
1358
			_elgg_services()->db->setupConnections();
1359
		} catch (DatabaseException $e) {
1360
			register_error($e->getMessage());
1361
			return false;
1362
		}
1363
1364
		return true;
1365
	}
1366
1367
	/**
1368
	 * Create the database tables
1369
	 *
1370
	 * @return bool
1371
	 */
1372
	protected function installDatabase() {
1373
		try {
1374
			_elgg_services()->db->runSqlScript(\Elgg\Application::elggDir()->getPath("/engine/schema/mysql.sql"));
1375
		} catch (Exception $e) {
1376
			$msg = $e->getMessage();
1377
			if (strpos($msg, 'already exists')) {
1378
				$msg = _elgg_services()->translator->translate('install:error:tables_exist');
1379
			}
1380
			register_error($msg);
1381
			return false;
1382
		}
1383
1384
		return true;
1385
	}
1386
1387
	/**
1388
	 * Site settings support methods
1389
	 */
1390
1391
	/**
1392
	 * Create the data directory if requested
1393
	 *
1394
	 * @param array $submissionVars Submitted vars
1395
	 * @param array $formVars       Variables in the form
1396
	 *
1397
	 * @return bool
1398
	 */
1399
	protected function createDataDirectory(&$submissionVars, $formVars) {
1400
		// did the user have option of Elgg creating the data directory
1401
		if ($formVars['dataroot']['type'] != 'combo') {
1402
			return true;
1403
		}
1404
1405
		// did the user select the option
1406
		if ($submissionVars['dataroot'] != 'dataroot-checkbox') {
1407
			return true;
1408
		}
1409
1410
		$dir = sanitise_filepath($submissionVars['path']) . 'data';
1411
		if (file_exists($dir) || mkdir($dir, 0700)) {
1412
			$submissionVars['dataroot'] = $dir;
1413
			if (!file_exists("$dir/.htaccess")) {
1414
				$htaccess = "Order Deny,Allow\nDeny from All\n";
1415
				if (!file_put_contents("$dir/.htaccess", $htaccess)) {
1416
					return false;
1417
				}
1418
			}
1419
			return true;
1420
		}
1421
1422
		return false;
1423
	}
1424
1425
	/**
1426
	 * Validate the site settings form variables
1427
	 *
1428
	 * @param array $submissionVars Submitted vars
1429
	 * @param array $formVars       Vars in the form
1430
	 *
1431
	 * @return bool
1432
	 */
1433
	protected function validateSettingsVars($submissionVars, $formVars) {
1434
		foreach ($formVars as $field => $info) {
1435
			$submissionVars[$field] = trim($submissionVars[$field]);
1436
			if ($info['required'] == true && $submissionVars[$field] === '') {
1437
				$name = _elgg_services()->translator->translate("install:settings:label:$field");
1438
				register_error(_elgg_services()->translator->translate('install:error:requiredfield', [$name]));
1439
				return false;
1440
			}
1441
		}
1442
1443
		// check that email address is email address
1444 View Code Duplication
		if ($submissionVars['siteemail'] && !is_email_address($submissionVars['siteemail'])) {
1445
			$msg = _elgg_services()->translator->translate('install:error:emailaddress', [$submissionVars['siteemail']]);
1446
			register_error($msg);
1447
			return false;
1448
		}
1449
1450
		// @todo check that url is a url
1451
		// @note filter_var cannot be used because it doesn't work on international urls
1452
1453
		return true;
1454
	}
1455
1456
	/**
1457
	 * Initialize the site including site entity, plugins, and configuration
1458
	 *
1459
	 * @param array $submissionVars Submitted vars
1460
	 *
1461
	 * @return bool
1462
	 */
1463
	protected function saveSiteSettings($submissionVars) {
1464
		$site = new ElggSite();
1465
		$site->name = strip_tags($submissionVars['sitename']);
1466
		$site->access_id = ACCESS_PUBLIC;
1467
		$site->email = $submissionVars['siteemail'];
1468
		$guid = $site->save();
1469
1470
		if ($guid !== 1) {
1471
			register_error(_elgg_services()->translator->translate('install:error:createsite'));
1472
			return false;
1473
		}
1474
1475
		// bootstrap site info
1476
		$this->CONFIG->site_guid = 1;
1477
		$this->CONFIG->site = $site;
1478
1479
		_elgg_services()->configTable->set('installed', time());
1480
		_elgg_services()->configTable->set('version', elgg_get_version());
1481
		_elgg_services()->configTable->set('simplecache_enabled', 1);
1482
		_elgg_services()->configTable->set('system_cache_enabled', 1);
1483
		_elgg_services()->configTable->set('simplecache_lastupdate', time());
1484
1485
		// new installations have run all the upgrades
1486
		$upgrades = elgg_get_upgrade_files(\Elgg\Application::elggDir()->getPath("/engine/lib/upgrades/"));
1487
		_elgg_services()->configTable->set('processed_upgrades', $upgrades);
1488
1489
		_elgg_services()->configTable->set('view', 'default');
1490
		_elgg_services()->configTable->set('language', 'en');
1491
		_elgg_services()->configTable->set('default_access', $submissionVars['siteaccess']);
1492
		_elgg_services()->configTable->set('allow_registration', false);
1493
		_elgg_services()->configTable->set('walled_garden', false);
1494
		_elgg_services()->configTable->set('allow_user_default_access', '');
1495
		_elgg_services()->configTable->set('default_limit', 10);
1496
		_elgg_services()->configTable->set('security_protect_upgrade', true);
1497
		_elgg_services()->configTable->set('security_notify_admins', true);
1498
		_elgg_services()->configTable->set('security_notify_user_password', true);
1499
		_elgg_services()->configTable->set('security_email_require_password', true);
1500
1501
		$this->setSubtypeClasses();
1502
1503
		$this->enablePlugins();
1504
1505
		return true;
1506
	}
1507
1508
	/**
1509
	 * Register classes for core objects
1510
	 *
1511
	 * @return void
1512
	 */
1513
	protected function setSubtypeClasses() {
1514
		add_subtype("object", "plugin", "ElggPlugin");
1515
		add_subtype("object", "file", "ElggFile");
1516
		add_subtype("object", "widget", "ElggWidget");
1517
		add_subtype("object", "comment", "ElggComment");
1518
		add_subtype("object", "elgg_upgrade", 'ElggUpgrade');
1519
	}
1520
1521
	/**
1522
	 * Enable a set of default plugins
1523
	 *
1524
	 * @return void
1525
	 */
1526
	protected function enablePlugins() {
1527
		_elgg_generate_plugin_entities();
1528
		$plugins = elgg_get_plugins('any');
1529
		foreach ($plugins as $plugin) {
1530
			if ($plugin->getManifest()) {
1531
				if ($plugin->getManifest()->getActivateOnInstall()) {
1532
					$plugin->activate();
1533
				}
1534
				if (in_array('theme', $plugin->getManifest()->getCategories())) {
1535
					$plugin->setPriority('last');
1536
				}
1537
			}
1538
		}
1539
	}
1540
1541
	/**
1542
	 * Admin account support methods
1543
	 */
1544
1545
	/**
1546
	 * Validate account form variables
1547
	 *
1548
	 * @param array $submissionVars Submitted vars
1549
	 * @param array $formVars       Form vars
1550
	 *
1551
	 * @return bool
1552
	 */
1553
	protected function validateAdminVars($submissionVars, $formVars) {
1554
1555 View Code Duplication
		foreach ($formVars as $field => $info) {
1556
			if ($info['required'] == true && !$submissionVars[$field]) {
1557
				$name = _elgg_services()->translator->translate("install:admin:label:$field");
1558
				register_error(_elgg_services()->translator->translate('install:error:requiredfield', [$name]));
1559
				return false;
1560
			}
1561
		}
1562
1563
		if ($submissionVars['password1'] !== $submissionVars['password2']) {
1564
			register_error(_elgg_services()->translator->translate('install:admin:password:mismatch'));
1565
			return false;
1566
		}
1567
1568
		if (trim($submissionVars['password1']) == "") {
1569
			register_error(_elgg_services()->translator->translate('install:admin:password:empty'));
1570
			return false;
1571
		}
1572
1573
		$minLength = _elgg_services()->configTable->get('min_password_length');
1574
		if (strlen($submissionVars['password1']) < $minLength) {
1575
			register_error(_elgg_services()->translator->translate('install:admin:password:tooshort'));
1576
			return false;
1577
		}
1578
1579
		// check that email address is email address
1580 View Code Duplication
		if ($submissionVars['email'] && !is_email_address($submissionVars['email'])) {
1581
			$msg = _elgg_services()->translator->translate('install:error:emailaddress', [$submissionVars['email']]);
1582
			register_error($msg);
1583
			return false;
1584
		}
1585
1586
		return true;
1587
	}
1588
1589
	/**
1590
	 * Create a user account for the admin
1591
	 *
1592
	 * @param array $submissionVars Submitted vars
1593
	 * @param bool  $login          Login in the admin user?
1594
	 *
1595
	 * @return bool
1596
	 */
1597
	protected function createAdminAccount($submissionVars, $login = false) {
1598
		try {
1599
			$guid = register_user(
1600
					$submissionVars['username'],
1601
					$submissionVars['password1'],
1602
					$submissionVars['displayname'],
1603
					$submissionVars['email']
1604
					);
1605
		} catch (Exception $e) {
1606
			register_error($e->getMessage());
1607
			return false;
1608
		}
1609
1610
		if (!$guid) {
1611
			register_error(_elgg_services()->translator->translate('install:admin:cannot_create'));
1612
			return false;
1613
		}
1614
1615
		$user = get_entity($guid);
1616
		if (!$user instanceof ElggUser) {
1617
			register_error(_elgg_services()->translator->translate('install:error:loadadmin'));
1618
			return false;
1619
		}
1620
1621
		elgg_set_ignore_access(true);
1622
		if ($user->makeAdmin() == false) {
1623
			register_error(_elgg_services()->translator->translate('install:error:adminaccess'));
1624
		} else {
1625
			_elgg_services()->configTable->set('admin_registered', 1);
1626
		}
1627
		elgg_set_ignore_access(false);
1628
1629
		// add validation data to satisfy user validation plugins
1630
		$user->validated = 1;
1631
		$user->validated_method = 'admin_user';
1632
1633
		if ($login) {
1634
			$handler = new Elgg\Http\DatabaseSessionHandler(_elgg_services()->db);
1635
1636
			// session.cache_limiter is unfortunately set to "" by the NativeSessionStorage constructor,
1637
			// so we must capture and inject it directly.
1638
			$options = [
1639
				'cache_limiter' => session_cache_limiter(),
1640
			];
1641
			$storage = new Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage($options, $handler);
1642
1643
			$session = new ElggSession(new Symfony\Component\HttpFoundation\Session\Session($storage));
1644
			$session->setName('Elgg');
1645
			_elgg_services()->setValue('session', $session);
1646
			if (login($user) == false) {
1647
				register_error(_elgg_services()->translator->translate('install:error:adminlogin'));
1648
			}
1649
		}
1650
1651
		return true;
1652
	}
1653
}
1654