Completed
Push — master ( 658f80...3de8bf )
by Steve
20:06 queued 10:55
created

ElggInstaller::finishBootstrapping()   C

Complexity

Conditions 8
Paths 36

Size

Total Lines 80
Code Lines 58

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 58
nc 36
nop 1
dl 0
loc 80
rs 6.0132
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
use Elgg\Filesystem\Directory;
0 ignored issues
show
Bug introduced by
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() {
0 ignored issues
show
Coding Style introduced by
__construct uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
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
Unused Code introduced by
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']) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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']) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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) {
0 ignored issues
show
Unused Code introduced by
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...
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
Unused Code introduced by
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) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison !== instead.

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

Loading history...
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->getSiteUrl(),
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 View Code Duplication
		if ($this->isAction) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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) {
0 ignored issues
show
Coding Style introduced by
admin uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
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 View Code Duplication
		if ($this->isAction) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
527
			do {
528
				if (!$this->validateAdminVars($submissionVars, $formVars)) {
529
					break;
530
				}
531
532
				if (!$this->createAdminAccount($submissionVars, $this->autoLogin)) {
533
					break;
534
				}
535
536
				system_message(_elgg_services()->translator->translate('install:success:admin'));
537
538
				$this->continueToNextStep('admin');
539
			} while (false);  // PHP doesn't support breaking out of if statements
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
		$params = [];
562
		if ($this->autoLogin) {
563
			$params['destination'] = 'admin';
564
		} else {
565
			$params['destination'] = 'index.php';
566
		}
567
568
		$this->render('complete', $params);
569
	}
570
571
	/**
572
	 * Step management
573
	 */
574
575
	/**
576
	 * Get an array of steps
577
	 *
578
	 * @return array
579
	 */
580
	protected function getSteps() {
581
		return $this->steps;
582
	}
583
584
	/**
585
	 * Forwards the browser to the next step
586
	 *
587
	 * @param string $currentStep Current installation step
588
	 *
589
	 * @return void
590
	 */
591
	protected function continueToNextStep($currentStep) {
592
		$this->isAction = false;
593
		forward($this->getNextStepUrl($currentStep));
594
	}
595
596
	/**
597
	 * Get the next step as a string
598
	 *
599
	 * @param string $currentStep Current installation step
600
	 *
601
	 * @return string
602
	 */
603
	protected function getNextStep($currentStep) {
604
		$index = 1 + array_search($currentStep, $this->steps);
605
		if (isset($this->steps[$index])) {
606
			return $this->steps[$index];
607
		} else {
608
			return null;
609
		}
610
	}
611
612
	/**
613
	 * Get the URL of the next step
614
	 *
615
	 * @param string $currentStep Current installation step
616
	 *
617
	 * @return string
618
	 */
619
	protected function getNextStepUrl($currentStep) {
620
		$nextStep = $this->getNextStep($currentStep);
621
		return _elgg_services()->config->getSiteUrl() . "install.php?step=$nextStep";
622
	}
623
624
	/**
625
	 * Check the different install steps for completion
626
	 *
627
	 * @return void
628
	 * @throws InstallationException
629
	 */
630
	protected function setInstallStatus() {
631
		if (!is_readable($this->getSettingsPath())) {
632
			return;
633
		}
634
635
		$this->loadSettingsFile();
636
637
		$this->status['config'] = true;
638
639
		// must be able to connect to database to jump install steps
640
		$dbSettingsPass = $this->checkDatabaseSettings(
641
				$this->CONFIG->dbuser,
642
				$this->CONFIG->dbpass,
643
				$this->CONFIG->dbname,
644
				$this->CONFIG->dbhost
645
				);
646
647
		if ($dbSettingsPass == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

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

Loading history...
648
			return;
649
		}
650
651
		if (!include_once(\Elgg\Application::elggDir()->getPath("engine/lib/database.php"))) {
652
			throw new InstallationException(_elgg_services()->translator->translate('InstallationException:MissingLibrary', ['database.php']));
653
		}
654
655
		// check that the config table has been created
656
		$query = "show tables";
657
		$result = _elgg_services()->db->getData($query);
658
		if ($result) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
659
			foreach ($result as $table) {
660
				$table = (array) $table;
661
				if (in_array("{$this->CONFIG->dbprefix}config", $table)) {
662
					$this->status['database'] = true;
663
				}
664
			}
665
			if ($this->status['database'] == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

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

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

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

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

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

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

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

Loading history...
686
			$this->status['admin'] = true;
687
		} else {
688
			return;
689
		}
690
	}
691
692
	/**
693
	 * Security check to ensure the installer cannot be run after installation
694
	 * has finished. If this is detected, the viewer is sent to the front page.
695
	 *
696
	 * @param string $step Installation step to check against
697
	 *
698
	 * @return void
699
	 */
700
	protected function checkInstallCompletion($step) {
701
		if ($step != 'complete') {
702
			if (!in_array(false, $this->status)) {
703
				// install complete but someone is trying to view an install page
704
				forward();
705
			}
706
		}
707
	}
708
709
	/**
710
	 * Check if this is a case of a install being resumed and figure
711
	 * out where to continue from. Returns the best guess on the step.
712
	 *
713
	 * @param string $step Installation step to resume from
714
	 *
715
	 * @return string
716
	 */
717
	protected function resumeInstall($step) {
718
		// only do a resume from the first step
719
		if ($step !== 'welcome') {
720
			return;
721
		}
722
723
		if ($this->status['database'] == false) {
724
			return;
725
		}
726
727
		if ($this->status['settings'] == false) {
728
			forward("install.php?step=settings");
729
		}
730
731
		if ($this->status['admin'] == false) {
732
			forward("install.php?step=admin");
733
		}
734
735
		// everything appears to be set up
736
		forward("install.php?step=complete");
737
	}
738
739
	/**
740
	 * Bootstraping
741
	 */
742
743
	/**
744
	 * Load the essential libraries of the engine
745
	 *
746
	 * @return void
747
	 */
748
	protected function bootstrapEngine() {
749
		$config = new \Elgg\Config($this->CONFIG);
750
		$services = new \Elgg\Di\ServiceProvider($config);
751
		(new \Elgg\Application($services))->loadCore();
752
	}
753
754
	/**
755
	 * Load remaining engine libraries and complete bootstrapping
756
	 *
757
	 * @param string $step Which step to boot strap for. Required because
758
	 *                     boot strapping is different until the DB is populated.
759
	 *
760
	 * @return void
761
	 * @throws InstallationException
762
	 */
763
	protected function finishBootstrapping($step) {
764
765
		$dbIndex = array_search('database', $this->getSteps());
766
		$settingsIndex = array_search('settings', $this->getSteps());
767
		$adminIndex = array_search('admin', $this->getSteps());
768
		$completeIndex = array_search('complete', $this->getSteps());
769
		$stepIndex = array_search($step, $this->getSteps());
770
771
		// To log in the user, we need to use the Elgg core session handling.
772
		// Otherwise, use default php session handling
773
		$useElggSession = ($stepIndex == $adminIndex && $this->isAction) ||
774
				$stepIndex == $completeIndex;
775
		if (!$useElggSession) {
776
			session_name('Elgg_install');
777
			session_start();
778
			_elgg_services()->events->unregisterHandler('boot', 'system', 'session_init');
779
		}
780
781
		if ($stepIndex > $dbIndex) {
782
			// once the database has been created, load rest of engine
783
			
784
			$lib_dir = \Elgg\Application::elggDir()->chroot('/engine/lib/');
785
786
			$this->loadSettingsFile();
787
788
			$lib_files = [
789
				// these want to be loaded first apparently?
790
				'autoloader.php',
791
				'database.php',
792
				'actions.php',
793
794
				'admin.php',
795
				'annotations.php',
796
				'cron.php',
797
				'entities.php',
798
				'extender.php',
799
				'filestore.php',
800
				'group.php',
801
				'mb_wrapper.php',
802
				'memcache.php',
803
				'metadata.php',
804
				'metastrings.php',
805
				'navigation.php',
806
				'notification.php',
807
				'objects.php',
808
				'pagehandler.php',
809
				'pam.php',
810
				'plugins.php',
811
				'private_settings.php',
812
				'relationships.php',
813
				'river.php',
814
				'sites.php',
815
				'statistics.php',
816
				'tags.php',
817
				'user_settings.php',
818
				'users.php',
819
				'upgrade.php',
820
				'widgets.php',
821
			];
822
823
			foreach ($lib_files as $file) {
824
				if (!include_once($lib_dir->getPath($file))) {
825
					throw new InstallationException('InstallationException:MissingLibrary', [$file]);
826
				}
827
			}
828
829
			_elgg_services()->db->setupConnections();
830
			_elgg_services()->translator->registerTranslations(\Elgg\Application::elggDir()->getPath("/languages/"));
831
			$this->CONFIG->language = 'en';
832
833
			if ($stepIndex > $settingsIndex) {
834
				$this->CONFIG->site_guid = 1;
835
				$this->CONFIG->site = get_entity(1);
836
				_elgg_services()->config->getCookieConfig();
837
				_elgg_session_boot();
838
			}
839
840
			_elgg_services()->events->trigger('init', 'system');
841
		}
842
	}
843
844
	/**
845
	 * Set up configuration variables
846
	 *
847
	 * @return void
848
	 */
849
	protected function bootstrapConfig() {
850
		$this->CONFIG->installer_running = true;
851
852
		$this->CONFIG->wwwroot = $this->getBaseUrl();
853
		$this->CONFIG->url = $this->CONFIG->wwwroot;
854
		$this->CONFIG->path = Directory\Local::root()->getPath('/');
855
		$this->view_path = $this->CONFIG->path . 'views/';
856
		$this->CONFIG->pluginspath = $this->CONFIG->path . 'mod/';
857
		$this->CONFIG->context = [];
858
		$this->CONFIG->entity_types = ['group', 'object', 'site', 'user'];
859
860
		// required by elgg_view_page()
861
		$this->CONFIG->sitename = '';
862
		$this->CONFIG->sitedescription = '';
863
864
		// required by Elgg\Config::get
865
		$this->CONFIG->site_guid = 1;
866
	}
867
	
868
	/**
869
	 * @return bool Whether the install process is encrypted.
870
	 */
871
	private function isHttps() {
0 ignored issues
show
Coding Style introduced by
isHttps uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
872
		return (!empty($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on") ||
873
			(!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443);
874
	}
875
876
	/**
877
	 * Get the best guess at the base URL
878
	 *
879
	 * @note Cannot use current_page_url() because it depends on $this->CONFIG->wwwroot
880
	 * @todo Should this be a core function?
881
	 *
882
	 * @return string
883
	 */
884
	protected function getBaseUrl() {
0 ignored issues
show
Coding Style introduced by
getBaseUrl uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
885
		$protocol = $this->isHttps() ? 'https' : 'http';
886
		
887
		if (isset($_SERVER["SERVER_PORT"])) {
888
			$port = ':' . $_SERVER["SERVER_PORT"];
889
		} else {
890
			$port = '';
891
		}
892
		if ($port == ':80' || $port == ':443') {
893
			$port = '';
894
		}
895
		$uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
896
		$cutoff = strpos($uri, 'install.php');
897
		$uri = substr($uri, 0, $cutoff);
898
		$serverName = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '';
899
900
		return "$protocol://{$serverName}$port{$uri}";
901
	}
902
903
	/**
904
	 * Load settings.php
905
	 *
906
	 * @return void
907
	 * @throws InstallationException
908
	 */
909
	protected function loadSettingsFile() {
910
		if (!include_once($this->getSettingsPath())) {
911
			throw new InstallationException(_elgg_services()->translator->translate('InstallationException:CannotLoadSettings'));
912
		}
913
	}
914
915
	/**
916
	 * Action handling methods
917
	 */
918
919
	/**
920
	 * Return an associative array of post variables
921
	 * (could be selective based on expected variables)
922
	 *
923
	 * Does not filter as person installing the site should not be attempting
924
	 * XSS attacks. If filtering is added, it should not be done for passwords.
925
	 *
926
	 * @return array
927
	 */
928
	protected function getPostVariables() {
0 ignored issues
show
Coding Style introduced by
getPostVariables uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
929
		$vars = [];
930
		foreach ($_POST as $k => $v) {
931
			$vars[$k] = $v;
932
		}
933
		return $vars;
934
	}
935
936
	/**
937
	 * If form is reshown, remember previously submitted variables
938
	 *
939
	 * @param array $formVars       Vars int he form
940
	 * @param array $submissionVars Submitted vars
941
	 *
942
	 * @return array
943
	 */
944
	protected function makeFormSticky($formVars, $submissionVars) {
945
		foreach ($submissionVars as $field => $value) {
946
			$formVars[$field]['value'] = $value;
947
		}
948
		return $formVars;
949
	}
950
951
	/* Requirement checks support methods */
952
953
	/**
954
	 * Indicates whether the webserver can add settings.php on its own or not.
955
	 *
956
	 * @param array $report The requirements report object
957
	 *
958
	 * @return bool
959
	 */
960
	protected function isInstallDirWritable(&$report) {
961
		$root = Directory\Local::root()->getPath();
962
		$abs_path = \Elgg\Application::elggDir()->getPath('elgg-config');
963
964
		if (0 === strpos($abs_path, $root)) {
965
			$relative_path = substr($abs_path, strlen($root));
966
		} else {
967
			$relative_path = $abs_path;
968
		}
969
		$relative_path = rtrim($relative_path, '/\\');
970
971
		$writable = is_writable(Directory\Local::root()->getPath('elgg-config'));
972
		if (!$writable) {
973
			$report['settings'] = [
974
				[
975
					'severity' => 'failure',
976
					'message' => _elgg_services()->translator->translate('install:check:installdir', [$relative_path]),
977
				]
978
			];
979
			return false;
980
		}
981
982
		return true;
983
	}
984
985
	/**
986
	 * Check that the settings file exists
987
	 *
988
	 * @param array $report The requirements report array
989
	 *
990
	 * @return bool
991
	 */
992
	protected function checkSettingsFile(&$report = []) {
993
		if (!file_exists($this->getSettingsPath())) {
994
			return false;
995
		}
996
997
		if (!is_readable($this->getSettingsPath())) {
998
			$report['settings'] = [
999
				[
1000
					'severity' => 'failure',
1001
					'message' => _elgg_services()->translator->translate('install:check:readsettings'),
1002
				]
1003
			];
1004
		}
1005
		
1006
		return true;
1007
	}
1008
	
1009
	/**
1010
	 * Returns the path to the root settings.php file.
1011
	 *
1012
	 * @return string
1013
	 */
1014
	private function getSettingsPath() {
1015
		return Directory\Local::root()->getPath("elgg-config/settings.php");
1016
	}
1017
1018
	/**
1019
	 * Check version of PHP, extensions, and variables
1020
	 *
1021
	 * @param array $report The requirements report array
1022
	 *
1023
	 * @return void
1024
	 */
1025
	protected function checkPHP(&$report) {
1026
		$phpReport = [];
1027
1028
		$min_php_version = '5.6.0';
1029
		if (version_compare(PHP_VERSION, $min_php_version, '<')) {
1030
			$phpReport[] = [
1031
				'severity' => 'failure',
1032
				'message' => _elgg_services()->translator->translate('install:check:php:version', [$min_php_version, PHP_VERSION])
1033
			];
1034
		}
1035
1036
		$this->checkPhpExtensions($phpReport);
1037
1038
		$this->checkPhpDirectives($phpReport);
1039
1040
		if (count($phpReport) == 0) {
1041
			$phpReport[] = [
1042
				'severity' => 'pass',
1043
				'message' => _elgg_services()->translator->translate('install:check:php:success')
1044
			];
1045
		}
1046
1047
		$report['php'] = $phpReport;
1048
	}
1049
1050
	/**
1051
	 * Check the server's PHP extensions
1052
	 *
1053
	 * @param array $phpReport The PHP requirements report array
1054
	 *
1055
	 * @return void
1056
	 */
1057
	protected function checkPhpExtensions(&$phpReport) {
1058
		$extensions = get_loaded_extensions();
1059
		$requiredExtensions = [
1060
			'pdo_mysql',
1061
			'json',
1062
			'xml',
1063
			'gd',
1064
		];
1065 View Code Duplication
		foreach ($requiredExtensions as $extension) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1066
			if (!in_array($extension, $extensions)) {
1067
				$phpReport[] = [
1068
					'severity' => 'failure',
1069
					'message' => _elgg_services()->translator->translate('install:check:php:extension', [$extension])
1070
				];
1071
			}
1072
		}
1073
1074
		$recommendedExtensions = [
1075
			'mbstring',
1076
		];
1077 View Code Duplication
		foreach ($recommendedExtensions as $extension) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1078
			if (!in_array($extension, $extensions)) {
1079
				$phpReport[] = [
1080
					'severity' => 'warning',
1081
					'message' => _elgg_services()->translator->translate('install:check:php:extension:recommend', [$extension])
1082
				];
1083
			}
1084
		}
1085
	}
1086
1087
	/**
1088
	 * Check PHP parameters
1089
	 *
1090
	 * @param array $phpReport The PHP requirements report array
1091
	 *
1092
	 * @return void
1093
	 */
1094
	protected function checkPhpDirectives(&$phpReport) {
1095
		if (ini_get('open_basedir')) {
1096
			$phpReport[] = [
1097
				'severity' => 'warning',
1098
				'message' => _elgg_services()->translator->translate("install:check:php:open_basedir")
1099
			];
1100
		}
1101
1102
		if (ini_get('safe_mode')) {
1103
			$phpReport[] = [
1104
				'severity' => 'warning',
1105
				'message' => _elgg_services()->translator->translate("install:check:php:safe_mode")
1106
			];
1107
		}
1108
1109
		if (ini_get('arg_separator.output') !== '&') {
1110
			$separator = htmlspecialchars(ini_get('arg_separator.output'));
1111
			$msg = _elgg_services()->translator->translate("install:check:php:arg_separator", [$separator]);
1112
			$phpReport[] = [
1113
				'severity' => 'failure',
1114
				'message' => $msg,
1115
			];
1116
		}
1117
1118
		if (ini_get('register_globals')) {
1119
			$phpReport[] = [
1120
				'severity' => 'failure',
1121
				'message' => _elgg_services()->translator->translate("install:check:php:register_globals")
1122
			];
1123
		}
1124
1125
		if (ini_get('session.auto_start')) {
1126
			$phpReport[] = [
1127
				'severity' => 'failure',
1128
				'message' => _elgg_services()->translator->translate("install:check:php:session.auto_start")
1129
			];
1130
		}
1131
	}
1132
1133
	/**
1134
	 * Confirm that the rewrite rules are firing
1135
	 *
1136
	 * @param array $report The requirements report array
1137
	 *
1138
	 * @return void
1139
	 */
1140
	protected function checkRewriteRules(&$report) {
1141
		$tester = new ElggRewriteTester();
1142
		$url = _elgg_services()->config->getSiteUrl() . "rewrite.php";
1143
		$report['rewrite'] = [$tester->run($url, Directory\Local::root()->getPath())];
1144
	}
1145
1146
	/**
1147
	 * Check if the request is coming from the URL rewrite test on the
1148
	 * requirements page.
1149
	 *
1150
	 * @return void
1151
	 */
1152
	protected function processRewriteTest() {
0 ignored issues
show
Coding Style introduced by
processRewriteTest uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1153
		if (strpos($_SERVER['REQUEST_URI'], 'rewrite.php') !== false) {
1154
			echo \Elgg\Application::REWRITE_TEST_OUTPUT;
1155
			exit;
1156
		}
1157
	}
1158
1159
	/**
1160
	 * Count the number of failures in the requirements report
1161
	 *
1162
	 * @param array  $report    The requirements report array
1163
	 * @param string $condition 'failure' or 'warning'
1164
	 *
1165
	 * @return int
1166
	 */
1167
	protected function countNumConditions($report, $condition) {
1168
		$count = 0;
1169
		foreach ($report as $category => $checks) {
1170
			foreach ($checks as $check) {
1171
				if ($check['severity'] === $condition) {
1172
					$count++;
1173
				}
1174
			}
1175
		}
1176
1177
		return $count;
1178
	}
1179
1180
1181
	/**
1182
	 * Database support methods
1183
	 */
1184
1185
	/**
1186
	 * Validate the variables for the database step
1187
	 *
1188
	 * @param array $submissionVars Submitted vars
1189
	 * @param array $formVars       Vars in the form
1190
	 *
1191
	 * @return bool
1192
	 */
1193
	protected function validateDatabaseVars($submissionVars, $formVars) {
1194
1195 View Code Duplication
		foreach ($formVars as $field => $info) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1196
			if ($info['required'] == true && !$submissionVars[$field]) {
1197
				$name = _elgg_services()->translator->translate("install:database:label:$field");
1198
				register_error(_elgg_services()->translator->translate('install:error:requiredfield', [$name]));
1199
				return false;
1200
			}
1201
		}
1202
1203
		// check that data root is absolute path
1204
		if (stripos(PHP_OS, 'win') === 0) {
1205 View Code Duplication
			if (strpos($submissionVars['dataroot'], ':') !== 1) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1206
				$msg = _elgg_services()->translator->translate('install:error:relative_path', [$submissionVars['dataroot']]);
1207
				register_error($msg);
1208
				return false;
1209
			}
1210 View Code Duplication
		} else {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1211
			if (strpos($submissionVars['dataroot'], '/') !== 0) {
1212
				$msg = _elgg_services()->translator->translate('install:error:relative_path', [$submissionVars['dataroot']]);
1213
				register_error($msg);
1214
				return false;
1215
			}
1216
		}
1217
1218
		// check that data root exists
1219 View Code Duplication
		if (!is_dir($submissionVars['dataroot'])) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1220
			$msg = _elgg_services()->translator->translate('install:error:datadirectoryexists', [$submissionVars['dataroot']]);
1221
			register_error($msg);
1222
			return false;
1223
		}
1224
1225
		// check that data root is writable
1226 View Code Duplication
		if (!is_writable($submissionVars['dataroot'])) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1227
			$msg = _elgg_services()->translator->translate('install:error:writedatadirectory', [$submissionVars['dataroot']]);
1228
			register_error($msg);
1229
			return false;
1230
		}
1231
1232
		if (!isset($this->CONFIG->data_dir_override) || !$this->CONFIG->data_dir_override) {
1233
			// check that data root is not subdirectory of Elgg root
1234
			if (stripos($submissionVars['dataroot'], $this->CONFIG->path) === 0) {
1235
				$msg = _elgg_services()->translator->translate('install:error:locationdatadirectory', [$submissionVars['dataroot']]);
1236
				register_error($msg);
1237
				return false;
1238
			}
1239
		}
1240
1241
		// according to postgres documentation: SQL identifiers and key words must
1242
		// begin with a letter (a-z, but also letters with diacritical marks and
1243
		// non-Latin letters) or an underscore (_). Subsequent characters in an
1244
		// identifier or key word can be letters, underscores, digits (0-9), or dollar signs ($).
1245
		// Refs #4994
1246
		if (!preg_match("/^[a-zA-Z_][\w]*$/", $submissionVars['dbprefix'])) {
1247
			register_error(_elgg_services()->translator->translate('install:error:database_prefix'));
1248
			return false;
1249
		}
1250
1251
		return $this->checkDatabaseSettings(
1252
					$submissionVars['dbuser'],
1253
					$submissionVars['dbpassword'],
1254
					$submissionVars['dbname'],
1255
					$submissionVars['dbhost']
1256
				);
1257
	}
1258
1259
	/**
1260
	 * Confirm the settings for the database
1261
	 *
1262
	 * @param string $user     Username
1263
	 * @param string $password Password
1264
	 * @param string $dbname   Database name
1265
	 * @param string $host     Host
1266
	 *
1267
	 * @return bool
1268
	 */
1269
	protected function checkDatabaseSettings($user, $password, $dbname, $host) {
1270
		$config = new \Elgg\Database\Config((object) [
1271
			'dbhost' => $host,
1272
			'dbuser' => $user,
1273
			'dbpass' => $password,
1274
			'dbname' => $dbname,
1275
		]);
1276
		$db = new \Elgg\Database($config);
1277
1278
		try {
1279
			$db->getDataRow("SELECT 1");
1280
		} catch (DatabaseException $e) {
1281
			if (0 === strpos($e->getMessage(), "Elgg couldn't connect")) {
1282
				register_error(_elgg_services()->translator->translate('install:error:databasesettings'));
1283
			} else {
1284
				register_error(_elgg_services()->translator->translate('install:error:nodatabase', [$dbname]));
1285
			}
1286
			return false;
1287
		}
1288
1289
		// check MySQL version - must be 5.0 or >
1290
		$version = $db->getServerVersion(\Elgg\Database\Config::READ_WRITE);
1291
		$required_version = 5.0;
1292
		$points = explode('.', $version);
1293
		if ($points[0] < $required_version) {
1294
			register_error(_elgg_services()->translator->translate('install:error:oldmysql', [$version]));
1295
			return false;
1296
		}
1297
1298
		return true;
1299
	}
1300
1301
	/**
1302
	 * Writes the settings file to the engine directory
1303
	 *
1304
	 * @param array $params Array of inputted params from the user
1305
	 *
1306
	 * @return bool
1307
	 */
1308
	protected function createSettingsFile($params) {
1309
		$template = \Elgg\Application::elggDir()->getContents("elgg-config/settings.example.php");
1310
		if (!$template) {
1311
			register_error(_elgg_services()->translator->translate('install:error:readsettingsphp'));
1312
			return false;
1313
		}
1314
1315
		foreach ($params as $k => $v) {
1316
			$template = str_replace("{{" . $k . "}}", $v, $template);
1317
		}
1318
1319
		$result = file_put_contents($this->getSettingsPath(), $template);
1320
		if (!$result) {
1321
			register_error(_elgg_services()->translator->translate('install:error:writesettingphp'));
1322
			return false;
1323
		}
1324
1325
		return true;
1326
	}
1327
1328
	/**
1329
	 * Bootstrap database connection before entire engine is available
1330
	 *
1331
	 * @return bool
1332
	 */
1333
	protected function connectToDatabase() {
1334
		if (!include_once($this->getSettingsPath())) {
1335
			register_error('Elgg could not load the settings file. It does not exist or there is a file permissions issue.');
1336
			return false;
1337
		}
1338
1339
		if (!include_once(\Elgg\Application::elggDir()->getPath("engine/lib/database.php"))) {
1340
			register_error('Could not load database.php');
1341
			return false;
1342
		}
1343
1344
		try {
1345
			_elgg_services()->db->setupConnections();
1346
		} catch (DatabaseException $e) {
1347
			register_error($e->getMessage());
1348
			return false;
1349
		}
1350
1351
		return true;
1352
	}
1353
1354
	/**
1355
	 * Create the database tables
1356
	 *
1357
	 * @return bool
1358
	 */
1359
	protected function installDatabase() {
1360
		try {
1361
			_elgg_services()->db->runSqlScript(\Elgg\Application::elggDir()->getPath("/engine/schema/mysql.sql"));
1362
		} catch (Exception $e) {
1363
			$msg = $e->getMessage();
1364
			if (strpos($msg, 'already exists')) {
1365
				$msg = _elgg_services()->translator->translate('install:error:tables_exist');
1366
			}
1367
			register_error($msg);
1368
			return false;
1369
		}
1370
1371
		return true;
1372
	}
1373
1374
	/**
1375
	 * Site settings support methods
1376
	 */
1377
1378
	/**
1379
	 * Create the data directory if requested
1380
	 *
1381
	 * @param array $submissionVars Submitted vars
1382
	 * @param array $formVars       Variables in the form
1383
	 *
1384
	 * @return bool
1385
	 */
1386
	protected function createDataDirectory(&$submissionVars, $formVars) {
1387
		// did the user have option of Elgg creating the data directory
1388
		if ($formVars['dataroot']['type'] != 'combo') {
1389
			return true;
1390
		}
1391
1392
		// did the user select the option
1393
		if ($submissionVars['dataroot'] != 'dataroot-checkbox') {
1394
			return true;
1395
		}
1396
1397
		$dir = sanitise_filepath($submissionVars['path']) . 'data';
1398
		if (file_exists($dir) || mkdir($dir, 0700)) {
1399
			$submissionVars['dataroot'] = $dir;
1400
			if (!file_exists("$dir/.htaccess")) {
1401
				$htaccess = "Order Deny,Allow\nDeny from All\n";
1402
				if (!file_put_contents("$dir/.htaccess", $htaccess)) {
1403
					return false;
1404
				}
1405
			}
1406
			return true;
1407
		}
1408
1409
		return false;
1410
	}
1411
1412
	/**
1413
	 * Validate the site settings form variables
1414
	 *
1415
	 * @param array $submissionVars Submitted vars
1416
	 * @param array $formVars       Vars in the form
1417
	 *
1418
	 * @return bool
1419
	 */
1420
	protected function validateSettingsVars($submissionVars, $formVars) {
1421
		foreach ($formVars as $field => $info) {
1422
			$submissionVars[$field] = trim($submissionVars[$field]);
1423
			if ($info['required'] == true && $submissionVars[$field] === '') {
1424
				$name = _elgg_services()->translator->translate("install:settings:label:$field");
1425
				register_error(_elgg_services()->translator->translate('install:error:requiredfield', [$name]));
1426
				return false;
1427
			}
1428
		}
1429
1430
		// check that email address is email address
1431 View Code Duplication
		if ($submissionVars['siteemail'] && !is_email_address($submissionVars['siteemail'])) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1432
			$msg = _elgg_services()->translator->translate('install:error:emailaddress', [$submissionVars['siteemail']]);
1433
			register_error($msg);
1434
			return false;
1435
		}
1436
1437
		// @todo check that url is a url
1438
		// @note filter_var cannot be used because it doesn't work on international urls
1439
1440
		return true;
1441
	}
1442
1443
	/**
1444
	 * Initialize the site including site entity, plugins, and configuration
1445
	 *
1446
	 * @param array $submissionVars Submitted vars
1447
	 *
1448
	 * @return bool
1449
	 */
1450
	protected function saveSiteSettings($submissionVars) {
1451
		$site = new ElggSite();
1452
		$site->name = strip_tags($submissionVars['sitename']);
1453
		$site->access_id = ACCESS_PUBLIC;
1454
		$site->email = $submissionVars['siteemail'];
1455
		$guid = $site->save();
1456
1457
		if ($guid !== 1) {
1458
			register_error(_elgg_services()->translator->translate('install:error:createsite'));
1459
			return false;
1460
		}
1461
1462
		// bootstrap site info
1463
		$this->CONFIG->site_guid = 1;
1464
		$this->CONFIG->site = $site;
1465
1466
		_elgg_services()->configTable->set('installed', time());
1467
		_elgg_services()->configTable->set('version', elgg_get_version());
1468
		_elgg_services()->configTable->set('simplecache_enabled', 1);
1469
		_elgg_services()->configTable->set('system_cache_enabled', 1);
1470
		_elgg_services()->configTable->set('simplecache_lastupdate', time());
1471
1472
		// new installations have run all the upgrades
1473
		$upgrades = elgg_get_upgrade_files(\Elgg\Application::elggDir()->getPath("/engine/lib/upgrades/"));
1474
		_elgg_services()->configTable->set('processed_upgrades', $upgrades);
1475
1476
		_elgg_services()->configTable->set('view', 'default');
1477
		_elgg_services()->configTable->set('language', 'en');
1478
		_elgg_services()->configTable->set('default_access', $submissionVars['siteaccess']);
1479
		_elgg_services()->configTable->set('allow_registration', true);
1480
		_elgg_services()->configTable->set('walled_garden', false);
1481
		_elgg_services()->configTable->set('allow_user_default_access', '');
1482
		_elgg_services()->configTable->set('default_limit', 10);
1483
		_elgg_services()->configTable->set('security_protect_upgrade', true);
1484
		_elgg_services()->configTable->set('security_notify_admins', true);
1485
		_elgg_services()->configTable->set('security_notify_user_password', true);
1486
		_elgg_services()->configTable->set('security_email_require_password', true);
1487
1488
		$this->setSubtypeClasses();
1489
1490
		$this->enablePlugins();
1491
1492
		return true;
1493
	}
1494
1495
	/**
1496
	 * Register classes for core objects
1497
	 *
1498
	 * @return void
1499
	 */
1500
	protected function setSubtypeClasses() {
1501
		add_subtype("object", "plugin", "ElggPlugin");
1502
		add_subtype("object", "file", "ElggFile");
1503
		add_subtype("object", "widget", "ElggWidget");
1504
		add_subtype("object", "comment", "ElggComment");
1505
		add_subtype("object", "elgg_upgrade", 'ElggUpgrade');
1506
	}
1507
1508
	/**
1509
	 * Enable a set of default plugins
1510
	 *
1511
	 * @return void
1512
	 */
1513
	protected function enablePlugins() {
1514
		_elgg_generate_plugin_entities();
1515
		$plugins = elgg_get_plugins('any');
1516
		foreach ($plugins as $plugin) {
1517
			if ($plugin->getManifest()) {
1518
				if ($plugin->getManifest()->getActivateOnInstall()) {
1519
					$plugin->activate();
1520
				}
1521
				if (in_array('theme', $plugin->getManifest()->getCategories())) {
1522
					$plugin->setPriority('last');
1523
				}
1524
			}
1525
		}
1526
	}
1527
1528
	/**
1529
	 * Admin account support methods
1530
	 */
1531
1532
	/**
1533
	 * Validate account form variables
1534
	 *
1535
	 * @param array $submissionVars Submitted vars
1536
	 * @param array $formVars       Form vars
1537
	 *
1538
	 * @return bool
1539
	 */
1540
	protected function validateAdminVars($submissionVars, $formVars) {
1541
1542 View Code Duplication
		foreach ($formVars as $field => $info) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1543
			if ($info['required'] == true && !$submissionVars[$field]) {
1544
				$name = _elgg_services()->translator->translate("install:admin:label:$field");
1545
				register_error(_elgg_services()->translator->translate('install:error:requiredfield', [$name]));
1546
				return false;
1547
			}
1548
		}
1549
1550
		if ($submissionVars['password1'] !== $submissionVars['password2']) {
1551
			register_error(_elgg_services()->translator->translate('install:admin:password:mismatch'));
1552
			return false;
1553
		}
1554
1555
		if (trim($submissionVars['password1']) == "") {
1556
			register_error(_elgg_services()->translator->translate('install:admin:password:empty'));
1557
			return false;
1558
		}
1559
1560
		$minLength = _elgg_services()->configTable->get('min_password_length');
1561
		if (strlen($submissionVars['password1']) < $minLength) {
1562
			register_error(_elgg_services()->translator->translate('install:admin:password:tooshort'));
1563
			return false;
1564
		}
1565
1566
		// check that email address is email address
1567 View Code Duplication
		if ($submissionVars['email'] && !is_email_address($submissionVars['email'])) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1568
			$msg = _elgg_services()->translator->translate('install:error:emailaddress', [$submissionVars['email']]);
1569
			register_error($msg);
1570
			return false;
1571
		}
1572
1573
		return true;
1574
	}
1575
1576
	/**
1577
	 * Create a user account for the admin
1578
	 *
1579
	 * @param array $submissionVars Submitted vars
1580
	 * @param bool  $login          Login in the admin user?
1581
	 *
1582
	 * @return bool
1583
	 */
1584
	protected function createAdminAccount($submissionVars, $login = false) {
1585
		try {
1586
			$guid = register_user(
1587
					$submissionVars['username'],
1588
					$submissionVars['password1'],
1589
					$submissionVars['displayname'],
1590
					$submissionVars['email']
1591
					);
1592
		} catch (Exception $e) {
1593
			register_error($e->getMessage());
1594
			return false;
1595
		}
1596
1597
		if (!$guid) {
1598
			register_error(_elgg_services()->translator->translate('install:admin:cannot_create'));
1599
			return false;
1600
		}
1601
1602
		$user = get_entity($guid);
1603
		if (!$user instanceof ElggUser) {
1604
			register_error(_elgg_services()->translator->translate('install:error:loadadmin'));
1605
			return false;
1606
		}
1607
1608
		elgg_set_ignore_access(true);
1609
		if ($user->makeAdmin() == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

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

Loading history...
1610
			register_error(_elgg_services()->translator->translate('install:error:adminaccess'));
1611
		} else {
1612
			_elgg_services()->configTable->set('admin_registered', 1);
1613
		}
1614
		elgg_set_ignore_access(false);
1615
1616
		// add validation data to satisfy user validation plugins
1617
		$user->validated = 1;
1 ignored issue
show
Documentation introduced by
The property validated does not exist on object<ElggUser>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1618
		$user->validated_method = 'admin_user';
1 ignored issue
show
Documentation introduced by
The property validated_method does not exist on object<ElggUser>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1619
1620
		if ($login) {
1621
			$handler = new Elgg\Http\DatabaseSessionHandler(_elgg_services()->db);
1622
1623
			// session.cache_limiter is unfortunately set to "" by the NativeSessionStorage constructor,
1624
			// so we must capture and inject it directly.
1625
			$options = [
1626
				'cache_limiter' => session_cache_limiter(),
1627
			];
1628
			$storage = new Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage($options, $handler);
1629
1630
			$session = new ElggSession(new Symfony\Component\HttpFoundation\Session\Session($storage));
1631
			$session->setName('Elgg');
1632
			_elgg_services()->setValue('session', $session);
1633
			if (login($user) == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

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

Loading history...
1634
				register_error(_elgg_services()->translator->translate('install:error:adminlogin'));
1635
			}
1636
		}
1637
1638
		return true;
1639
	}
1640
}
1641