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

engine/classes/ElggInstaller.php (1 issue)

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;
0 ignored issues
show
This use statement conflicts with another class in this namespace, Directory.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
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())) {
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) {
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