Test Failed
Push — master ( 294b60...945405 )
by Jerome
02:06
created
engine/classes/Elgg/BootService.php 1 patch
Indentation   +205 added lines, -205 removed lines patch added patch discarded remove patch
@@ -17,209 +17,209 @@
 block discarded – undo
17 17
  * @since 2.1
18 18
  */
19 19
 class BootService {
20
-	use Profilable;
21
-
22
-	/**
23
-	 * Under load, a short TTL gives nearly all of the benefits of a longer TTL, but it also ensures
24
-	 * that, should cache invalidation fail for some reason, it'll be rebuilt quickly anyway.
25
-	 *
26
-	 * In 2.x we do not cache by default. This will likely change to 10 in 3.0.
27
-	 */
28
-	const DEFAULT_BOOT_CACHE_TTL = 0;
29
-
30
-	/**
31
-	 * Has the cache already been invalidated this request? Avoids thrashing
32
-	 *
33
-	 * @var bool
34
-	 */
35
-	private $was_cleared = false;
36
-
37
-	/**
38
-	 * Boots the engine
39
-	 *
40
-	 * @param ServiceProvider $services Services
41
-	 * @return void
42
-	 */
43
-	public function boot(ServiceProvider $services) {
44
-		$db = $services->db;
45
-		$config = $services->config;
46
-
47
-		// set cookie values for session and remember me
48
-		$config->getCookieConfig();
49
-
50
-		// defaults in case these aren't in config table
51
-		if ($config->boot_cache_ttl === null) {
52
-			$config->boot_cache_ttl = self::DEFAULT_BOOT_CACHE_TTL;
53
-		}
54
-		if ($config->simplecache_enabled === null) {
55
-			$config->simplecache_enabled = 0;
56
-		}
57
-		if ($config->system_cache_enabled === null) {
58
-			$config->system_cache_enabled = false;
59
-		}
60
-		if ($config->simplecache_lastupdate === null) {
61
-			$config->simplecache_lastupdate = 0;
62
-		}
63
-
64
-		// we were using NOTICE temporarily so we can't just check for null
65
-		if (!$config->hasInitialValue('debug')) {
66
-			$config->debug = '';
67
-		}
68
-
69
-		// copy all table values into config
70
-		$config->mergeValues($services->configTable->getAll());
71
-
72
-		// needs to be set before [init, system] for links in html head
73
-		$config->lastcache = (int) $config->simplecache_lastupdate;
74
-
75
-		if (!$config->elgg_config_set_secret) {
76
-			$site_secret = SiteSecret::fromConfig($config);
77
-			if (!$site_secret) {
78
-				// The 2.3 installer doesn't create a site key (it's created on-demand on the first request)
79
-				// so for our Travis upgrade testing we need to check for this and create one on the spot.
80
-				if (defined('UPGRADING')) {
81
-					$site_secret = SiteSecret::regenerate($services->crypto, $services->configTable);
82
-				}
83
-			}
84
-			if ($site_secret) {
85
-				$services->setValue('siteSecret', $site_secret);
86
-			} else {
87
-				throw new \RuntimeException('The site secret is not set.');
88
-			}
89
-		}
90
-
91
-		$installed = isset($config->installed);
92
-
93
-		if ($this->timer) {
94
-			$this->timer->begin([__CLASS__ . '::getBootData']);
95
-		}
96
-
97
-		// early config is done, now get the core boot data
98
-		$data = $this->getBootData($config, $db, $installed);
99
-
100
-		$site = $data->getSite();
101
-		if (!$site) {
102
-			// must be set in config
103
-			$site = $config->site;
104
-			if (!$site instanceof \ElggSite) {
105
-				throw new \RuntimeException('Before installation, config->site must have an unsaved ElggSite.');
106
-			}
107
-		}
108
-		$config->site = $site;
109
-		$config->sitename = $site->name;
110
-		$config->sitedescription = $site->description;
111
-
112
-		$services->subtypeTable->setCachedValues($data->getSubtypeData());
113
-
114
-		$services->plugins->setBootPlugins($data->getActivePlugins());
115
-
116
-		$services->pluginSettingsCache->setCachedValues($data->getPluginSettings());
117
-
118
-		$services->logger->setLevel($config->debug);
119
-		if ($config->debug) {
120
-			$services->logger->setDisplay(true);
121
-		}
122
-
123
-		// finish boot sequence
124
-		_elgg_session_boot($services);
125
-
126
-		if ($config->system_cache_enabled) {
127
-			$config->system_cache_loaded = false;
128
-
129
-			if ($services->views->configureFromCache($services->systemCache)) {
130
-				$config->system_cache_loaded = true;
131
-			}
132
-		}
133
-
134
-		// we don't store langs in boot data because it varies by user
135
-		$services->translator->loadTranslations();
136
-
137
-		// we always need site->email and user->icontime, so load them together
138
-		$user_guid = $services->session->getLoggedInUserGuid();
139
-		if ($user_guid) {
140
-			$services->metadataCache->populateFromEntities([$user_guid]);
141
-		}
142
-
143
-		// invalidate on some actions just in case other invalidation triggers miss something
144
-		$services->hooks->registerHandler('action', 'all', function ($action) {
145
-			if (0 === strpos($action, 'admin/' || $action === 'plugins/settings/save')) {
146
-				$this->invalidateCache();
147
-			}
148
-		}, 1);
149
-	}
150
-
151
-	/**
152
-	 * Invalidate the cache item
153
-	 *
154
-	 * @return void
155
-	 */
156
-	public function invalidateCache() {
157
-		if (!$this->was_cleared) {
158
-			$this->getStashItem(_elgg_config())->clear();
159
-			$this->was_cleared = true;
160
-		}
161
-	}
162
-
163
-	/**
164
-	 * Get the boot data
165
-	 *
166
-	 * @param Config   $config    Elgg config object
167
-	 * @param Database $db        Elgg database
168
-	 * @param bool     $installed Is the site installed?
169
-	 *
170
-	 * @return BootData
171
-	 *
172
-	 * @throws \InstallationException
173
-	 */
174
-	private function getBootData(Config $config, Database $db, $installed) {
175
-		$config->_boot_cache_hit = false;
176
-
177
-		if (!$config->boot_cache_ttl) {
178
-			$data = new BootData();
179
-			$data->populate($db, _elgg_services()->entityTable, _elgg_services()->plugins, $installed);
180
-			return $data;
181
-		}
182
-
183
-		$item = $this->getStashItem($config);
184
-		$item->setInvalidationMethod(Invalidation::NONE);
185
-		$data = $item->get();
186
-		if ($item->isMiss()) {
187
-			$data = new BootData();
188
-			$data->populate($db, _elgg_services()->entityTable, _elgg_services()->plugins, $installed);
189
-			$item->set($data);
190
-			$item->expiresAfter($config->boot_cache_ttl);
191
-			$item->save();
192
-		} else {
193
-			$config->_boot_cache_hit = true;
194
-		}
195
-
196
-		return $data;
197
-	}
198
-
199
-	/**
200
-	 * Get a Stash cache item
201
-	 *
202
-	 * @param Config $config Elgg config
203
-	 *
204
-	 * @return \Stash\Interfaces\ItemInterface
205
-	 */
206
-	private function getStashItem(Config $config) {
207
-		if ($config->memcache && class_exists('Memcache')) {
208
-			$options = [];
209
-			if ($config->memcache_servers) {
210
-				$options['servers'] = $config->memcache_servers;
211
-			}
212
-			$driver = new Memcache($options);
213
-		} else {
214
-			if (!$config->dataroot) {
215
-				// we're in the installer
216
-				$driver = new BlackHole();
217
-			} else {
218
-				$driver = new FileSystem([
219
-					'path' => $config->dataroot,
220
-				]);
221
-			}
222
-		}
223
-		return (new Pool($driver))->getItem("boot_data");
224
-	}
20
+    use Profilable;
21
+
22
+    /**
23
+     * Under load, a short TTL gives nearly all of the benefits of a longer TTL, but it also ensures
24
+     * that, should cache invalidation fail for some reason, it'll be rebuilt quickly anyway.
25
+     *
26
+     * In 2.x we do not cache by default. This will likely change to 10 in 3.0.
27
+     */
28
+    const DEFAULT_BOOT_CACHE_TTL = 0;
29
+
30
+    /**
31
+     * Has the cache already been invalidated this request? Avoids thrashing
32
+     *
33
+     * @var bool
34
+     */
35
+    private $was_cleared = false;
36
+
37
+    /**
38
+     * Boots the engine
39
+     *
40
+     * @param ServiceProvider $services Services
41
+     * @return void
42
+     */
43
+    public function boot(ServiceProvider $services) {
44
+        $db = $services->db;
45
+        $config = $services->config;
46
+
47
+        // set cookie values for session and remember me
48
+        $config->getCookieConfig();
49
+
50
+        // defaults in case these aren't in config table
51
+        if ($config->boot_cache_ttl === null) {
52
+            $config->boot_cache_ttl = self::DEFAULT_BOOT_CACHE_TTL;
53
+        }
54
+        if ($config->simplecache_enabled === null) {
55
+            $config->simplecache_enabled = 0;
56
+        }
57
+        if ($config->system_cache_enabled === null) {
58
+            $config->system_cache_enabled = false;
59
+        }
60
+        if ($config->simplecache_lastupdate === null) {
61
+            $config->simplecache_lastupdate = 0;
62
+        }
63
+
64
+        // we were using NOTICE temporarily so we can't just check for null
65
+        if (!$config->hasInitialValue('debug')) {
66
+            $config->debug = '';
67
+        }
68
+
69
+        // copy all table values into config
70
+        $config->mergeValues($services->configTable->getAll());
71
+
72
+        // needs to be set before [init, system] for links in html head
73
+        $config->lastcache = (int) $config->simplecache_lastupdate;
74
+
75
+        if (!$config->elgg_config_set_secret) {
76
+            $site_secret = SiteSecret::fromConfig($config);
77
+            if (!$site_secret) {
78
+                // The 2.3 installer doesn't create a site key (it's created on-demand on the first request)
79
+                // so for our Travis upgrade testing we need to check for this and create one on the spot.
80
+                if (defined('UPGRADING')) {
81
+                    $site_secret = SiteSecret::regenerate($services->crypto, $services->configTable);
82
+                }
83
+            }
84
+            if ($site_secret) {
85
+                $services->setValue('siteSecret', $site_secret);
86
+            } else {
87
+                throw new \RuntimeException('The site secret is not set.');
88
+            }
89
+        }
90
+
91
+        $installed = isset($config->installed);
92
+
93
+        if ($this->timer) {
94
+            $this->timer->begin([__CLASS__ . '::getBootData']);
95
+        }
96
+
97
+        // early config is done, now get the core boot data
98
+        $data = $this->getBootData($config, $db, $installed);
99
+
100
+        $site = $data->getSite();
101
+        if (!$site) {
102
+            // must be set in config
103
+            $site = $config->site;
104
+            if (!$site instanceof \ElggSite) {
105
+                throw new \RuntimeException('Before installation, config->site must have an unsaved ElggSite.');
106
+            }
107
+        }
108
+        $config->site = $site;
109
+        $config->sitename = $site->name;
110
+        $config->sitedescription = $site->description;
111
+
112
+        $services->subtypeTable->setCachedValues($data->getSubtypeData());
113
+
114
+        $services->plugins->setBootPlugins($data->getActivePlugins());
115
+
116
+        $services->pluginSettingsCache->setCachedValues($data->getPluginSettings());
117
+
118
+        $services->logger->setLevel($config->debug);
119
+        if ($config->debug) {
120
+            $services->logger->setDisplay(true);
121
+        }
122
+
123
+        // finish boot sequence
124
+        _elgg_session_boot($services);
125
+
126
+        if ($config->system_cache_enabled) {
127
+            $config->system_cache_loaded = false;
128
+
129
+            if ($services->views->configureFromCache($services->systemCache)) {
130
+                $config->system_cache_loaded = true;
131
+            }
132
+        }
133
+
134
+        // we don't store langs in boot data because it varies by user
135
+        $services->translator->loadTranslations();
136
+
137
+        // we always need site->email and user->icontime, so load them together
138
+        $user_guid = $services->session->getLoggedInUserGuid();
139
+        if ($user_guid) {
140
+            $services->metadataCache->populateFromEntities([$user_guid]);
141
+        }
142
+
143
+        // invalidate on some actions just in case other invalidation triggers miss something
144
+        $services->hooks->registerHandler('action', 'all', function ($action) {
145
+            if (0 === strpos($action, 'admin/' || $action === 'plugins/settings/save')) {
146
+                $this->invalidateCache();
147
+            }
148
+        }, 1);
149
+    }
150
+
151
+    /**
152
+     * Invalidate the cache item
153
+     *
154
+     * @return void
155
+     */
156
+    public function invalidateCache() {
157
+        if (!$this->was_cleared) {
158
+            $this->getStashItem(_elgg_config())->clear();
159
+            $this->was_cleared = true;
160
+        }
161
+    }
162
+
163
+    /**
164
+     * Get the boot data
165
+     *
166
+     * @param Config   $config    Elgg config object
167
+     * @param Database $db        Elgg database
168
+     * @param bool     $installed Is the site installed?
169
+     *
170
+     * @return BootData
171
+     *
172
+     * @throws \InstallationException
173
+     */
174
+    private function getBootData(Config $config, Database $db, $installed) {
175
+        $config->_boot_cache_hit = false;
176
+
177
+        if (!$config->boot_cache_ttl) {
178
+            $data = new BootData();
179
+            $data->populate($db, _elgg_services()->entityTable, _elgg_services()->plugins, $installed);
180
+            return $data;
181
+        }
182
+
183
+        $item = $this->getStashItem($config);
184
+        $item->setInvalidationMethod(Invalidation::NONE);
185
+        $data = $item->get();
186
+        if ($item->isMiss()) {
187
+            $data = new BootData();
188
+            $data->populate($db, _elgg_services()->entityTable, _elgg_services()->plugins, $installed);
189
+            $item->set($data);
190
+            $item->expiresAfter($config->boot_cache_ttl);
191
+            $item->save();
192
+        } else {
193
+            $config->_boot_cache_hit = true;
194
+        }
195
+
196
+        return $data;
197
+    }
198
+
199
+    /**
200
+     * Get a Stash cache item
201
+     *
202
+     * @param Config $config Elgg config
203
+     *
204
+     * @return \Stash\Interfaces\ItemInterface
205
+     */
206
+    private function getStashItem(Config $config) {
207
+        if ($config->memcache && class_exists('Memcache')) {
208
+            $options = [];
209
+            if ($config->memcache_servers) {
210
+                $options['servers'] = $config->memcache_servers;
211
+            }
212
+            $driver = new Memcache($options);
213
+        } else {
214
+            if (!$config->dataroot) {
215
+                // we're in the installer
216
+                $driver = new BlackHole();
217
+            } else {
218
+                $driver = new FileSystem([
219
+                    'path' => $config->dataroot,
220
+                ]);
221
+            }
222
+        }
223
+        return (new Pool($driver))->getItem("boot_data");
224
+    }
225 225
 }
Please login to merge, or discard this patch.
engine/classes/ElggInstaller.php 1 patch
Indentation   +1404 added lines, -1404 removed lines patch added patch discarded remove patch
@@ -34,1412 +34,1412 @@
 block discarded – undo
34 34
  */
35 35
 class ElggInstaller {
36 36
 	
37
-	private $steps = [
38
-		'welcome',
39
-		'requirements',
40
-		'database',
41
-		'settings',
42
-		'admin',
43
-		'complete',
44
-		];
45
-
46
-	private $has_completed = [
47
-		'config' => false,
48
-		'database' => false,
49
-		'settings' => false,
50
-		'admin' => false,
51
-	];
52
-
53
-	private $is_action = false;
54
-
55
-	private $autoLogin = true;
56
-
57
-	/**
58
-	 * @var ServiceProvider
59
-	 */
60
-	private $services;
61
-
62
-	/**
63
-	 * @var Config
64
-	 */
65
-	private $config;
66
-
67
-	/**
68
-	 * @var Application
69
-	 */
70
-	private $app;
71
-
72
-	/**
73
-	 * Dispatches a request to one of the step controllers
74
-	 *
75
-	 * @return void
76
-	 * @throws InstallationException
77
-	 */
78
-	public function run() {
79
-		$this->getApp();
80
-
81
-		$this->is_action = $this->services->request->getMethod() === 'POST';
82
-
83
-		$step = get_input('step', 'welcome');
84
-
85
-		if (!in_array($step, $this->getSteps())) {
86
-			$msg = elgg_echo('InstallationException:UnknownStep', [$step]);
87
-			throw new InstallationException($msg);
88
-		}
89
-
90
-		$this->determineInstallStatus();
37
+    private $steps = [
38
+        'welcome',
39
+        'requirements',
40
+        'database',
41
+        'settings',
42
+        'admin',
43
+        'complete',
44
+        ];
45
+
46
+    private $has_completed = [
47
+        'config' => false,
48
+        'database' => false,
49
+        'settings' => false,
50
+        'admin' => false,
51
+    ];
52
+
53
+    private $is_action = false;
54
+
55
+    private $autoLogin = true;
56
+
57
+    /**
58
+     * @var ServiceProvider
59
+     */
60
+    private $services;
61
+
62
+    /**
63
+     * @var Config
64
+     */
65
+    private $config;
66
+
67
+    /**
68
+     * @var Application
69
+     */
70
+    private $app;
71
+
72
+    /**
73
+     * Dispatches a request to one of the step controllers
74
+     *
75
+     * @return void
76
+     * @throws InstallationException
77
+     */
78
+    public function run() {
79
+        $this->getApp();
80
+
81
+        $this->is_action = $this->services->request->getMethod() === 'POST';
82
+
83
+        $step = get_input('step', 'welcome');
84
+
85
+        if (!in_array($step, $this->getSteps())) {
86
+            $msg = elgg_echo('InstallationException:UnknownStep', [$step]);
87
+            throw new InstallationException($msg);
88
+        }
89
+
90
+        $this->determineInstallStatus();
91 91
 	
92
-		$this->checkInstallCompletion($step);
93
-
94
-		// check if this is an install being resumed
95
-		$this->resumeInstall($step);
96
-
97
-		$this->finishBootstrapping($step);
98
-
99
-		$params = $this->services->request->request->all();
100
-
101
-		$method = "run" . ucwords($step);
102
-		$this->$method($params);
103
-	}
104
-
105
-	/**
106
-	 * Build the application needed by the installer
107
-	 *
108
-	 * @return Application
109
-	 * @throws InstallationException
110
-	 */
111
-	protected function getApp() {
112
-		if ($this->app) {
113
-			return $this->app;
114
-		}
115
-
116
-		$config = new Config();
117
-		$config->elgg_config_locks = false;
118
-		$config->installer_running = true;
119
-		$config->dbencoding = 'utf8mb4';
120
-
121
-		$this->config = $config;
122
-		$this->services = new ServiceProvider($config);
123
-
124
-		$app = Application::factory([
125
-			'service_provider' => $this->services,
126
-			'handle_exceptions' => false,
127
-			'handle_shutdown' => false,
128
-
129
-			// allows settings.php to be loaded, which might try to write to global $CONFIG
130
-			// which is only a problem due to the config values that deep-write to arrays,
131
-			// like cookies.
132
-			'set_global_config' => false,
133
-		]);
134
-		$app->loadCore();
135
-		$this->app = $app;
136
-
137
-		$this->services->setValue('session', \ElggSession::getMock());
138
-		$this->services->views->setViewtype('installation');
139
-		$this->services->views->registerPluginViews(Paths::elgg());
140
-		$this->services->translator->registerTranslations(Paths::elgg() . "install/languages/", true);
141
-
142
-		return $this->app;
143
-	}
144
-
145
-	/**
146
-	 * Set the auto login flag
147
-	 *
148
-	 * @param bool $flag Auto login
149
-	 *
150
-	 * @return void
151
-	 */
152
-	public function setAutoLogin($flag) {
153
-		$this->autoLogin = (bool) $flag;
154
-	}
155
-
156
-	/**
157
-	 * A batch install of Elgg
158
-	 *
159
-	 * All required parameters must be passed in as an associative array. See
160
-	 * $requiredParams for a list of them. This creates the necessary files,
161
-	 * loads the database, configures the site settings, and creates the admin
162
-	 * account. If it fails, an exception is thrown. It does not check any of
163
-	 * the requirements as the multiple step web installer does.
164
-	 *
165
-	 * @param array $params          Array of key value pairs
166
-	 * @param bool  $create_htaccess Should .htaccess be created
167
-	 *
168
-	 * @return void
169
-	 * @throws InstallationException
170
-	 */
171
-	public function batchInstall(array $params, $create_htaccess = false) {
172
-		$this->getApp();
173
-
174
-		$defaults = [
175
-			'dbhost' => 'localhost',
176
-			'dbprefix' => 'elgg_',
177
-			'language' => 'en',
178
-			'siteaccess' => ACCESS_PUBLIC,
179
-		];
180
-		$params = array_merge($defaults, $params);
181
-
182
-		$required_params = [
183
-			'dbuser',
184
-			'dbpassword',
185
-			'dbname',
186
-			'sitename',
187
-			'wwwroot',
188
-			'dataroot',
189
-			'displayname',
190
-			'email',
191
-			'username',
192
-			'password',
193
-		];
194
-		foreach ($required_params as $key) {
195
-			if (empty($params[$key])) {
196
-				$msg = elgg_echo('install:error:requiredfield', [$key]);
197
-				throw new InstallationException($msg);
198
-			}
199
-		}
200
-
201
-		// password is passed in once
202
-		$params['password1'] = $params['password2'] = $params['password'];
203
-
204
-		if ($create_htaccess) {
205
-			$rewrite_tester = new ElggRewriteTester();
206
-			if (!$rewrite_tester->createHtaccess($params['wwwroot'])) {
207
-				throw new InstallationException(elgg_echo('install:error:htaccess'));
208
-			}
209
-		}
210
-
211
-		$this->determineInstallStatus();
212
-
213
-		if (!$this->has_completed['config']) {
214
-			if (!$this->createSettingsFile($params)) {
215
-				throw new InstallationException(elgg_echo('install:error:settings'));
216
-			}
217
-		}
218
-
219
-		$this->loadSettingsFile();
220
-
221
-		// Make sure settings file matches parameters
222
-		$config = $this->config;
223
-		$config_keys = [
224
-			// param key => config key
225
-			'dbhost' => 'dbhost',
226
-			'dbuser' => 'dbuser',
227
-			'dbpassword' => 'dbpass',
228
-			'dbname' => 'dbname',
229
-			'dataroot' => 'dataroot',
230
-			'dbprefix' => 'dbprefix',
231
-		];
232
-		foreach ($config_keys as $params_key => $config_key) {
233
-			if ($params[$params_key] !== $config->$config_key) {
234
-				throw new InstallationException(elgg_echo('install:error:settings_mismatch', [$config_key]));
235
-			}
236
-		}
237
-
238
-		if (!$this->connectToDatabase()) {
239
-			throw new InstallationException(elgg_echo('install:error:databasesettings'));
240
-		}
241
-
242
-		if (!$this->has_completed['database']) {
243
-			if (!$this->installDatabase()) {
244
-				throw new InstallationException(elgg_echo('install:error:cannotloadtables'));
245
-			}
246
-		}
247
-
248
-		// load remaining core libraries
249
-		$this->finishBootstrapping('settings');
250
-
251
-		if (!$this->saveSiteSettings($params)) {
252
-			throw new InstallationException(elgg_echo('install:error:savesitesettings'));
253
-		}
254
-
255
-		if (!$this->createAdminAccount($params)) {
256
-			throw new InstallationException(elgg_echo('install:admin:cannot_create'));
257
-		}
258
-	}
259
-
260
-	/**
261
-	 * Renders the data passed by a controller
262
-	 *
263
-	 * @param string $step The current step
264
-	 * @param array  $vars Array of vars to pass to the view
265
-	 *
266
-	 * @return void
267
-	 */
268
-	protected function render($step, $vars = []) {
269
-		$vars['next_step'] = $this->getNextStep($step);
270
-
271
-		$title = elgg_echo("install:$step");
272
-		$body = elgg_view("install/pages/$step", $vars);
92
+        $this->checkInstallCompletion($step);
93
+
94
+        // check if this is an install being resumed
95
+        $this->resumeInstall($step);
96
+
97
+        $this->finishBootstrapping($step);
98
+
99
+        $params = $this->services->request->request->all();
100
+
101
+        $method = "run" . ucwords($step);
102
+        $this->$method($params);
103
+    }
104
+
105
+    /**
106
+     * Build the application needed by the installer
107
+     *
108
+     * @return Application
109
+     * @throws InstallationException
110
+     */
111
+    protected function getApp() {
112
+        if ($this->app) {
113
+            return $this->app;
114
+        }
115
+
116
+        $config = new Config();
117
+        $config->elgg_config_locks = false;
118
+        $config->installer_running = true;
119
+        $config->dbencoding = 'utf8mb4';
120
+
121
+        $this->config = $config;
122
+        $this->services = new ServiceProvider($config);
123
+
124
+        $app = Application::factory([
125
+            'service_provider' => $this->services,
126
+            'handle_exceptions' => false,
127
+            'handle_shutdown' => false,
128
+
129
+            // allows settings.php to be loaded, which might try to write to global $CONFIG
130
+            // which is only a problem due to the config values that deep-write to arrays,
131
+            // like cookies.
132
+            'set_global_config' => false,
133
+        ]);
134
+        $app->loadCore();
135
+        $this->app = $app;
136
+
137
+        $this->services->setValue('session', \ElggSession::getMock());
138
+        $this->services->views->setViewtype('installation');
139
+        $this->services->views->registerPluginViews(Paths::elgg());
140
+        $this->services->translator->registerTranslations(Paths::elgg() . "install/languages/", true);
141
+
142
+        return $this->app;
143
+    }
144
+
145
+    /**
146
+     * Set the auto login flag
147
+     *
148
+     * @param bool $flag Auto login
149
+     *
150
+     * @return void
151
+     */
152
+    public function setAutoLogin($flag) {
153
+        $this->autoLogin = (bool) $flag;
154
+    }
155
+
156
+    /**
157
+     * A batch install of Elgg
158
+     *
159
+     * All required parameters must be passed in as an associative array. See
160
+     * $requiredParams for a list of them. This creates the necessary files,
161
+     * loads the database, configures the site settings, and creates the admin
162
+     * account. If it fails, an exception is thrown. It does not check any of
163
+     * the requirements as the multiple step web installer does.
164
+     *
165
+     * @param array $params          Array of key value pairs
166
+     * @param bool  $create_htaccess Should .htaccess be created
167
+     *
168
+     * @return void
169
+     * @throws InstallationException
170
+     */
171
+    public function batchInstall(array $params, $create_htaccess = false) {
172
+        $this->getApp();
173
+
174
+        $defaults = [
175
+            'dbhost' => 'localhost',
176
+            'dbprefix' => 'elgg_',
177
+            'language' => 'en',
178
+            'siteaccess' => ACCESS_PUBLIC,
179
+        ];
180
+        $params = array_merge($defaults, $params);
181
+
182
+        $required_params = [
183
+            'dbuser',
184
+            'dbpassword',
185
+            'dbname',
186
+            'sitename',
187
+            'wwwroot',
188
+            'dataroot',
189
+            'displayname',
190
+            'email',
191
+            'username',
192
+            'password',
193
+        ];
194
+        foreach ($required_params as $key) {
195
+            if (empty($params[$key])) {
196
+                $msg = elgg_echo('install:error:requiredfield', [$key]);
197
+                throw new InstallationException($msg);
198
+            }
199
+        }
200
+
201
+        // password is passed in once
202
+        $params['password1'] = $params['password2'] = $params['password'];
203
+
204
+        if ($create_htaccess) {
205
+            $rewrite_tester = new ElggRewriteTester();
206
+            if (!$rewrite_tester->createHtaccess($params['wwwroot'])) {
207
+                throw new InstallationException(elgg_echo('install:error:htaccess'));
208
+            }
209
+        }
210
+
211
+        $this->determineInstallStatus();
212
+
213
+        if (!$this->has_completed['config']) {
214
+            if (!$this->createSettingsFile($params)) {
215
+                throw new InstallationException(elgg_echo('install:error:settings'));
216
+            }
217
+        }
218
+
219
+        $this->loadSettingsFile();
220
+
221
+        // Make sure settings file matches parameters
222
+        $config = $this->config;
223
+        $config_keys = [
224
+            // param key => config key
225
+            'dbhost' => 'dbhost',
226
+            'dbuser' => 'dbuser',
227
+            'dbpassword' => 'dbpass',
228
+            'dbname' => 'dbname',
229
+            'dataroot' => 'dataroot',
230
+            'dbprefix' => 'dbprefix',
231
+        ];
232
+        foreach ($config_keys as $params_key => $config_key) {
233
+            if ($params[$params_key] !== $config->$config_key) {
234
+                throw new InstallationException(elgg_echo('install:error:settings_mismatch', [$config_key]));
235
+            }
236
+        }
237
+
238
+        if (!$this->connectToDatabase()) {
239
+            throw new InstallationException(elgg_echo('install:error:databasesettings'));
240
+        }
241
+
242
+        if (!$this->has_completed['database']) {
243
+            if (!$this->installDatabase()) {
244
+                throw new InstallationException(elgg_echo('install:error:cannotloadtables'));
245
+            }
246
+        }
247
+
248
+        // load remaining core libraries
249
+        $this->finishBootstrapping('settings');
250
+
251
+        if (!$this->saveSiteSettings($params)) {
252
+            throw new InstallationException(elgg_echo('install:error:savesitesettings'));
253
+        }
254
+
255
+        if (!$this->createAdminAccount($params)) {
256
+            throw new InstallationException(elgg_echo('install:admin:cannot_create'));
257
+        }
258
+    }
259
+
260
+    /**
261
+     * Renders the data passed by a controller
262
+     *
263
+     * @param string $step The current step
264
+     * @param array  $vars Array of vars to pass to the view
265
+     *
266
+     * @return void
267
+     */
268
+    protected function render($step, $vars = []) {
269
+        $vars['next_step'] = $this->getNextStep($step);
270
+
271
+        $title = elgg_echo("install:$step");
272
+        $body = elgg_view("install/pages/$step", $vars);
273 273
 				
274
-		echo elgg_view_page(
275
-				$title,
276
-				$body,
277
-				'default',
278
-				[
279
-					'step' => $step,
280
-					'steps' => $this->getSteps(),
281
-					]
282
-				);
283
-		exit;
284
-	}
285
-
286
-	/**
287
-	 * Step controllers
288
-	 */
289
-
290
-	/**
291
-	 * Welcome controller
292
-	 *
293
-	 * @param array $vars Not used
294
-	 *
295
-	 * @return void
296
-	 */
297
-	protected function runWelcome($vars) {
298
-		$this->render('welcome');
299
-	}
300
-
301
-	/**
302
-	 * Requirements controller
303
-	 *
304
-	 * Checks version of php, libraries, permissions, and rewrite rules
305
-	 *
306
-	 * @param array $vars Vars
307
-	 *
308
-	 * @return void
309
-	 */
310
-	protected function runRequirements($vars) {
311
-
312
-		$report = [];
313
-
314
-		// check PHP parameters and libraries
315
-		$this->checkPHP($report);
316
-
317
-		// check URL rewriting
318
-		$this->checkRewriteRules($report);
319
-
320
-		// check for existence of settings file
321
-		if ($this->checkSettingsFile($report) != true) {
322
-			// no file, so check permissions on engine directory
323
-			$this->isInstallDirWritable($report);
324
-		}
325
-
326
-		// check the database later
327
-		$report['database'] = [[
328
-			'severity' => 'info',
329
-			'message' => elgg_echo('install:check:database')
330
-		]];
331
-
332
-		// any failures?
333
-		$numFailures = $this->countNumConditions($report, 'failure');
334
-
335
-		// any warnings
336
-		$numWarnings = $this->countNumConditions($report, 'warning');
337
-
338
-
339
-		$params = [
340
-			'report' => $report,
341
-			'num_failures' => $numFailures,
342
-			'num_warnings' => $numWarnings,
343
-		];
344
-
345
-		$this->render('requirements', $params);
346
-	}
347
-
348
-	/**
349
-	 * Database set up controller
350
-	 *
351
-	 * Creates the settings.php file and creates the database tables
352
-	 *
353
-	 * @param array $submissionVars Submitted form variables
354
-	 *
355
-	 * @return void
356
-	 */
357
-	protected function runDatabase($submissionVars) {
358
-
359
-		$formVars = [
360
-			'dbuser' => [
361
-				'type' => 'text',
362
-				'value' => '',
363
-				'required' => true,
364
-				],
365
-			'dbpassword' => [
366
-				'type' => 'password',
367
-				'value' => '',
368
-				'required' => false,
369
-				],
370
-			'dbname' => [
371
-				'type' => 'text',
372
-				'value' => '',
373
-				'required' => true,
374
-				],
375
-			'dbhost' => [
376
-				'type' => 'text',
377
-				'value' => 'localhost',
378
-				'required' => true,
379
-				],
380
-			'dbprefix' => [
381
-				'type' => 'text',
382
-				'value' => 'elgg_',
383
-				'required' => true,
384
-				],
385
-			'dataroot' => [
386
-				'type' => 'text',
387
-				'value' => '',
388
-				'required' => true,
389
-			],
390
-			'wwwroot' => [
391
-				'type' => 'url',
392
-				'value' => _elgg_services()->config->wwwroot,
393
-				'required' => true,
394
-			],
395
-			'timezone' => [
396
-				'type' => 'dropdown',
397
-				'value' => 'UTC',
398
-				'options' => \DateTimeZone::listIdentifiers(),
399
-				'required' => true
400
-			]
401
-		];
402
-
403
-		if ($this->checkSettingsFile()) {
404
-			// user manually created settings file so we fake out action test
405
-			$this->is_action = true;
406
-		}
407
-
408
-		if ($this->is_action) {
409
-			call_user_func(function () use ($submissionVars, $formVars) {
410
-				// only create settings file if it doesn't exist
411
-				if (!$this->checkSettingsFile()) {
412
-					if (!$this->validateDatabaseVars($submissionVars, $formVars)) {
413
-						// error so we break out of action and serve same page
414
-						return;
415
-					}
416
-
417
-					if (!$this->createSettingsFile($submissionVars)) {
418
-						return;
419
-					}
420
-				}
421
-
422
-				// check db version and connect
423
-				if (!$this->connectToDatabase()) {
424
-					return;
425
-				}
426
-
427
-				if (!$this->installDatabase()) {
428
-					return;
429
-				}
430
-
431
-				system_message(elgg_echo('install:success:database'));
432
-
433
-				$this->continueToNextStep('database');
434
-			});
435
-		}
436
-
437
-		$formVars = $this->makeFormSticky($formVars, $submissionVars);
438
-
439
-		$params = ['variables' => $formVars,];
440
-
441
-		if ($this->checkSettingsFile()) {
442
-			// settings file exists and we're here so failed to create database
443
-			$params['failure'] = true;
444
-		}
445
-
446
-		$this->render('database', $params);
447
-	}
448
-
449
-	/**
450
-	 * Site settings controller
451
-	 *
452
-	 * Sets the site name, URL, data directory, etc.
453
-	 *
454
-	 * @param array $submissionVars Submitted vars
455
-	 *
456
-	 * @return void
457
-	 */
458
-	protected function runSettings($submissionVars) {
459
-		$formVars = [
460
-			'sitename' => [
461
-				'type' => 'text',
462
-				'value' => 'My New Community',
463
-				'required' => true,
464
-				],
465
-			'siteemail' => [
466
-				'type' => 'email',
467
-				'value' => '',
468
-				'required' => false,
469
-				],
470
-			'siteaccess' => [
471
-				'type' => 'access',
472
-				'value' => ACCESS_PUBLIC,
473
-				'required' => true,
474
-				],
475
-		];
476
-
477
-		if ($this->is_action) {
478
-			call_user_func(function () use ($submissionVars, $formVars) {
479
-				if (!$this->validateSettingsVars($submissionVars, $formVars)) {
480
-					return;
481
-				}
482
-
483
-				if (!$this->saveSiteSettings($submissionVars)) {
484
-					return;
485
-				}
486
-
487
-				system_message(elgg_echo('install:success:settings'));
488
-
489
-				$this->continueToNextStep('settings');
490
-			});
491
-		}
492
-
493
-		$formVars = $this->makeFormSticky($formVars, $submissionVars);
494
-
495
-		$this->render('settings', ['variables' => $formVars]);
496
-	}
497
-
498
-	/**
499
-	 * Admin account controller
500
-	 *
501
-	 * Creates an admin user account
502
-	 *
503
-	 * @param array $submissionVars Submitted vars
504
-	 *
505
-	 * @return void
506
-	 */
507
-	protected function runAdmin($submissionVars) {
508
-		$formVars = [
509
-			'displayname' => [
510
-				'type' => 'text',
511
-				'value' => '',
512
-				'required' => true,
513
-				],
514
-			'email' => [
515
-				'type' => 'email',
516
-				'value' => '',
517
-				'required' => true,
518
-				],
519
-			'username' => [
520
-				'type' => 'text',
521
-				'value' => '',
522
-				'required' => true,
523
-				],
524
-			'password1' => [
525
-				'type' => 'password',
526
-				'value' => '',
527
-				'required' => true,
528
-				'pattern' => '.{6,}',
529
-				],
530
-			'password2' => [
531
-				'type' => 'password',
532
-				'value' => '',
533
-				'required' => true,
534
-				],
535
-		];
536
-
537
-		if ($this->is_action) {
538
-			call_user_func(function () use ($submissionVars, $formVars) {
539
-				if (!$this->validateAdminVars($submissionVars, $formVars)) {
540
-					return;
541
-				}
542
-
543
-				if (!$this->createAdminAccount($submissionVars, $this->autoLogin)) {
544
-					return;
545
-				}
546
-
547
-				system_message(elgg_echo('install:success:admin'));
548
-
549
-				$this->continueToNextStep('admin');
550
-			});
551
-		}
552
-
553
-		// Bit of a hack to get the password help to show right number of characters
554
-		// We burn the value into the stored translation.
555
-		$lang = $this->services->translator->getCurrentLanguage();
556
-		$translations = $this->services->translator->getLoadedTranslations();
557
-		$this->services->translator->addTranslation($lang, [
558
-			'install:admin:help:password1' => sprintf(
559
-				$translations[$lang]['install:admin:help:password1'],
560
-				$this->config->min_password_length
561
-			),
562
-		]);
563
-
564
-		$formVars = $this->makeFormSticky($formVars, $submissionVars);
565
-
566
-		$this->render('admin', ['variables' => $formVars]);
567
-	}
568
-
569
-	/**
570
-	 * Controller for last step
571
-	 *
572
-	 * @return void
573
-	 */
574
-	protected function runComplete() {
575
-
576
-		// nudge to check out settings
577
-		$link = elgg_format_element([
578
-			'#tag_name' => 'a',
579
-			'#text' => elgg_echo('install:complete:admin_notice:link_text'),
580
-			'href' => elgg_normalize_url('admin/settings/basic'),
581
-		]);
582
-		$notice = elgg_echo('install:complete:admin_notice', [$link]);
583
-		elgg_add_admin_notice('fresh_install', $notice);
584
-
585
-		$this->render('complete');
586
-	}
587
-
588
-	/**
589
-	 * Step management
590
-	 */
591
-
592
-	/**
593
-	 * Get an array of steps
594
-	 *
595
-	 * @return array
596
-	 */
597
-	protected function getSteps() {
598
-		return $this->steps;
599
-	}
600
-
601
-	/**
602
-	 * Forwards the browser to the next step
603
-	 *
604
-	 * @param string $currentStep Current installation step
605
-	 *
606
-	 * @return void
607
-	 */
608
-	protected function continueToNextStep($currentStep) {
609
-		$this->is_action = false;
610
-		forward($this->getNextStepUrl($currentStep));
611
-	}
612
-
613
-	/**
614
-	 * Get the next step as a string
615
-	 *
616
-	 * @param string $currentStep Current installation step
617
-	 *
618
-	 * @return string
619
-	 */
620
-	protected function getNextStep($currentStep) {
621
-		$index = 1 + array_search($currentStep, $this->steps);
622
-		if (isset($this->steps[$index])) {
623
-			return $this->steps[$index];
624
-		} else {
625
-			return null;
626
-		}
627
-	}
628
-
629
-	/**
630
-	 * Get the URL of the next step
631
-	 *
632
-	 * @param string $currentStep Current installation step
633
-	 *
634
-	 * @return string
635
-	 */
636
-	protected function getNextStepUrl($currentStep) {
637
-		$nextStep = $this->getNextStep($currentStep);
638
-		return $this->config->wwwroot . "install.php?step=$nextStep";
639
-	}
640
-
641
-	/**
642
-	 * Updates $this->has_completed according to the current installation
643
-	 *
644
-	 * @return void
645
-	 * @throws InstallationException
646
-	 */
647
-	protected function determineInstallStatus() {
648
-		$path = Paths::settingsFile();
649
-		if (!is_file($path) || !is_readable($path)) {
650
-			return;
651
-		}
652
-
653
-		$this->loadSettingsFile();
654
-
655
-		$this->has_completed['config'] = true;
656
-
657
-		// must be able to connect to database to jump install steps
658
-		$dbSettingsPass = $this->checkDatabaseSettings(
659
-			$this->config->dbuser,
660
-			$this->config->dbpass,
661
-			$this->config->dbname,
662
-			$this->config->dbhost
663
-		);
664
-
665
-		if (!$dbSettingsPass) {
666
-			return;
667
-		}
668
-
669
-		$db = $this->services->db;
670
-
671
-		// check that the config table has been created
672
-		$result = $db->getData("SHOW TABLES");
673
-		if ($result) {
674
-			foreach ($result as $table) {
675
-				$table = (array) $table;
676
-				if (in_array("{$db->prefix}config", $table)) {
677
-					$this->has_completed['database'] = true;
678
-				}
679
-			}
680
-			if ($this->has_completed['database'] == false) {
681
-				return;
682
-			}
683
-		} else {
684
-			// no tables
685
-			return;
686
-		}
687
-
688
-		// check that the config table has entries
689
-		$query = "SELECT COUNT(*) AS total FROM {$db->prefix}config";
690
-		$result = $db->getData($query);
691
-		if ($result && $result[0]->total > 0) {
692
-			$this->has_completed['settings'] = true;
693
-		} else {
694
-			return;
695
-		}
696
-
697
-		// check that the users entity table has an entry
698
-		$query = "SELECT COUNT(*) AS total FROM {$db->prefix}users_entity";
699
-		$result = $db->getData($query);
700
-		if ($result && $result[0]->total > 0) {
701
-			$this->has_completed['admin'] = true;
702
-		} else {
703
-			return;
704
-		}
705
-	}
706
-
707
-	/**
708
-	 * Security check to ensure the installer cannot be run after installation
709
-	 * has finished. If this is detected, the viewer is sent to the front page.
710
-	 *
711
-	 * @param string $step Installation step to check against
712
-	 *
713
-	 * @return void
714
-	 */
715
-	protected function checkInstallCompletion($step) {
716
-		if ($step != 'complete') {
717
-			if (!in_array(false, $this->has_completed)) {
718
-				// install complete but someone is trying to view an install page
719
-				forward();
720
-			}
721
-		}
722
-	}
723
-
724
-	/**
725
-	 * Check if this is a case of a install being resumed and figure
726
-	 * out where to continue from. Returns the best guess on the step.
727
-	 *
728
-	 * @param string $step Installation step to resume from
729
-	 *
730
-	 * @return string
731
-	 */
732
-	protected function resumeInstall($step) {
733
-		// only do a resume from the first step
734
-		if ($step !== 'welcome') {
735
-			return;
736
-		}
737
-
738
-		if ($this->has_completed['database'] == false) {
739
-			return;
740
-		}
741
-
742
-		if ($this->has_completed['settings'] == false) {
743
-			forward("install.php?step=settings");
744
-		}
745
-
746
-		if ($this->has_completed['admin'] == false) {
747
-			forward("install.php?step=admin");
748
-		}
749
-
750
-		// everything appears to be set up
751
-		forward("install.php?step=complete");
752
-	}
753
-
754
-	/**
755
-	 * Bootstrapping
756
-	 */
757
-
758
-	/**
759
-	 * Load remaining engine libraries and complete bootstrapping
760
-	 *
761
-	 * @param string $step Which step to boot strap for. Required because
762
-	 *                     boot strapping is different until the DB is populated.
763
-	 *
764
-	 * @return void
765
-	 * @throws InstallationException
766
-	 */
767
-	protected function finishBootstrapping($step) {
768
-
769
-		$index_db = array_search('database', $this->getSteps());
770
-		$index_settings = array_search('settings', $this->getSteps());
771
-		$index_admin = array_search('admin', $this->getSteps());
772
-		$index_complete = array_search('complete', $this->getSteps());
773
-		$index_step = array_search($step, $this->getSteps());
774
-
775
-		// To log in the user, we need to use the Elgg core session handling.
776
-		// Otherwise, use default php session handling
777
-		$use_elgg_session = ($index_step == $index_admin && $this->is_action) || ($index_step == $index_complete);
778
-		if (!$use_elgg_session) {
779
-			$session = ElggSession::fromFiles($this->config);
780
-			$session->setName('Elgg_install');
781
-			$this->services->setValue('session', $session);
782
-		}
783
-
784
-		if ($index_step > $index_db) {
785
-			// once the database has been created, load rest of engine
786
-
787
-			// dummy site needed to boot
788
-			$this->config->site = new ElggSite();
789
-
790
-			$this->app->bootCore();
791
-		}
792
-	}
793
-
794
-	/**
795
-	 * Load settings
796
-	 *
797
-	 * @return void
798
-	 * @throws InstallationException
799
-	 */
800
-	protected function loadSettingsFile() {
801
-		try {
802
-			$config = Config::fromFile(Paths::settingsFile());
803
-			$this->config->mergeValues($config->getValues());
804
-
805
-			// in case the DB instance is already captured in services, we re-inject its settings.
806
-			$this->services->db->resetConnections(DbConfig::fromElggConfig($this->config));
807
-		} catch (\Exception $e) {
808
-			$msg = elgg_echo('InstallationException:CannotLoadSettings');
809
-			throw new InstallationException($msg, 0, $e);
810
-		}
811
-	}
812
-
813
-	/**
814
-	 * Action handling methods
815
-	 */
816
-
817
-	/**
818
-	 * If form is reshown, remember previously submitted variables
819
-	 *
820
-	 * @param array $formVars       Vars int he form
821
-	 * @param array $submissionVars Submitted vars
822
-	 *
823
-	 * @return array
824
-	 */
825
-	protected function makeFormSticky($formVars, $submissionVars) {
826
-		foreach ($submissionVars as $field => $value) {
827
-			$formVars[$field]['value'] = $value;
828
-		}
829
-		return $formVars;
830
-	}
831
-
832
-	/* Requirement checks support methods */
833
-
834
-	/**
835
-	 * Indicates whether the webserver can add settings.php on its own or not.
836
-	 *
837
-	 * @param array $report The requirements report object
838
-	 *
839
-	 * @return bool
840
-	 */
841
-	protected function isInstallDirWritable(&$report) {
842
-		if (!is_writable(Paths::projectConfig())) {
843
-			$msg = elgg_echo('install:check:installdir', [Paths::PATH_TO_CONFIG]);
844
-			$report['settings'] = [
845
-				[
846
-					'severity' => 'failure',
847
-					'message' => $msg,
848
-				]
849
-			];
850
-			return false;
851
-		}
852
-
853
-		return true;
854
-	}
855
-
856
-	/**
857
-	 * Check that the settings file exists
858
-	 *
859
-	 * @param array $report The requirements report array
860
-	 *
861
-	 * @return bool
862
-	 */
863
-	protected function checkSettingsFile(&$report = []) {
864
-		if (!is_file(Paths::settingsFile())) {
865
-			return false;
866
-		}
867
-
868
-		if (!is_readable(Paths::settingsFile())) {
869
-			$report['settings'] = [
870
-				[
871
-					'severity' => 'failure',
872
-					'message' => elgg_echo('install:check:readsettings'),
873
-				]
874
-			];
875
-		}
274
+        echo elgg_view_page(
275
+                $title,
276
+                $body,
277
+                'default',
278
+                [
279
+                    'step' => $step,
280
+                    'steps' => $this->getSteps(),
281
+                    ]
282
+                );
283
+        exit;
284
+    }
285
+
286
+    /**
287
+     * Step controllers
288
+     */
289
+
290
+    /**
291
+     * Welcome controller
292
+     *
293
+     * @param array $vars Not used
294
+     *
295
+     * @return void
296
+     */
297
+    protected function runWelcome($vars) {
298
+        $this->render('welcome');
299
+    }
300
+
301
+    /**
302
+     * Requirements controller
303
+     *
304
+     * Checks version of php, libraries, permissions, and rewrite rules
305
+     *
306
+     * @param array $vars Vars
307
+     *
308
+     * @return void
309
+     */
310
+    protected function runRequirements($vars) {
311
+
312
+        $report = [];
313
+
314
+        // check PHP parameters and libraries
315
+        $this->checkPHP($report);
316
+
317
+        // check URL rewriting
318
+        $this->checkRewriteRules($report);
319
+
320
+        // check for existence of settings file
321
+        if ($this->checkSettingsFile($report) != true) {
322
+            // no file, so check permissions on engine directory
323
+            $this->isInstallDirWritable($report);
324
+        }
325
+
326
+        // check the database later
327
+        $report['database'] = [[
328
+            'severity' => 'info',
329
+            'message' => elgg_echo('install:check:database')
330
+        ]];
331
+
332
+        // any failures?
333
+        $numFailures = $this->countNumConditions($report, 'failure');
334
+
335
+        // any warnings
336
+        $numWarnings = $this->countNumConditions($report, 'warning');
337
+
338
+
339
+        $params = [
340
+            'report' => $report,
341
+            'num_failures' => $numFailures,
342
+            'num_warnings' => $numWarnings,
343
+        ];
344
+
345
+        $this->render('requirements', $params);
346
+    }
347
+
348
+    /**
349
+     * Database set up controller
350
+     *
351
+     * Creates the settings.php file and creates the database tables
352
+     *
353
+     * @param array $submissionVars Submitted form variables
354
+     *
355
+     * @return void
356
+     */
357
+    protected function runDatabase($submissionVars) {
358
+
359
+        $formVars = [
360
+            'dbuser' => [
361
+                'type' => 'text',
362
+                'value' => '',
363
+                'required' => true,
364
+                ],
365
+            'dbpassword' => [
366
+                'type' => 'password',
367
+                'value' => '',
368
+                'required' => false,
369
+                ],
370
+            'dbname' => [
371
+                'type' => 'text',
372
+                'value' => '',
373
+                'required' => true,
374
+                ],
375
+            'dbhost' => [
376
+                'type' => 'text',
377
+                'value' => 'localhost',
378
+                'required' => true,
379
+                ],
380
+            'dbprefix' => [
381
+                'type' => 'text',
382
+                'value' => 'elgg_',
383
+                'required' => true,
384
+                ],
385
+            'dataroot' => [
386
+                'type' => 'text',
387
+                'value' => '',
388
+                'required' => true,
389
+            ],
390
+            'wwwroot' => [
391
+                'type' => 'url',
392
+                'value' => _elgg_services()->config->wwwroot,
393
+                'required' => true,
394
+            ],
395
+            'timezone' => [
396
+                'type' => 'dropdown',
397
+                'value' => 'UTC',
398
+                'options' => \DateTimeZone::listIdentifiers(),
399
+                'required' => true
400
+            ]
401
+        ];
402
+
403
+        if ($this->checkSettingsFile()) {
404
+            // user manually created settings file so we fake out action test
405
+            $this->is_action = true;
406
+        }
407
+
408
+        if ($this->is_action) {
409
+            call_user_func(function () use ($submissionVars, $formVars) {
410
+                // only create settings file if it doesn't exist
411
+                if (!$this->checkSettingsFile()) {
412
+                    if (!$this->validateDatabaseVars($submissionVars, $formVars)) {
413
+                        // error so we break out of action and serve same page
414
+                        return;
415
+                    }
416
+
417
+                    if (!$this->createSettingsFile($submissionVars)) {
418
+                        return;
419
+                    }
420
+                }
421
+
422
+                // check db version and connect
423
+                if (!$this->connectToDatabase()) {
424
+                    return;
425
+                }
426
+
427
+                if (!$this->installDatabase()) {
428
+                    return;
429
+                }
430
+
431
+                system_message(elgg_echo('install:success:database'));
432
+
433
+                $this->continueToNextStep('database');
434
+            });
435
+        }
436
+
437
+        $formVars = $this->makeFormSticky($formVars, $submissionVars);
438
+
439
+        $params = ['variables' => $formVars,];
440
+
441
+        if ($this->checkSettingsFile()) {
442
+            // settings file exists and we're here so failed to create database
443
+            $params['failure'] = true;
444
+        }
445
+
446
+        $this->render('database', $params);
447
+    }
448
+
449
+    /**
450
+     * Site settings controller
451
+     *
452
+     * Sets the site name, URL, data directory, etc.
453
+     *
454
+     * @param array $submissionVars Submitted vars
455
+     *
456
+     * @return void
457
+     */
458
+    protected function runSettings($submissionVars) {
459
+        $formVars = [
460
+            'sitename' => [
461
+                'type' => 'text',
462
+                'value' => 'My New Community',
463
+                'required' => true,
464
+                ],
465
+            'siteemail' => [
466
+                'type' => 'email',
467
+                'value' => '',
468
+                'required' => false,
469
+                ],
470
+            'siteaccess' => [
471
+                'type' => 'access',
472
+                'value' => ACCESS_PUBLIC,
473
+                'required' => true,
474
+                ],
475
+        ];
476
+
477
+        if ($this->is_action) {
478
+            call_user_func(function () use ($submissionVars, $formVars) {
479
+                if (!$this->validateSettingsVars($submissionVars, $formVars)) {
480
+                    return;
481
+                }
482
+
483
+                if (!$this->saveSiteSettings($submissionVars)) {
484
+                    return;
485
+                }
486
+
487
+                system_message(elgg_echo('install:success:settings'));
488
+
489
+                $this->continueToNextStep('settings');
490
+            });
491
+        }
492
+
493
+        $formVars = $this->makeFormSticky($formVars, $submissionVars);
494
+
495
+        $this->render('settings', ['variables' => $formVars]);
496
+    }
497
+
498
+    /**
499
+     * Admin account controller
500
+     *
501
+     * Creates an admin user account
502
+     *
503
+     * @param array $submissionVars Submitted vars
504
+     *
505
+     * @return void
506
+     */
507
+    protected function runAdmin($submissionVars) {
508
+        $formVars = [
509
+            'displayname' => [
510
+                'type' => 'text',
511
+                'value' => '',
512
+                'required' => true,
513
+                ],
514
+            'email' => [
515
+                'type' => 'email',
516
+                'value' => '',
517
+                'required' => true,
518
+                ],
519
+            'username' => [
520
+                'type' => 'text',
521
+                'value' => '',
522
+                'required' => true,
523
+                ],
524
+            'password1' => [
525
+                'type' => 'password',
526
+                'value' => '',
527
+                'required' => true,
528
+                'pattern' => '.{6,}',
529
+                ],
530
+            'password2' => [
531
+                'type' => 'password',
532
+                'value' => '',
533
+                'required' => true,
534
+                ],
535
+        ];
536
+
537
+        if ($this->is_action) {
538
+            call_user_func(function () use ($submissionVars, $formVars) {
539
+                if (!$this->validateAdminVars($submissionVars, $formVars)) {
540
+                    return;
541
+                }
542
+
543
+                if (!$this->createAdminAccount($submissionVars, $this->autoLogin)) {
544
+                    return;
545
+                }
546
+
547
+                system_message(elgg_echo('install:success:admin'));
548
+
549
+                $this->continueToNextStep('admin');
550
+            });
551
+        }
552
+
553
+        // Bit of a hack to get the password help to show right number of characters
554
+        // We burn the value into the stored translation.
555
+        $lang = $this->services->translator->getCurrentLanguage();
556
+        $translations = $this->services->translator->getLoadedTranslations();
557
+        $this->services->translator->addTranslation($lang, [
558
+            'install:admin:help:password1' => sprintf(
559
+                $translations[$lang]['install:admin:help:password1'],
560
+                $this->config->min_password_length
561
+            ),
562
+        ]);
563
+
564
+        $formVars = $this->makeFormSticky($formVars, $submissionVars);
565
+
566
+        $this->render('admin', ['variables' => $formVars]);
567
+    }
568
+
569
+    /**
570
+     * Controller for last step
571
+     *
572
+     * @return void
573
+     */
574
+    protected function runComplete() {
575
+
576
+        // nudge to check out settings
577
+        $link = elgg_format_element([
578
+            '#tag_name' => 'a',
579
+            '#text' => elgg_echo('install:complete:admin_notice:link_text'),
580
+            'href' => elgg_normalize_url('admin/settings/basic'),
581
+        ]);
582
+        $notice = elgg_echo('install:complete:admin_notice', [$link]);
583
+        elgg_add_admin_notice('fresh_install', $notice);
584
+
585
+        $this->render('complete');
586
+    }
587
+
588
+    /**
589
+     * Step management
590
+     */
591
+
592
+    /**
593
+     * Get an array of steps
594
+     *
595
+     * @return array
596
+     */
597
+    protected function getSteps() {
598
+        return $this->steps;
599
+    }
600
+
601
+    /**
602
+     * Forwards the browser to the next step
603
+     *
604
+     * @param string $currentStep Current installation step
605
+     *
606
+     * @return void
607
+     */
608
+    protected function continueToNextStep($currentStep) {
609
+        $this->is_action = false;
610
+        forward($this->getNextStepUrl($currentStep));
611
+    }
612
+
613
+    /**
614
+     * Get the next step as a string
615
+     *
616
+     * @param string $currentStep Current installation step
617
+     *
618
+     * @return string
619
+     */
620
+    protected function getNextStep($currentStep) {
621
+        $index = 1 + array_search($currentStep, $this->steps);
622
+        if (isset($this->steps[$index])) {
623
+            return $this->steps[$index];
624
+        } else {
625
+            return null;
626
+        }
627
+    }
628
+
629
+    /**
630
+     * Get the URL of the next step
631
+     *
632
+     * @param string $currentStep Current installation step
633
+     *
634
+     * @return string
635
+     */
636
+    protected function getNextStepUrl($currentStep) {
637
+        $nextStep = $this->getNextStep($currentStep);
638
+        return $this->config->wwwroot . "install.php?step=$nextStep";
639
+    }
640
+
641
+    /**
642
+     * Updates $this->has_completed according to the current installation
643
+     *
644
+     * @return void
645
+     * @throws InstallationException
646
+     */
647
+    protected function determineInstallStatus() {
648
+        $path = Paths::settingsFile();
649
+        if (!is_file($path) || !is_readable($path)) {
650
+            return;
651
+        }
652
+
653
+        $this->loadSettingsFile();
654
+
655
+        $this->has_completed['config'] = true;
656
+
657
+        // must be able to connect to database to jump install steps
658
+        $dbSettingsPass = $this->checkDatabaseSettings(
659
+            $this->config->dbuser,
660
+            $this->config->dbpass,
661
+            $this->config->dbname,
662
+            $this->config->dbhost
663
+        );
664
+
665
+        if (!$dbSettingsPass) {
666
+            return;
667
+        }
668
+
669
+        $db = $this->services->db;
670
+
671
+        // check that the config table has been created
672
+        $result = $db->getData("SHOW TABLES");
673
+        if ($result) {
674
+            foreach ($result as $table) {
675
+                $table = (array) $table;
676
+                if (in_array("{$db->prefix}config", $table)) {
677
+                    $this->has_completed['database'] = true;
678
+                }
679
+            }
680
+            if ($this->has_completed['database'] == false) {
681
+                return;
682
+            }
683
+        } else {
684
+            // no tables
685
+            return;
686
+        }
687
+
688
+        // check that the config table has entries
689
+        $query = "SELECT COUNT(*) AS total FROM {$db->prefix}config";
690
+        $result = $db->getData($query);
691
+        if ($result && $result[0]->total > 0) {
692
+            $this->has_completed['settings'] = true;
693
+        } else {
694
+            return;
695
+        }
696
+
697
+        // check that the users entity table has an entry
698
+        $query = "SELECT COUNT(*) AS total FROM {$db->prefix}users_entity";
699
+        $result = $db->getData($query);
700
+        if ($result && $result[0]->total > 0) {
701
+            $this->has_completed['admin'] = true;
702
+        } else {
703
+            return;
704
+        }
705
+    }
706
+
707
+    /**
708
+     * Security check to ensure the installer cannot be run after installation
709
+     * has finished. If this is detected, the viewer is sent to the front page.
710
+     *
711
+     * @param string $step Installation step to check against
712
+     *
713
+     * @return void
714
+     */
715
+    protected function checkInstallCompletion($step) {
716
+        if ($step != 'complete') {
717
+            if (!in_array(false, $this->has_completed)) {
718
+                // install complete but someone is trying to view an install page
719
+                forward();
720
+            }
721
+        }
722
+    }
723
+
724
+    /**
725
+     * Check if this is a case of a install being resumed and figure
726
+     * out where to continue from. Returns the best guess on the step.
727
+     *
728
+     * @param string $step Installation step to resume from
729
+     *
730
+     * @return string
731
+     */
732
+    protected function resumeInstall($step) {
733
+        // only do a resume from the first step
734
+        if ($step !== 'welcome') {
735
+            return;
736
+        }
737
+
738
+        if ($this->has_completed['database'] == false) {
739
+            return;
740
+        }
741
+
742
+        if ($this->has_completed['settings'] == false) {
743
+            forward("install.php?step=settings");
744
+        }
745
+
746
+        if ($this->has_completed['admin'] == false) {
747
+            forward("install.php?step=admin");
748
+        }
749
+
750
+        // everything appears to be set up
751
+        forward("install.php?step=complete");
752
+    }
753
+
754
+    /**
755
+     * Bootstrapping
756
+     */
757
+
758
+    /**
759
+     * Load remaining engine libraries and complete bootstrapping
760
+     *
761
+     * @param string $step Which step to boot strap for. Required because
762
+     *                     boot strapping is different until the DB is populated.
763
+     *
764
+     * @return void
765
+     * @throws InstallationException
766
+     */
767
+    protected function finishBootstrapping($step) {
768
+
769
+        $index_db = array_search('database', $this->getSteps());
770
+        $index_settings = array_search('settings', $this->getSteps());
771
+        $index_admin = array_search('admin', $this->getSteps());
772
+        $index_complete = array_search('complete', $this->getSteps());
773
+        $index_step = array_search($step, $this->getSteps());
774
+
775
+        // To log in the user, we need to use the Elgg core session handling.
776
+        // Otherwise, use default php session handling
777
+        $use_elgg_session = ($index_step == $index_admin && $this->is_action) || ($index_step == $index_complete);
778
+        if (!$use_elgg_session) {
779
+            $session = ElggSession::fromFiles($this->config);
780
+            $session->setName('Elgg_install');
781
+            $this->services->setValue('session', $session);
782
+        }
783
+
784
+        if ($index_step > $index_db) {
785
+            // once the database has been created, load rest of engine
786
+
787
+            // dummy site needed to boot
788
+            $this->config->site = new ElggSite();
789
+
790
+            $this->app->bootCore();
791
+        }
792
+    }
793
+
794
+    /**
795
+     * Load settings
796
+     *
797
+     * @return void
798
+     * @throws InstallationException
799
+     */
800
+    protected function loadSettingsFile() {
801
+        try {
802
+            $config = Config::fromFile(Paths::settingsFile());
803
+            $this->config->mergeValues($config->getValues());
804
+
805
+            // in case the DB instance is already captured in services, we re-inject its settings.
806
+            $this->services->db->resetConnections(DbConfig::fromElggConfig($this->config));
807
+        } catch (\Exception $e) {
808
+            $msg = elgg_echo('InstallationException:CannotLoadSettings');
809
+            throw new InstallationException($msg, 0, $e);
810
+        }
811
+    }
812
+
813
+    /**
814
+     * Action handling methods
815
+     */
816
+
817
+    /**
818
+     * If form is reshown, remember previously submitted variables
819
+     *
820
+     * @param array $formVars       Vars int he form
821
+     * @param array $submissionVars Submitted vars
822
+     *
823
+     * @return array
824
+     */
825
+    protected function makeFormSticky($formVars, $submissionVars) {
826
+        foreach ($submissionVars as $field => $value) {
827
+            $formVars[$field]['value'] = $value;
828
+        }
829
+        return $formVars;
830
+    }
831
+
832
+    /* Requirement checks support methods */
833
+
834
+    /**
835
+     * Indicates whether the webserver can add settings.php on its own or not.
836
+     *
837
+     * @param array $report The requirements report object
838
+     *
839
+     * @return bool
840
+     */
841
+    protected function isInstallDirWritable(&$report) {
842
+        if (!is_writable(Paths::projectConfig())) {
843
+            $msg = elgg_echo('install:check:installdir', [Paths::PATH_TO_CONFIG]);
844
+            $report['settings'] = [
845
+                [
846
+                    'severity' => 'failure',
847
+                    'message' => $msg,
848
+                ]
849
+            ];
850
+            return false;
851
+        }
852
+
853
+        return true;
854
+    }
855
+
856
+    /**
857
+     * Check that the settings file exists
858
+     *
859
+     * @param array $report The requirements report array
860
+     *
861
+     * @return bool
862
+     */
863
+    protected function checkSettingsFile(&$report = []) {
864
+        if (!is_file(Paths::settingsFile())) {
865
+            return false;
866
+        }
867
+
868
+        if (!is_readable(Paths::settingsFile())) {
869
+            $report['settings'] = [
870
+                [
871
+                    'severity' => 'failure',
872
+                    'message' => elgg_echo('install:check:readsettings'),
873
+                ]
874
+            ];
875
+        }
876 876
 		
877
-		return true;
878
-	}
877
+        return true;
878
+    }
879 879
 	
880
-	/**
881
-	 * Check version of PHP, extensions, and variables
882
-	 *
883
-	 * @param array $report The requirements report array
884
-	 *
885
-	 * @return void
886
-	 */
887
-	protected function checkPHP(&$report) {
888
-		$phpReport = [];
889
-
890
-		$min_php_version = '5.6.0';
891
-		if (version_compare(PHP_VERSION, $min_php_version, '<')) {
892
-			$phpReport[] = [
893
-				'severity' => 'failure',
894
-				'message' => elgg_echo('install:check:php:version', [$min_php_version, PHP_VERSION])
895
-			];
896
-		}
897
-
898
-		$this->checkPhpExtensions($phpReport);
899
-
900
-		$this->checkPhpDirectives($phpReport);
901
-
902
-		if (count($phpReport) == 0) {
903
-			$phpReport[] = [
904
-				'severity' => 'pass',
905
-				'message' => elgg_echo('install:check:php:success')
906
-			];
907
-		}
908
-
909
-		$report['php'] = $phpReport;
910
-	}
911
-
912
-	/**
913
-	 * Check the server's PHP extensions
914
-	 *
915
-	 * @param array $phpReport The PHP requirements report array
916
-	 *
917
-	 * @return void
918
-	 */
919
-	protected function checkPhpExtensions(&$phpReport) {
920
-		$extensions = get_loaded_extensions();
921
-		$requiredExtensions = [
922
-			'pdo_mysql',
923
-			'json',
924
-			'xml',
925
-			'gd',
926
-		];
927
-		foreach ($requiredExtensions as $extension) {
928
-			if (!in_array($extension, $extensions)) {
929
-				$phpReport[] = [
930
-					'severity' => 'failure',
931
-					'message' => elgg_echo('install:check:php:extension', [$extension])
932
-				];
933
-			}
934
-		}
935
-
936
-		$recommendedExtensions = [
937
-			'mbstring',
938
-		];
939
-		foreach ($recommendedExtensions as $extension) {
940
-			if (!in_array($extension, $extensions)) {
941
-				$phpReport[] = [
942
-					'severity' => 'warning',
943
-					'message' => elgg_echo('install:check:php:extension:recommend', [$extension])
944
-				];
945
-			}
946
-		}
947
-	}
948
-
949
-	/**
950
-	 * Check PHP parameters
951
-	 *
952
-	 * @param array $phpReport The PHP requirements report array
953
-	 *
954
-	 * @return void
955
-	 */
956
-	protected function checkPhpDirectives(&$phpReport) {
957
-		if (ini_get('open_basedir')) {
958
-			$phpReport[] = [
959
-				'severity' => 'warning',
960
-				'message' => elgg_echo("install:check:php:open_basedir")
961
-			];
962
-		}
963
-
964
-		if (ini_get('safe_mode')) {
965
-			$phpReport[] = [
966
-				'severity' => 'warning',
967
-				'message' => elgg_echo("install:check:php:safe_mode")
968
-			];
969
-		}
970
-
971
-		if (ini_get('arg_separator.output') !== '&') {
972
-			$separator = htmlspecialchars(ini_get('arg_separator.output'));
973
-			$msg = elgg_echo("install:check:php:arg_separator", [$separator]);
974
-			$phpReport[] = [
975
-				'severity' => 'failure',
976
-				'message' => $msg,
977
-			];
978
-		}
979
-
980
-		if (ini_get('register_globals')) {
981
-			$phpReport[] = [
982
-				'severity' => 'failure',
983
-				'message' => elgg_echo("install:check:php:register_globals")
984
-			];
985
-		}
986
-
987
-		if (ini_get('session.auto_start')) {
988
-			$phpReport[] = [
989
-				'severity' => 'failure',
990
-				'message' => elgg_echo("install:check:php:session.auto_start")
991
-			];
992
-		}
993
-	}
994
-
995
-	/**
996
-	 * Confirm that the rewrite rules are firing
997
-	 *
998
-	 * @param array $report The requirements report array
999
-	 *
1000
-	 * @return void
1001
-	 */
1002
-	protected function checkRewriteRules(&$report) {
1003
-		$tester = new ElggRewriteTester();
1004
-		$url = $this->config->wwwroot;
1005
-		$url .= Request::REWRITE_TEST_TOKEN . '?' . http_build_query([
1006
-				Request::REWRITE_TEST_TOKEN => '1',
1007
-		]);
1008
-		$report['rewrite'] = [$tester->run($url, Paths::project())];
1009
-	}
1010
-
1011
-	/**
1012
-	 * Count the number of failures in the requirements report
1013
-	 *
1014
-	 * @param array  $report    The requirements report array
1015
-	 * @param string $condition 'failure' or 'warning'
1016
-	 *
1017
-	 * @return int
1018
-	 */
1019
-	protected function countNumConditions($report, $condition) {
1020
-		$count = 0;
1021
-		foreach ($report as $category => $checks) {
1022
-			foreach ($checks as $check) {
1023
-				if ($check['severity'] === $condition) {
1024
-					$count++;
1025
-				}
1026
-			}
1027
-		}
1028
-
1029
-		return $count;
1030
-	}
1031
-
1032
-
1033
-	/**
1034
-	 * Database support methods
1035
-	 */
1036
-
1037
-	/**
1038
-	 * Validate the variables for the database step
1039
-	 *
1040
-	 * @param array $submissionVars Submitted vars
1041
-	 * @param array $formVars       Vars in the form
1042
-	 *
1043
-	 * @return bool
1044
-	 */
1045
-	protected function validateDatabaseVars($submissionVars, $formVars) {
1046
-
1047
-		foreach ($formVars as $field => $info) {
1048
-			if ($info['required'] == true && !$submissionVars[$field]) {
1049
-				$name = elgg_echo("install:database:label:$field");
1050
-				register_error(elgg_echo('install:error:requiredfield', [$name]));
1051
-				return false;
1052
-			}
1053
-		}
1054
-
1055
-		// check that data root is absolute path
1056
-		if (stripos(PHP_OS, 'win') === 0) {
1057
-			if (strpos($submissionVars['dataroot'], ':') !== 1) {
1058
-				$msg = elgg_echo('install:error:relative_path', [$submissionVars['dataroot']]);
1059
-				register_error($msg);
1060
-				return false;
1061
-			}
1062
-		} else {
1063
-			if (strpos($submissionVars['dataroot'], '/') !== 0) {
1064
-				$msg = elgg_echo('install:error:relative_path', [$submissionVars['dataroot']]);
1065
-				register_error($msg);
1066
-				return false;
1067
-			}
1068
-		}
1069
-
1070
-		// check that data root exists
1071
-		if (!is_dir($submissionVars['dataroot'])) {
1072
-			$msg = elgg_echo('install:error:datadirectoryexists', [$submissionVars['dataroot']]);
1073
-			register_error($msg);
1074
-			return false;
1075
-		}
1076
-
1077
-		// check that data root is writable
1078
-		if (!is_writable($submissionVars['dataroot'])) {
1079
-			$msg = elgg_echo('install:error:writedatadirectory', [$submissionVars['dataroot']]);
1080
-			register_error($msg);
1081
-			return false;
1082
-		}
1083
-
1084
-		if (!$this->config->data_dir_override) {
1085
-			// check that data root is not subdirectory of Elgg root
1086
-			if (stripos($submissionVars['dataroot'], $this->config->path) === 0) {
1087
-				$msg = elgg_echo('install:error:locationdatadirectory', [$submissionVars['dataroot']]);
1088
-				register_error($msg);
1089
-				return false;
1090
-			}
1091
-		}
1092
-
1093
-		// according to postgres documentation: SQL identifiers and key words must
1094
-		// begin with a letter (a-z, but also letters with diacritical marks and
1095
-		// non-Latin letters) or an underscore (_). Subsequent characters in an
1096
-		// identifier or key word can be letters, underscores, digits (0-9), or dollar signs ($).
1097
-		// Refs #4994
1098
-		if (!preg_match("/^[a-zA-Z_][\w]*$/", $submissionVars['dbprefix'])) {
1099
-			register_error(elgg_echo('install:error:database_prefix'));
1100
-			return false;
1101
-		}
1102
-
1103
-		return $this->checkDatabaseSettings(
1104
-			$submissionVars['dbuser'],
1105
-			$submissionVars['dbpassword'],
1106
-			$submissionVars['dbname'],
1107
-			$submissionVars['dbhost']
1108
-		);
1109
-	}
1110
-
1111
-	/**
1112
-	 * Confirm the settings for the database
1113
-	 *
1114
-	 * @param string $user     Username
1115
-	 * @param string $password Password
1116
-	 * @param string $dbname   Database name
1117
-	 * @param string $host     Host
1118
-	 *
1119
-	 * @return bool
1120
-	 */
1121
-	protected function checkDatabaseSettings($user, $password, $dbname, $host) {
1122
-		$config = new DbConfig((object) [
1123
-			'dbhost' => $host,
1124
-			'dbuser' => $user,
1125
-			'dbpass' => $password,
1126
-			'dbname' => $dbname,
1127
-			'dbencoding' => 'utf8mb4',
1128
-		]);
1129
-		$db = new \Elgg\Database($config);
1130
-
1131
-		try {
1132
-			$db->getDataRow("SELECT 1");
1133
-		} catch (DatabaseException $e) {
1134
-			if (0 === strpos($e->getMessage(), "Elgg couldn't connect")) {
1135
-				register_error(elgg_echo('install:error:databasesettings'));
1136
-			} else {
1137
-				register_error(elgg_echo('install:error:nodatabase', [$dbname]));
1138
-			}
1139
-			return false;
1140
-		}
1141
-
1142
-		// check MySQL version
1143
-		$version = $db->getServerVersion(DbConfig::READ_WRITE);
1144
-		if (version_compare($version, '5.5.3', '<')) {
1145
-			register_error(elgg_echo('install:error:oldmysql2', [$version]));
1146
-			return false;
1147
-		}
1148
-
1149
-		return true;
1150
-	}
1151
-
1152
-	/**
1153
-	 * Writes the settings file to the engine directory
1154
-	 *
1155
-	 * @param array $params Array of inputted params from the user
1156
-	 *
1157
-	 * @return bool
1158
-	 */
1159
-	protected function createSettingsFile($params) {
1160
-		$template = Application::elggDir()->getContents("elgg-config/settings.example.php");
1161
-		if (!$template) {
1162
-			register_error(elgg_echo('install:error:readsettingsphp'));
1163
-			return false;
1164
-		}
1165
-
1166
-		foreach ($params as $k => $v) {
1167
-			$template = str_replace("{{" . $k . "}}", $v, $template);
1168
-		}
1169
-
1170
-		$result = file_put_contents(Paths::settingsFile(), $template);
1171
-		if (!$result) {
1172
-			register_error(elgg_echo('install:error:writesettingphp'));
1173
-			return false;
1174
-		}
1175
-
1176
-		return true;
1177
-	}
1178
-
1179
-	/**
1180
-	 * Bootstrap database connection before entire engine is available
1181
-	 *
1182
-	 * @return bool
1183
-	 */
1184
-	protected function connectToDatabase() {
1185
-		try {
1186
-			$this->services->db->setupConnections();
1187
-		} catch (DatabaseException $e) {
1188
-			register_error($e->getMessage());
1189
-			return false;
1190
-		}
1191
-
1192
-		return true;
1193
-	}
1194
-
1195
-	/**
1196
-	 * Create the database tables
1197
-	 *
1198
-	 * @return bool
1199
-	 */
1200
-	protected function installDatabase() {
1201
-		$ret = \Elgg\Application::migrate();
1202
-		if ($ret) {
1203
-			init_site_secret();
1204
-		}
1205
-		return $ret;
1206
-	}
1207
-
1208
-	/**
1209
-	 * Site settings support methods
1210
-	 */
1211
-
1212
-	/**
1213
-	 * Create the data directory if requested
1214
-	 *
1215
-	 * @param array $submissionVars Submitted vars
1216
-	 * @param array $formVars       Variables in the form
1217
-	 *
1218
-	 * @return bool
1219
-	 */
1220
-	protected function createDataDirectory(&$submissionVars, $formVars) {
1221
-		// did the user have option of Elgg creating the data directory
1222
-		if ($formVars['dataroot']['type'] != 'combo') {
1223
-			return true;
1224
-		}
1225
-
1226
-		// did the user select the option
1227
-		if ($submissionVars['dataroot'] != 'dataroot-checkbox') {
1228
-			return true;
1229
-		}
1230
-
1231
-		$dir = sanitise_filepath($submissionVars['path']) . 'data';
1232
-		if (file_exists($dir) || mkdir($dir, 0700)) {
1233
-			$submissionVars['dataroot'] = $dir;
1234
-			if (!file_exists("$dir/.htaccess")) {
1235
-				$htaccess = "Order Deny,Allow\nDeny from All\n";
1236
-				if (!file_put_contents("$dir/.htaccess", $htaccess)) {
1237
-					return false;
1238
-				}
1239
-			}
1240
-			return true;
1241
-		}
1242
-
1243
-		return false;
1244
-	}
1245
-
1246
-	/**
1247
-	 * Validate the site settings form variables
1248
-	 *
1249
-	 * @param array $submissionVars Submitted vars
1250
-	 * @param array $formVars       Vars in the form
1251
-	 *
1252
-	 * @return bool
1253
-	 */
1254
-	protected function validateSettingsVars($submissionVars, $formVars) {
1255
-		foreach ($formVars as $field => $info) {
1256
-			$submissionVars[$field] = trim($submissionVars[$field]);
1257
-			if ($info['required'] == true && $submissionVars[$field] === '') {
1258
-				$name = elgg_echo("install:settings:label:$field");
1259
-				register_error(elgg_echo('install:error:requiredfield', [$name]));
1260
-				return false;
1261
-			}
1262
-		}
1263
-
1264
-		// check that email address is email address
1265
-		if ($submissionVars['siteemail'] && !is_email_address($submissionVars['siteemail'])) {
1266
-			$msg = elgg_echo('install:error:emailaddress', [$submissionVars['siteemail']]);
1267
-			register_error($msg);
1268
-			return false;
1269
-		}
1270
-
1271
-		// @todo check that url is a url
1272
-		// @note filter_var cannot be used because it doesn't work on international urls
1273
-
1274
-		return true;
1275
-	}
1276
-
1277
-	/**
1278
-	 * Initialize the site including site entity, plugins, and configuration
1279
-	 *
1280
-	 * @param array $submissionVars Submitted vars
1281
-	 *
1282
-	 * @return bool
1283
-	 */
1284
-	protected function saveSiteSettings($submissionVars) {
1285
-		$site = new ElggSite();
1286
-		$site->name = strip_tags($submissionVars['sitename']);
1287
-		$site->access_id = ACCESS_PUBLIC;
1288
-		$site->email = $submissionVars['siteemail'];
1289
-
1290
-		$guid = $site->save();
1291
-		if ($guid !== 1) {
1292
-			register_error(elgg_echo('install:error:createsite'));
1293
-			return false;
1294
-		}
1295
-
1296
-		$this->config->site = $site;
1297
-
1298
-		// new installations have run all the upgrades
1299
-		$upgrades = elgg_get_upgrade_files(Paths::elgg() . "engine/lib/upgrades/");
1300
-
1301
-		$sets = [
1302
-			'installed' => time(),
1303
-			'version' => elgg_get_version(),
1304
-			'simplecache_enabled' => 1,
1305
-			'system_cache_enabled' => 1,
1306
-			'simplecache_lastupdate' => time(),
1307
-			'processed_upgrades' => $upgrades,
1308
-			'language' => 'en',
1309
-			'default_access' => $submissionVars['siteaccess'],
1310
-			'allow_registration' => false,
1311
-			'walled_garden' => false,
1312
-			'allow_user_default_access' => '',
1313
-			'default_limit' => 10,
1314
-			'security_protect_upgrade' => true,
1315
-			'security_notify_admins' => true,
1316
-			'security_notify_user_password' => true,
1317
-			'security_email_require_password' => true,
1318
-		];
1319
-		foreach ($sets as $key => $value) {
1320
-			elgg_save_config($key, $value);
1321
-		}
1322
-
1323
-		// Enable a set of default plugins
1324
-		_elgg_generate_plugin_entities();
1325
-
1326
-		foreach (elgg_get_plugins('any') as $plugin) {
1327
-			if ($plugin->getManifest()) {
1328
-				if ($plugin->getManifest()->getActivateOnInstall()) {
1329
-					$plugin->activate();
1330
-				}
1331
-				if (in_array('theme', $plugin->getManifest()->getCategories())) {
1332
-					$plugin->setPriority('last');
1333
-				}
1334
-			}
1335
-		}
1336
-
1337
-		return true;
1338
-	}
1339
-
1340
-	/**
1341
-	 * Admin account support methods
1342
-	 */
1343
-
1344
-	/**
1345
-	 * Validate account form variables
1346
-	 *
1347
-	 * @param array $submissionVars Submitted vars
1348
-	 * @param array $formVars       Form vars
1349
-	 *
1350
-	 * @return bool
1351
-	 */
1352
-	protected function validateAdminVars($submissionVars, $formVars) {
1353
-
1354
-		foreach ($formVars as $field => $info) {
1355
-			if ($info['required'] == true && !$submissionVars[$field]) {
1356
-				$name = elgg_echo("install:admin:label:$field");
1357
-				register_error(elgg_echo('install:error:requiredfield', [$name]));
1358
-				return false;
1359
-			}
1360
-		}
1361
-
1362
-		if ($submissionVars['password1'] !== $submissionVars['password2']) {
1363
-			register_error(elgg_echo('install:admin:password:mismatch'));
1364
-			return false;
1365
-		}
1366
-
1367
-		if (trim($submissionVars['password1']) == "") {
1368
-			register_error(elgg_echo('install:admin:password:empty'));
1369
-			return false;
1370
-		}
1371
-
1372
-		$minLength = $this->services->configTable->get('min_password_length');
1373
-		if (strlen($submissionVars['password1']) < $minLength) {
1374
-			register_error(elgg_echo('install:admin:password:tooshort'));
1375
-			return false;
1376
-		}
1377
-
1378
-		// check that email address is email address
1379
-		if ($submissionVars['email'] && !is_email_address($submissionVars['email'])) {
1380
-			$msg = elgg_echo('install:error:emailaddress', [$submissionVars['email']]);
1381
-			register_error($msg);
1382
-			return false;
1383
-		}
1384
-
1385
-		return true;
1386
-	}
1387
-
1388
-	/**
1389
-	 * Create a user account for the admin
1390
-	 *
1391
-	 * @param array $submissionVars Submitted vars
1392
-	 * @param bool  $login          Login in the admin user?
1393
-	 *
1394
-	 * @return bool
1395
-	 */
1396
-	protected function createAdminAccount($submissionVars, $login = false) {
1397
-		try {
1398
-			$guid = register_user(
1399
-				$submissionVars['username'],
1400
-				$submissionVars['password1'],
1401
-				$submissionVars['displayname'],
1402
-				$submissionVars['email']
1403
-			);
1404
-		} catch (Exception $e) {
1405
-			register_error($e->getMessage());
1406
-			return false;
1407
-		}
1408
-
1409
-		if (!$guid) {
1410
-			register_error(elgg_echo('install:admin:cannot_create'));
1411
-			return false;
1412
-		}
1413
-
1414
-		$user = get_entity($guid);
1415
-		if (!$user instanceof ElggUser) {
1416
-			register_error(elgg_echo('install:error:loadadmin'));
1417
-			return false;
1418
-		}
1419
-
1420
-		elgg_set_ignore_access(true);
1421
-		if ($user->makeAdmin() == false) {
1422
-			register_error(elgg_echo('install:error:adminaccess'));
1423
-		} else {
1424
-			$this->services->configTable->set('admin_registered', 1);
1425
-		}
1426
-		elgg_set_ignore_access(false);
1427
-
1428
-		// add validation data to satisfy user validation plugins
1429
-		$user->validated = 1;
1430
-		$user->validated_method = 'admin_user';
1431
-
1432
-		if (!$login) {
1433
-			return true;
1434
-		}
1435
-
1436
-		$session = ElggSession::fromDatabase($this->config, $this->services->db);
1437
-		$session->start();
1438
-		$this->services->setValue('session', $session);
1439
-		if (login($user) == false) {
1440
-			register_error(elgg_echo('install:error:adminlogin'));
1441
-		}
1442
-
1443
-		return true;
1444
-	}
880
+    /**
881
+     * Check version of PHP, extensions, and variables
882
+     *
883
+     * @param array $report The requirements report array
884
+     *
885
+     * @return void
886
+     */
887
+    protected function checkPHP(&$report) {
888
+        $phpReport = [];
889
+
890
+        $min_php_version = '5.6.0';
891
+        if (version_compare(PHP_VERSION, $min_php_version, '<')) {
892
+            $phpReport[] = [
893
+                'severity' => 'failure',
894
+                'message' => elgg_echo('install:check:php:version', [$min_php_version, PHP_VERSION])
895
+            ];
896
+        }
897
+
898
+        $this->checkPhpExtensions($phpReport);
899
+
900
+        $this->checkPhpDirectives($phpReport);
901
+
902
+        if (count($phpReport) == 0) {
903
+            $phpReport[] = [
904
+                'severity' => 'pass',
905
+                'message' => elgg_echo('install:check:php:success')
906
+            ];
907
+        }
908
+
909
+        $report['php'] = $phpReport;
910
+    }
911
+
912
+    /**
913
+     * Check the server's PHP extensions
914
+     *
915
+     * @param array $phpReport The PHP requirements report array
916
+     *
917
+     * @return void
918
+     */
919
+    protected function checkPhpExtensions(&$phpReport) {
920
+        $extensions = get_loaded_extensions();
921
+        $requiredExtensions = [
922
+            'pdo_mysql',
923
+            'json',
924
+            'xml',
925
+            'gd',
926
+        ];
927
+        foreach ($requiredExtensions as $extension) {
928
+            if (!in_array($extension, $extensions)) {
929
+                $phpReport[] = [
930
+                    'severity' => 'failure',
931
+                    'message' => elgg_echo('install:check:php:extension', [$extension])
932
+                ];
933
+            }
934
+        }
935
+
936
+        $recommendedExtensions = [
937
+            'mbstring',
938
+        ];
939
+        foreach ($recommendedExtensions as $extension) {
940
+            if (!in_array($extension, $extensions)) {
941
+                $phpReport[] = [
942
+                    'severity' => 'warning',
943
+                    'message' => elgg_echo('install:check:php:extension:recommend', [$extension])
944
+                ];
945
+            }
946
+        }
947
+    }
948
+
949
+    /**
950
+     * Check PHP parameters
951
+     *
952
+     * @param array $phpReport The PHP requirements report array
953
+     *
954
+     * @return void
955
+     */
956
+    protected function checkPhpDirectives(&$phpReport) {
957
+        if (ini_get('open_basedir')) {
958
+            $phpReport[] = [
959
+                'severity' => 'warning',
960
+                'message' => elgg_echo("install:check:php:open_basedir")
961
+            ];
962
+        }
963
+
964
+        if (ini_get('safe_mode')) {
965
+            $phpReport[] = [
966
+                'severity' => 'warning',
967
+                'message' => elgg_echo("install:check:php:safe_mode")
968
+            ];
969
+        }
970
+
971
+        if (ini_get('arg_separator.output') !== '&') {
972
+            $separator = htmlspecialchars(ini_get('arg_separator.output'));
973
+            $msg = elgg_echo("install:check:php:arg_separator", [$separator]);
974
+            $phpReport[] = [
975
+                'severity' => 'failure',
976
+                'message' => $msg,
977
+            ];
978
+        }
979
+
980
+        if (ini_get('register_globals')) {
981
+            $phpReport[] = [
982
+                'severity' => 'failure',
983
+                'message' => elgg_echo("install:check:php:register_globals")
984
+            ];
985
+        }
986
+
987
+        if (ini_get('session.auto_start')) {
988
+            $phpReport[] = [
989
+                'severity' => 'failure',
990
+                'message' => elgg_echo("install:check:php:session.auto_start")
991
+            ];
992
+        }
993
+    }
994
+
995
+    /**
996
+     * Confirm that the rewrite rules are firing
997
+     *
998
+     * @param array $report The requirements report array
999
+     *
1000
+     * @return void
1001
+     */
1002
+    protected function checkRewriteRules(&$report) {
1003
+        $tester = new ElggRewriteTester();
1004
+        $url = $this->config->wwwroot;
1005
+        $url .= Request::REWRITE_TEST_TOKEN . '?' . http_build_query([
1006
+                Request::REWRITE_TEST_TOKEN => '1',
1007
+        ]);
1008
+        $report['rewrite'] = [$tester->run($url, Paths::project())];
1009
+    }
1010
+
1011
+    /**
1012
+     * Count the number of failures in the requirements report
1013
+     *
1014
+     * @param array  $report    The requirements report array
1015
+     * @param string $condition 'failure' or 'warning'
1016
+     *
1017
+     * @return int
1018
+     */
1019
+    protected function countNumConditions($report, $condition) {
1020
+        $count = 0;
1021
+        foreach ($report as $category => $checks) {
1022
+            foreach ($checks as $check) {
1023
+                if ($check['severity'] === $condition) {
1024
+                    $count++;
1025
+                }
1026
+            }
1027
+        }
1028
+
1029
+        return $count;
1030
+    }
1031
+
1032
+
1033
+    /**
1034
+     * Database support methods
1035
+     */
1036
+
1037
+    /**
1038
+     * Validate the variables for the database step
1039
+     *
1040
+     * @param array $submissionVars Submitted vars
1041
+     * @param array $formVars       Vars in the form
1042
+     *
1043
+     * @return bool
1044
+     */
1045
+    protected function validateDatabaseVars($submissionVars, $formVars) {
1046
+
1047
+        foreach ($formVars as $field => $info) {
1048
+            if ($info['required'] == true && !$submissionVars[$field]) {
1049
+                $name = elgg_echo("install:database:label:$field");
1050
+                register_error(elgg_echo('install:error:requiredfield', [$name]));
1051
+                return false;
1052
+            }
1053
+        }
1054
+
1055
+        // check that data root is absolute path
1056
+        if (stripos(PHP_OS, 'win') === 0) {
1057
+            if (strpos($submissionVars['dataroot'], ':') !== 1) {
1058
+                $msg = elgg_echo('install:error:relative_path', [$submissionVars['dataroot']]);
1059
+                register_error($msg);
1060
+                return false;
1061
+            }
1062
+        } else {
1063
+            if (strpos($submissionVars['dataroot'], '/') !== 0) {
1064
+                $msg = elgg_echo('install:error:relative_path', [$submissionVars['dataroot']]);
1065
+                register_error($msg);
1066
+                return false;
1067
+            }
1068
+        }
1069
+
1070
+        // check that data root exists
1071
+        if (!is_dir($submissionVars['dataroot'])) {
1072
+            $msg = elgg_echo('install:error:datadirectoryexists', [$submissionVars['dataroot']]);
1073
+            register_error($msg);
1074
+            return false;
1075
+        }
1076
+
1077
+        // check that data root is writable
1078
+        if (!is_writable($submissionVars['dataroot'])) {
1079
+            $msg = elgg_echo('install:error:writedatadirectory', [$submissionVars['dataroot']]);
1080
+            register_error($msg);
1081
+            return false;
1082
+        }
1083
+
1084
+        if (!$this->config->data_dir_override) {
1085
+            // check that data root is not subdirectory of Elgg root
1086
+            if (stripos($submissionVars['dataroot'], $this->config->path) === 0) {
1087
+                $msg = elgg_echo('install:error:locationdatadirectory', [$submissionVars['dataroot']]);
1088
+                register_error($msg);
1089
+                return false;
1090
+            }
1091
+        }
1092
+
1093
+        // according to postgres documentation: SQL identifiers and key words must
1094
+        // begin with a letter (a-z, but also letters with diacritical marks and
1095
+        // non-Latin letters) or an underscore (_). Subsequent characters in an
1096
+        // identifier or key word can be letters, underscores, digits (0-9), or dollar signs ($).
1097
+        // Refs #4994
1098
+        if (!preg_match("/^[a-zA-Z_][\w]*$/", $submissionVars['dbprefix'])) {
1099
+            register_error(elgg_echo('install:error:database_prefix'));
1100
+            return false;
1101
+        }
1102
+
1103
+        return $this->checkDatabaseSettings(
1104
+            $submissionVars['dbuser'],
1105
+            $submissionVars['dbpassword'],
1106
+            $submissionVars['dbname'],
1107
+            $submissionVars['dbhost']
1108
+        );
1109
+    }
1110
+
1111
+    /**
1112
+     * Confirm the settings for the database
1113
+     *
1114
+     * @param string $user     Username
1115
+     * @param string $password Password
1116
+     * @param string $dbname   Database name
1117
+     * @param string $host     Host
1118
+     *
1119
+     * @return bool
1120
+     */
1121
+    protected function checkDatabaseSettings($user, $password, $dbname, $host) {
1122
+        $config = new DbConfig((object) [
1123
+            'dbhost' => $host,
1124
+            'dbuser' => $user,
1125
+            'dbpass' => $password,
1126
+            'dbname' => $dbname,
1127
+            'dbencoding' => 'utf8mb4',
1128
+        ]);
1129
+        $db = new \Elgg\Database($config);
1130
+
1131
+        try {
1132
+            $db->getDataRow("SELECT 1");
1133
+        } catch (DatabaseException $e) {
1134
+            if (0 === strpos($e->getMessage(), "Elgg couldn't connect")) {
1135
+                register_error(elgg_echo('install:error:databasesettings'));
1136
+            } else {
1137
+                register_error(elgg_echo('install:error:nodatabase', [$dbname]));
1138
+            }
1139
+            return false;
1140
+        }
1141
+
1142
+        // check MySQL version
1143
+        $version = $db->getServerVersion(DbConfig::READ_WRITE);
1144
+        if (version_compare($version, '5.5.3', '<')) {
1145
+            register_error(elgg_echo('install:error:oldmysql2', [$version]));
1146
+            return false;
1147
+        }
1148
+
1149
+        return true;
1150
+    }
1151
+
1152
+    /**
1153
+     * Writes the settings file to the engine directory
1154
+     *
1155
+     * @param array $params Array of inputted params from the user
1156
+     *
1157
+     * @return bool
1158
+     */
1159
+    protected function createSettingsFile($params) {
1160
+        $template = Application::elggDir()->getContents("elgg-config/settings.example.php");
1161
+        if (!$template) {
1162
+            register_error(elgg_echo('install:error:readsettingsphp'));
1163
+            return false;
1164
+        }
1165
+
1166
+        foreach ($params as $k => $v) {
1167
+            $template = str_replace("{{" . $k . "}}", $v, $template);
1168
+        }
1169
+
1170
+        $result = file_put_contents(Paths::settingsFile(), $template);
1171
+        if (!$result) {
1172
+            register_error(elgg_echo('install:error:writesettingphp'));
1173
+            return false;
1174
+        }
1175
+
1176
+        return true;
1177
+    }
1178
+
1179
+    /**
1180
+     * Bootstrap database connection before entire engine is available
1181
+     *
1182
+     * @return bool
1183
+     */
1184
+    protected function connectToDatabase() {
1185
+        try {
1186
+            $this->services->db->setupConnections();
1187
+        } catch (DatabaseException $e) {
1188
+            register_error($e->getMessage());
1189
+            return false;
1190
+        }
1191
+
1192
+        return true;
1193
+    }
1194
+
1195
+    /**
1196
+     * Create the database tables
1197
+     *
1198
+     * @return bool
1199
+     */
1200
+    protected function installDatabase() {
1201
+        $ret = \Elgg\Application::migrate();
1202
+        if ($ret) {
1203
+            init_site_secret();
1204
+        }
1205
+        return $ret;
1206
+    }
1207
+
1208
+    /**
1209
+     * Site settings support methods
1210
+     */
1211
+
1212
+    /**
1213
+     * Create the data directory if requested
1214
+     *
1215
+     * @param array $submissionVars Submitted vars
1216
+     * @param array $formVars       Variables in the form
1217
+     *
1218
+     * @return bool
1219
+     */
1220
+    protected function createDataDirectory(&$submissionVars, $formVars) {
1221
+        // did the user have option of Elgg creating the data directory
1222
+        if ($formVars['dataroot']['type'] != 'combo') {
1223
+            return true;
1224
+        }
1225
+
1226
+        // did the user select the option
1227
+        if ($submissionVars['dataroot'] != 'dataroot-checkbox') {
1228
+            return true;
1229
+        }
1230
+
1231
+        $dir = sanitise_filepath($submissionVars['path']) . 'data';
1232
+        if (file_exists($dir) || mkdir($dir, 0700)) {
1233
+            $submissionVars['dataroot'] = $dir;
1234
+            if (!file_exists("$dir/.htaccess")) {
1235
+                $htaccess = "Order Deny,Allow\nDeny from All\n";
1236
+                if (!file_put_contents("$dir/.htaccess", $htaccess)) {
1237
+                    return false;
1238
+                }
1239
+            }
1240
+            return true;
1241
+        }
1242
+
1243
+        return false;
1244
+    }
1245
+
1246
+    /**
1247
+     * Validate the site settings form variables
1248
+     *
1249
+     * @param array $submissionVars Submitted vars
1250
+     * @param array $formVars       Vars in the form
1251
+     *
1252
+     * @return bool
1253
+     */
1254
+    protected function validateSettingsVars($submissionVars, $formVars) {
1255
+        foreach ($formVars as $field => $info) {
1256
+            $submissionVars[$field] = trim($submissionVars[$field]);
1257
+            if ($info['required'] == true && $submissionVars[$field] === '') {
1258
+                $name = elgg_echo("install:settings:label:$field");
1259
+                register_error(elgg_echo('install:error:requiredfield', [$name]));
1260
+                return false;
1261
+            }
1262
+        }
1263
+
1264
+        // check that email address is email address
1265
+        if ($submissionVars['siteemail'] && !is_email_address($submissionVars['siteemail'])) {
1266
+            $msg = elgg_echo('install:error:emailaddress', [$submissionVars['siteemail']]);
1267
+            register_error($msg);
1268
+            return false;
1269
+        }
1270
+
1271
+        // @todo check that url is a url
1272
+        // @note filter_var cannot be used because it doesn't work on international urls
1273
+
1274
+        return true;
1275
+    }
1276
+
1277
+    /**
1278
+     * Initialize the site including site entity, plugins, and configuration
1279
+     *
1280
+     * @param array $submissionVars Submitted vars
1281
+     *
1282
+     * @return bool
1283
+     */
1284
+    protected function saveSiteSettings($submissionVars) {
1285
+        $site = new ElggSite();
1286
+        $site->name = strip_tags($submissionVars['sitename']);
1287
+        $site->access_id = ACCESS_PUBLIC;
1288
+        $site->email = $submissionVars['siteemail'];
1289
+
1290
+        $guid = $site->save();
1291
+        if ($guid !== 1) {
1292
+            register_error(elgg_echo('install:error:createsite'));
1293
+            return false;
1294
+        }
1295
+
1296
+        $this->config->site = $site;
1297
+
1298
+        // new installations have run all the upgrades
1299
+        $upgrades = elgg_get_upgrade_files(Paths::elgg() . "engine/lib/upgrades/");
1300
+
1301
+        $sets = [
1302
+            'installed' => time(),
1303
+            'version' => elgg_get_version(),
1304
+            'simplecache_enabled' => 1,
1305
+            'system_cache_enabled' => 1,
1306
+            'simplecache_lastupdate' => time(),
1307
+            'processed_upgrades' => $upgrades,
1308
+            'language' => 'en',
1309
+            'default_access' => $submissionVars['siteaccess'],
1310
+            'allow_registration' => false,
1311
+            'walled_garden' => false,
1312
+            'allow_user_default_access' => '',
1313
+            'default_limit' => 10,
1314
+            'security_protect_upgrade' => true,
1315
+            'security_notify_admins' => true,
1316
+            'security_notify_user_password' => true,
1317
+            'security_email_require_password' => true,
1318
+        ];
1319
+        foreach ($sets as $key => $value) {
1320
+            elgg_save_config($key, $value);
1321
+        }
1322
+
1323
+        // Enable a set of default plugins
1324
+        _elgg_generate_plugin_entities();
1325
+
1326
+        foreach (elgg_get_plugins('any') as $plugin) {
1327
+            if ($plugin->getManifest()) {
1328
+                if ($plugin->getManifest()->getActivateOnInstall()) {
1329
+                    $plugin->activate();
1330
+                }
1331
+                if (in_array('theme', $plugin->getManifest()->getCategories())) {
1332
+                    $plugin->setPriority('last');
1333
+                }
1334
+            }
1335
+        }
1336
+
1337
+        return true;
1338
+    }
1339
+
1340
+    /**
1341
+     * Admin account support methods
1342
+     */
1343
+
1344
+    /**
1345
+     * Validate account form variables
1346
+     *
1347
+     * @param array $submissionVars Submitted vars
1348
+     * @param array $formVars       Form vars
1349
+     *
1350
+     * @return bool
1351
+     */
1352
+    protected function validateAdminVars($submissionVars, $formVars) {
1353
+
1354
+        foreach ($formVars as $field => $info) {
1355
+            if ($info['required'] == true && !$submissionVars[$field]) {
1356
+                $name = elgg_echo("install:admin:label:$field");
1357
+                register_error(elgg_echo('install:error:requiredfield', [$name]));
1358
+                return false;
1359
+            }
1360
+        }
1361
+
1362
+        if ($submissionVars['password1'] !== $submissionVars['password2']) {
1363
+            register_error(elgg_echo('install:admin:password:mismatch'));
1364
+            return false;
1365
+        }
1366
+
1367
+        if (trim($submissionVars['password1']) == "") {
1368
+            register_error(elgg_echo('install:admin:password:empty'));
1369
+            return false;
1370
+        }
1371
+
1372
+        $minLength = $this->services->configTable->get('min_password_length');
1373
+        if (strlen($submissionVars['password1']) < $minLength) {
1374
+            register_error(elgg_echo('install:admin:password:tooshort'));
1375
+            return false;
1376
+        }
1377
+
1378
+        // check that email address is email address
1379
+        if ($submissionVars['email'] && !is_email_address($submissionVars['email'])) {
1380
+            $msg = elgg_echo('install:error:emailaddress', [$submissionVars['email']]);
1381
+            register_error($msg);
1382
+            return false;
1383
+        }
1384
+
1385
+        return true;
1386
+    }
1387
+
1388
+    /**
1389
+     * Create a user account for the admin
1390
+     *
1391
+     * @param array $submissionVars Submitted vars
1392
+     * @param bool  $login          Login in the admin user?
1393
+     *
1394
+     * @return bool
1395
+     */
1396
+    protected function createAdminAccount($submissionVars, $login = false) {
1397
+        try {
1398
+            $guid = register_user(
1399
+                $submissionVars['username'],
1400
+                $submissionVars['password1'],
1401
+                $submissionVars['displayname'],
1402
+                $submissionVars['email']
1403
+            );
1404
+        } catch (Exception $e) {
1405
+            register_error($e->getMessage());
1406
+            return false;
1407
+        }
1408
+
1409
+        if (!$guid) {
1410
+            register_error(elgg_echo('install:admin:cannot_create'));
1411
+            return false;
1412
+        }
1413
+
1414
+        $user = get_entity($guid);
1415
+        if (!$user instanceof ElggUser) {
1416
+            register_error(elgg_echo('install:error:loadadmin'));
1417
+            return false;
1418
+        }
1419
+
1420
+        elgg_set_ignore_access(true);
1421
+        if ($user->makeAdmin() == false) {
1422
+            register_error(elgg_echo('install:error:adminaccess'));
1423
+        } else {
1424
+            $this->services->configTable->set('admin_registered', 1);
1425
+        }
1426
+        elgg_set_ignore_access(false);
1427
+
1428
+        // add validation data to satisfy user validation plugins
1429
+        $user->validated = 1;
1430
+        $user->validated_method = 'admin_user';
1431
+
1432
+        if (!$login) {
1433
+            return true;
1434
+        }
1435
+
1436
+        $session = ElggSession::fromDatabase($this->config, $this->services->db);
1437
+        $session->start();
1438
+        $this->services->setValue('session', $session);
1439
+        if (login($user) == false) {
1440
+            register_error(elgg_echo('install:error:adminlogin'));
1441
+        }
1442
+
1443
+        return true;
1444
+    }
1445 1445
 }
Please login to merge, or discard this patch.
engine/classes/Elgg/Application.php 3 patches
Doc Comments   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -457,7 +457,7 @@  discard block
 block discarded – undo
457 457
 	/**
458 458
 	 * Elgg's front controller. Handles basically all incoming URL requests.
459 459
 	 *
460
-	 * @return bool True if Elgg will handle the request, false if the server should (PHP-CLI server)
460
+	 * @return boolean|null True if Elgg will handle the request, false if the server should (PHP-CLI server)
461 461
 	 */
462 462
 	public static function index() {
463 463
 		$req = Request::createFromGlobals();
@@ -474,7 +474,7 @@  discard block
 block discarded – undo
474 474
 	/**
475 475
 	 * Routes the request, booting core if not yet booted
476 476
 	 *
477
-	 * @return bool False if Elgg wants the PHP CLI server to handle the request
477
+	 * @return boolean|null False if Elgg wants the PHP CLI server to handle the request
478 478
 	 */
479 479
 	public function run() {
480 480
 		$config = $this->_services->config;
@@ -852,7 +852,7 @@  discard block
 block discarded – undo
852 852
 	 * @param int    $linenum  The line number the error was raised at
853 853
 	 * @param array  $vars     An array that points to the active symbol table where error occurred
854 854
 	 *
855
-	 * @return true
855
+	 * @return boolean
856 856
 	 * @throws \Exception
857 857
 	 * @access private
858 858
 	 */
Please login to merge, or discard this patch.
Spacing   +24 added lines, -24 removed lines patch added patch discarded remove patch
@@ -95,13 +95,13 @@  discard block
 block discarded – undo
95 95
 		}
96 96
 
97 97
 		if ($config->elgg_config_locks) {
98
-			$lock = function ($name) use ($config) {
98
+			$lock = function($name) use ($config) {
99 99
 				$config->lock($name);
100 100
 			};
101 101
 		} else {
102 102
 			// the installer needs to build an application with defaults then update
103 103
 			// them after they're validated, so we don't want to lock them.
104
-			$lock = function () {
104
+			$lock = function() {
105 105
 			};
106 106
 		}
107 107
 
@@ -113,7 +113,7 @@  discard block
 block discarded – undo
113 113
 		}
114 114
 
115 115
 		if ($config->dataroot) {
116
-			$config->dataroot = rtrim($config->dataroot, '\\/') . DIRECTORY_SEPARATOR;
116
+			$config->dataroot = rtrim($config->dataroot, '\\/').DIRECTORY_SEPARATOR;
117 117
 		} else {
118 118
 			if (!$config->installer_running) {
119 119
 				throw new ConfigurationException('Config value "dataroot" is required.');
@@ -122,14 +122,14 @@  discard block
 block discarded – undo
122 122
 		$lock('dataroot');
123 123
 
124 124
 		if ($config->cacheroot) {
125
-			$config->cacheroot = rtrim($config->cacheroot, '\\/') . DIRECTORY_SEPARATOR;
125
+			$config->cacheroot = rtrim($config->cacheroot, '\\/').DIRECTORY_SEPARATOR;
126 126
 		} else {
127 127
 			$config->cacheroot = $config->dataroot;
128 128
 		}
129 129
 		$lock('cacheroot');
130 130
 
131 131
 		if ($config->wwwroot) {
132
-			$config->wwwroot = rtrim($config->wwwroot, '/') . '/';
132
+			$config->wwwroot = rtrim($config->wwwroot, '/').'/';
133 133
 		} else {
134 134
 			$config->wwwroot = $this->_services->request->sniffElggUrl();
135 135
 		}
@@ -148,8 +148,8 @@  discard block
 block discarded – undo
148 148
 		$locked_props = [
149 149
 			'site_guid' => 1,
150 150
 			'path' => Paths::project(),
151
-			'plugins_path' => Paths::project() . "mod/",
152
-			'pluginspath' => Paths::project() . "mod/",
151
+			'plugins_path' => Paths::project()."mod/",
152
+			'pluginspath' => Paths::project()."mod/",
153 153
 			'url' => $config->wwwroot,
154 154
 		];
155 155
 		foreach ($locked_props as $name => $value) {
@@ -199,7 +199,7 @@  discard block
 block discarded – undo
199 199
 		}
200 200
 
201 201
 		$setups = [];
202
-		$path = Paths::elgg() . 'engine/lib';
202
+		$path = Paths::elgg().'engine/lib';
203 203
 
204 204
 		// include library files, capturing setup functions
205 205
 		foreach (self::getEngineLibs() as $file) {
@@ -261,7 +261,7 @@  discard block
 block discarded – undo
261 261
 		$config = $this->_services->config;
262 262
 
263 263
 		if ($this->isTestingApplication()) {
264
-			throw new \RuntimeException('Unit tests should not call ' . __METHOD__);
264
+			throw new \RuntimeException('Unit tests should not call '.__METHOD__);
265 265
 		}
266 266
 
267 267
 		if ($config->boot_complete) {
@@ -300,7 +300,7 @@  discard block
 block discarded – undo
300 300
 			// as a custom plugin that is always loaded last and can't be disabled...
301 301
 			if (!$config->system_cache_loaded) {
302 302
 				// configure view locations for the custom plugin (not Elgg core)
303
-				$viewsFile = Paths::project() . 'views.php';
303
+				$viewsFile = Paths::project().'views.php';
304 304
 				if (is_file($viewsFile)) {
305 305
 					$viewsSpec = Includer::includeFile($viewsFile);
306 306
 					if (is_array($viewsSpec)) {
@@ -317,7 +317,7 @@  discard block
 block discarded – undo
317 317
 			}
318 318
 
319 319
 			// This is root directory start.php
320
-			$root_start = Paths::project() . "start.php";
320
+			$root_start = Paths::project()."start.php";
321 321
 			if (is_file($root_start)) {
322 322
 				require $root_start;
323 323
 			}
@@ -367,7 +367,7 @@  discard block
 block discarded – undo
367 367
 		if (isset(self::$public_services[$name])) {
368 368
 			return $this->_services->{$name};
369 369
 		}
370
-		trigger_error("Undefined property: " . __CLASS__ . ":\${$name}");
370
+		trigger_error("Undefined property: ".__CLASS__.":\${$name}");
371 371
 	}
372 372
 
373 373
 	/**
@@ -418,7 +418,7 @@  discard block
 block discarded – undo
418 418
 			if ($spec['request'] instanceof Request) {
419 419
 				$spec['service_provider']->setValue('request', $spec['request']);
420 420
 			} else {
421
-				throw new \InvalidArgumentException("Given request is not a " . Request::class);
421
+				throw new \InvalidArgumentException("Given request is not a ".Request::class);
422 422
 			}
423 423
 		}
424 424
 
@@ -432,7 +432,7 @@  discard block
 block discarded – undo
432 432
 		if ($spec['handle_shutdown']) {
433 433
 			// we need to register for shutdown before Symfony registers the
434 434
 			// session_write_close() function. https://github.com/Elgg/Elgg/issues/9243
435
-			register_shutdown_function(function () {
435
+			register_shutdown_function(function() {
436 436
 				// There are cases where we may exit before this function is defined
437 437
 				if (function_exists('_elgg_shutdown_hook')) {
438 438
 					_elgg_shutdown_hook();
@@ -486,7 +486,7 @@  discard block
 block discarded – undo
486 486
 			}
487 487
 
488 488
 			// overwrite value from settings
489
-			$www_root = rtrim($request->getSchemeAndHttpHost() . $request->getBaseUrl(), '/') . '/';
489
+			$www_root = rtrim($request->getSchemeAndHttpHost().$request->getBaseUrl(), '/').'/';
490 490
 			$config->wwwroot = $www_root;
491 491
 			$config->wwwroot_cli_server = $www_root;
492 492
 		}
@@ -564,9 +564,9 @@  discard block
 block discarded – undo
564 564
 		ini_set('display_errors', 1);
565 565
 		$is_cli = (php_sapi_name() === 'cli');
566 566
 
567
-		$forward = function ($url) use ($is_cli) {
567
+		$forward = function($url) use ($is_cli) {
568 568
 			if ($is_cli) {
569
-				fwrite(STDOUT, "Open $url in your browser to continue." . PHP_EOL);
569
+				fwrite(STDOUT, "Open $url in your browser to continue.".PHP_EOL);
570 570
 				return;
571 571
 			}
572 572
 
@@ -585,14 +585,14 @@  discard block
 block discarded – undo
585 585
 		}
586 586
 
587 587
 		$site_url = _elgg_config()->url;
588
-		$site_host = parse_url($site_url, PHP_URL_HOST) . '/';
588
+		$site_host = parse_url($site_url, PHP_URL_HOST).'/';
589 589
 
590 590
 		// turn any full in-site URLs into absolute paths
591 591
 		$forward_url = get_input('forward', '/admin', false);
592 592
 		$forward_url = str_replace([$site_url, $site_host], '/', $forward_url);
593 593
 
594 594
 		if (strpos($forward_url, '/') !== 0) {
595
-			$forward_url = '/' . $forward_url;
595
+			$forward_url = '/'.$forward_url;
596 596
 		}
597 597
 
598 598
 		if ($is_cli || (get_input('upgrade') == 'upgrade')) {
@@ -614,7 +614,7 @@  discard block
 block discarded – undo
614 614
 			}
615 615
 		} else {
616 616
 			$rewriteTester = new \ElggRewriteTester();
617
-			$url = elgg_get_site_url() . "__testing_rewrite?__testing_rewrite=1";
617
+			$url = elgg_get_site_url()."__testing_rewrite?__testing_rewrite=1";
618 618
 			if (!$rewriteTester->runRewriteTest($url)) {
619 619
 				// see if there is a problem accessing the site at all
620 620
 				// due to ip restrictions for example
@@ -691,7 +691,7 @@  discard block
 block discarded – undo
691 691
 
692 692
 		return [
693 693
 			"paths" => [
694
-				"migrations" => Paths::elgg() . 'engine/schema/migrations/',
694
+				"migrations" => Paths::elgg().'engine/schema/migrations/',
695 695
 			],
696 696
 			"environments" => [
697 697
 				"default_migration_table" => "{$conn['prefix']}migrations",
@@ -857,9 +857,9 @@  discard block
 block discarded – undo
857 857
 	 * @access private
858 858
 	 */
859 859
 	public function handleErrors($errno, $errmsg, $filename, $linenum, $vars) {
860
-		$error = date("Y-m-d H:i:s (T)") . ": \"$errmsg\" in file $filename (line $linenum)";
860
+		$error = date("Y-m-d H:i:s (T)").": \"$errmsg\" in file $filename (line $linenum)";
861 861
 
862
-		$log = function ($message, $level) {
862
+		$log = function($message, $level) {
863 863
 			if (self::isCoreLoaded()) {
864 864
 				return elgg_log($message, $level);
865 865
 			}
@@ -915,7 +915,7 @@  discard block
 block discarded – undo
915 915
 	 * @deprecated
916 916
 	 */
917 917
 	public function loadSettings() {
918
-		trigger_error(__METHOD__ . ' is no longer needed and will be removed.');
918
+		trigger_error(__METHOD__.' is no longer needed and will be removed.');
919 919
 	}
920 920
 
921 921
 	/**
Please login to merge, or discard this patch.
Indentation   +941 added lines, -941 removed lines patch added patch discarded remove patch
@@ -28,945 +28,945 @@
 block discarded – undo
28 28
  */
29 29
 class Application {
30 30
 
31
-	const DEFAULT_LANG = 'en';
32
-	const DEFAULT_LIMIT = 10;
33
-
34
-	/**
35
-	 * @var ServiceProvider
36
-	 *
37
-	 * @internal DO NOT USE
38
-	 */
39
-	public $_services;
40
-
41
-	/**
42
-	 * @var bool
43
-	 */
44
-	private static $testing_app;
45
-
46
-	/**
47
-	 * Property names of the service provider to be exposed via __get()
48
-	 *
49
-	 * E.g. the presence of `'foo' => true` in the list would allow _elgg_services()->foo to
50
-	 * be accessed via elgg()->foo.
51
-	 *
52
-	 * @var string[]
53
-	 */
54
-	private static $public_services = [
55
-		//'config' => true,
56
-		'menus' => true,
57
-		'table_columns' => true,
58
-	];
59
-
60
-	/**
61
-	 * Reference to the loaded Application returned by elgg()
62
-	 *
63
-	 * @internal Do not use this. use elgg() to access the application
64
-	 * @access private
65
-	 * @var Application
66
-	 */
67
-	public static $_instance;
68
-
69
-	/**
70
-	 * Constructor
71
-	 *
72
-	 * Upon construction, no actions are taken to load or boot Elgg.
73
-	 *
74
-	 * @param ServiceProvider $services Elgg services provider
75
-	 * @throws ConfigurationException
76
-	 */
77
-	public function __construct(ServiceProvider $services) {
78
-		$this->_services = $services;
79
-
80
-		$this->initConfig();
81
-	}
82
-
83
-	/**
84
-	 * Validate, normalize, fill in missing values, and lock some
85
-	 *
86
-	 * @return void
87
-	 * @throws ConfigurationException
88
-	 */
89
-	private function initConfig() {
90
-		$config = $this->_services->config;
91
-
92
-		if ($config->elgg_config_locks === null) {
93
-			$config->elgg_config_locks = true;
94
-		}
95
-
96
-		if ($config->elgg_config_locks) {
97
-			$lock = function ($name) use ($config) {
98
-				$config->lock($name);
99
-			};
100
-		} else {
101
-			// the installer needs to build an application with defaults then update
102
-			// them after they're validated, so we don't want to lock them.
103
-			$lock = function () {
104
-			};
105
-		}
106
-
107
-		$this->_services->timer->begin([]);
108
-
109
-		// Until DB loads, let's log problems
110
-		if ($config->debug === null) {
111
-			$config->debug = 'NOTICE';
112
-		}
113
-
114
-		if ($config->dataroot) {
115
-			$config->dataroot = rtrim($config->dataroot, '\\/') . DIRECTORY_SEPARATOR;
116
-		} else {
117
-			if (!$config->installer_running) {
118
-				throw new ConfigurationException('Config value "dataroot" is required.');
119
-			}
120
-		}
121
-		$lock('dataroot');
122
-
123
-		if ($config->cacheroot) {
124
-			$config->cacheroot = rtrim($config->cacheroot, '\\/') . DIRECTORY_SEPARATOR;
125
-		} else {
126
-			$config->cacheroot = $config->dataroot;
127
-		}
128
-		$lock('cacheroot');
129
-
130
-		if ($config->wwwroot) {
131
-			$config->wwwroot = rtrim($config->wwwroot, '/') . '/';
132
-		} else {
133
-			$config->wwwroot = $this->_services->request->sniffElggUrl();
134
-		}
135
-		$lock('wwwroot');
136
-
137
-		if (!$config->language) {
138
-			$config->language = self::DEFAULT_LANG;
139
-		}
140
-
141
-		if ($config->default_limit) {
142
-			$lock('default_limit');
143
-		} else {
144
-			$config->default_limit = self::DEFAULT_LIMIT;
145
-		}
146
-
147
-		$locked_props = [
148
-			'site_guid' => 1,
149
-			'path' => Paths::project(),
150
-			'plugins_path' => Paths::project() . "mod/",
151
-			'pluginspath' => Paths::project() . "mod/",
152
-			'url' => $config->wwwroot,
153
-		];
154
-		foreach ($locked_props as $name => $value) {
155
-			$config->$name = $value;
156
-			$lock($name);
157
-		}
158
-
159
-		// move sensitive credentials into isolated services
160
-		$this->_services->dbConfig;
161
-
162
-		// If the site secret is in the settings file, let's move it to that component
163
-		// right away to keep this value out of config.
164
-		$secret = SiteSecret::fromConfig($config);
165
-		if ($secret) {
166
-			$this->_services->setValue('siteSecret', $secret);
167
-			$config->elgg_config_set_secret = true;
168
-		}
169
-
170
-		$config->boot_complete = false;
171
-	}
172
-
173
-	/**
174
-	 * Get the DB credentials.
175
-	 *
176
-	 * We no longer leave DB credentials in the config in case it gets accidentally dumped.
177
-	 *
178
-	 * @return \Elgg\Database\DbConfig
179
-	 */
180
-	public function getDbConfig() {
181
-		return $this->_services->dbConfig;
182
-	}
183
-
184
-	/**
185
-	 * Define all Elgg global functions and constants, wire up boot events, but don't boot
186
-	 *
187
-	 * This includes all the .php files in engine/lib (not upgrades). If a script returns a function,
188
-	 * it is queued and executed at the end.
189
-	 *
190
-	 * @return void
191
-	 * @access private
192
-	 * @internal
193
-	 * @throws \InstallationException
194
-	 */
195
-	public function loadCore() {
196
-		if (self::isCoreLoaded()) {
197
-			return;
198
-		}
199
-
200
-		$setups = [];
201
-		$path = Paths::elgg() . 'engine/lib';
202
-
203
-		// include library files, capturing setup functions
204
-		foreach (self::getEngineLibs() as $file) {
205
-			$return = Includer::includeFile("$path/$file");
206
-			if (!$return) {
207
-				throw new \InstallationException("Elgg lib file failed include: engine/lib/$file");
208
-			}
209
-			if ($return instanceof \Closure) {
210
-				$setups[$file] = $return;
211
-			}
212
-		}
213
-
214
-		// store instance to be returned by elgg()
215
-		self::$_instance = $this;
216
-
217
-		$hooks = $this->_services->hooks;
218
-		$events = $hooks->getEvents();
219
-
220
-		// run setups
221
-		foreach ($setups as $func) {
222
-			$func($events, $hooks);
223
-		}
224
-	}
225
-
226
-	/**
227
-	 * Start and boot the core
228
-	 *
229
-	 * @return self
230
-	 */
231
-	public static function start() {
232
-		$app = self::factory();
233
-		$app->bootCore();
234
-		return $app;
235
-	}
236
-
237
-	/**
238
-	 * Are Elgg's global functions loaded?
239
-	 *
240
-	 * @return bool
241
-	 */
242
-	public static function isCoreLoaded() {
243
-		return function_exists('elgg');
244
-	}
245
-
246
-	/**
247
-	 * Bootstrap the Elgg engine, loads plugins, and calls initial system events
248
-	 *
249
-	 * This method loads the full Elgg engine, checks the installation
250
-	 * state, and triggers a series of events to finish booting Elgg:
251
-	 * 	- {@elgg_event boot system}
252
-	 * 	- {@elgg_event init system}
253
-	 * 	- {@elgg_event ready system}
254
-	 *
255
-	 * If Elgg is not fully installed, the browser will be redirected to an installation page.
256
-	 *
257
-	 * @return void
258
-	 */
259
-	public function bootCore() {
260
-		$config = $this->_services->config;
261
-
262
-		if ($this->isTestingApplication()) {
263
-			throw new \RuntimeException('Unit tests should not call ' . __METHOD__);
264
-		}
265
-
266
-		if ($config->boot_complete) {
267
-			return;
268
-		}
269
-
270
-		// in case not loaded already
271
-		$this->loadCore();
272
-
273
-		if (!$this->_services->db) {
274
-			// no database boot!
275
-			elgg_views_boot();
276
-			$this->_services->session->start();
277
-			$this->_services->translator->loadTranslations();
278
-
279
-			actions_init();
280
-			_elgg_init();
281
-			_elgg_input_init();
282
-			_elgg_nav_init();
283
-
284
-			$config->boot_complete = true;
285
-			$config->lock('boot_complete');
286
-			return;
287
-		}
288
-
289
-		// Connect to database, load language files, load configuration, init session
290
-		$this->_services->boot->boot($this->_services);
291
-
292
-		elgg_views_boot();
293
-
294
-		// Load the plugins that are active
295
-		$this->_services->plugins->load();
296
-
297
-		if (Paths::project() != Paths::elgg()) {
298
-			// Elgg is installed as a composer dep, so try to treat the root directory
299
-			// as a custom plugin that is always loaded last and can't be disabled...
300
-			if (!$config->system_cache_loaded) {
301
-				// configure view locations for the custom plugin (not Elgg core)
302
-				$viewsFile = Paths::project() . 'views.php';
303
-				if (is_file($viewsFile)) {
304
-					$viewsSpec = Includer::includeFile($viewsFile);
305
-					if (is_array($viewsSpec)) {
306
-						$this->_services->views->mergeViewsSpec($viewsSpec);
307
-					}
308
-				}
309
-
310
-				// find views for the custom plugin (not Elgg core)
311
-				$this->_services->views->registerPluginViews(Paths::project());
312
-			}
313
-
314
-			if (!$config->i18n_loaded_from_cache) {
315
-				$this->_services->translator->registerPluginTranslations(Paths::project());
316
-			}
317
-
318
-			// This is root directory start.php
319
-			$root_start = Paths::project() . "start.php";
320
-			if (is_file($root_start)) {
321
-				require $root_start;
322
-			}
323
-		}
324
-
325
-		// after plugins are started we know which viewtypes are populated
326
-		$this->_services->views->clampViewtypeToPopulatedViews();
327
-
328
-		$this->allowPathRewrite();
329
-
330
-		$events = $this->_services->hooks->getEvents();
331
-
332
-		// Allows registering handlers strictly before all init, system handlers
333
-		$events->trigger('plugins_boot', 'system');
334
-
335
-		// Complete the boot process for both engine and plugins
336
-		$events->trigger('init', 'system');
337
-
338
-		$config->boot_complete = true;
339
-		$config->lock('boot_complete');
340
-
341
-		// System loaded and ready
342
-		$events->trigger('ready', 'system');
343
-	}
344
-
345
-	/**
346
-	 * Get a Database wrapper for performing queries without booting Elgg
347
-	 *
348
-	 * If settings has not been loaded, it will be loaded to configure the DB connection.
349
-	 *
350
-	 * @note Before boot, the Database instance will not yet be bound to a Logger.
351
-	 *
352
-	 * @return \Elgg\Application\Database
353
-	 */
354
-	public function getDb() {
355
-		return $this->_services->publicDb;
356
-	}
357
-
358
-	/**
359
-	 * Get an undefined property
360
-	 *
361
-	 * @param string $name The property name accessed
362
-	 *
363
-	 * @return mixed
364
-	 */
365
-	public function __get($name) {
366
-		if (isset(self::$public_services[$name])) {
367
-			return $this->_services->{$name};
368
-		}
369
-		trigger_error("Undefined property: " . __CLASS__ . ":\${$name}");
370
-	}
371
-
372
-	/**
373
-	 * Creates a new, trivial instance of Elgg\Application and set it as the singleton instance.
374
-	 * If the singleton is already set, it's returned.
375
-	 *
376
-	 * @param array $spec Specification for initial call.
377
-	 * @return self
378
-	 * @throws ConfigurationException
379
-	 */
380
-	public static function factory(array $spec = []) {
381
-		if (self::$_instance !== null) {
382
-			return self::$_instance;
383
-		}
384
-
385
-		$defaults = [
386
-			'service_provider' => null,
387
-			'config' => null,
388
-			'settings_path' => null,
389
-			'handle_exceptions' => true,
390
-			'handle_shutdown' => true,
391
-			'set_global_config' => true,
392
-			'set_start_time' => true,
393
-			'request' => null,
394
-			'set_global_instance' => true,
395
-		];
396
-		$spec = array_merge($defaults, $spec);
397
-
398
-		if ($spec['set_start_time']) {
399
-			/**
400
-			 * The time with microseconds when the Elgg engine was started.
401
-			 *
402
-			 * @global float
403
-			 */
404
-			if (!isset($GLOBALS['START_MICROTIME'])) {
405
-				$GLOBALS['START_MICROTIME'] = microtime(true);
406
-			}
407
-		}
408
-
409
-		if (!$spec['service_provider']) {
410
-			if (!$spec['config']) {
411
-				$spec['config'] = Config::factory($spec['settings_path']);
412
-			}
413
-			$spec['service_provider'] = new ServiceProvider($spec['config']);
414
-		}
415
-
416
-		if ($spec['request']) {
417
-			if ($spec['request'] instanceof Request) {
418
-				$spec['service_provider']->setValue('request', $spec['request']);
419
-			} else {
420
-				throw new \InvalidArgumentException("Given request is not a " . Request::class);
421
-			}
422
-		}
423
-
424
-		$app = new self($spec['service_provider']);
425
-
426
-		if ($spec['handle_exceptions']) {
427
-			set_error_handler([$app, 'handleErrors']);
428
-			set_exception_handler([$app, 'handleExceptions']);
429
-		}
430
-
431
-		if ($spec['handle_shutdown']) {
432
-			// we need to register for shutdown before Symfony registers the
433
-			// session_write_close() function. https://github.com/Elgg/Elgg/issues/9243
434
-			register_shutdown_function(function () {
435
-				// There are cases where we may exit before this function is defined
436
-				if (function_exists('_elgg_shutdown_hook')) {
437
-					_elgg_shutdown_hook();
438
-				}
439
-			});
440
-		}
441
-
442
-		if ($spec['set_global_config']) {
443
-			global $CONFIG;
444
-
445
-			// this will be buggy be at least PHP will log failures
446
-			$CONFIG = $spec['service_provider']->config;
447
-		}
448
-
449
-		if ($spec['set_global_instance']) {
450
-			self::$_instance = $app;
451
-		}
452
-
453
-		return $app;
454
-	}
455
-
456
-	/**
457
-	 * Elgg's front controller. Handles basically all incoming URL requests.
458
-	 *
459
-	 * @return bool True if Elgg will handle the request, false if the server should (PHP-CLI server)
460
-	 */
461
-	public static function index() {
462
-		$req = Request::createFromGlobals();
463
-		/** @var Request $req */
464
-
465
-		if ($req->isRewriteCheck()) {
466
-			echo Request::REWRITE_TEST_OUTPUT;
467
-			return true;
468
-		}
469
-
470
-		return self::factory(['request' => $req])->run();
471
-	}
472
-
473
-	/**
474
-	 * Routes the request, booting core if not yet booted
475
-	 *
476
-	 * @return bool False if Elgg wants the PHP CLI server to handle the request
477
-	 */
478
-	public function run() {
479
-		$config = $this->_services->config;
480
-		$request = $this->_services->request;
481
-
482
-		if ($request->isCliServer()) {
483
-			if ($request->isCliServable(Paths::project())) {
484
-				return false;
485
-			}
486
-
487
-			// overwrite value from settings
488
-			$www_root = rtrim($request->getSchemeAndHttpHost() . $request->getBaseUrl(), '/') . '/';
489
-			$config->wwwroot = $www_root;
490
-			$config->wwwroot_cli_server = $www_root;
491
-		}
492
-
493
-		if (0 === strpos($request->getElggPath(), '/cache/')) {
494
-			$this->_services->cacheHandler->handleRequest($request, $this)->prepare($request)->send();
495
-			return true;
496
-		}
497
-
498
-		if (0 === strpos($request->getElggPath(), '/serve-file/')) {
499
-			$this->_services->serveFileHandler->getResponse($request)->send();
500
-			return true;
501
-		}
502
-
503
-		$this->bootCore();
504
-
505
-		// TODO use formal Response object instead
506
-		// This is to set the charset to UTF-8.
507
-		header("Content-Type: text/html;charset=utf-8", true);
508
-
509
-		// re-fetch new request from services in case it was replaced by route:rewrite
510
-		$request = $this->_services->request;
511
-
512
-		if (!$this->_services->router->route($request)) {
513
-			forward('', '404');
514
-		}
515
-	}
516
-
517
-	/**
518
-	 * Returns a directory that points to the root of Elgg, but not necessarily
519
-	 * the install root. See `self::root()` for that.
520
-	 *
521
-	 * @return Directory
522
-	 */
523
-	public static function elggDir() {
524
-		return Local::elggRoot();
525
-	}
526
-
527
-	/**
528
-	 * Returns a directory that points to the project root, where composer is installed.
529
-	 *
530
-	 * @return Directory
531
-	 */
532
-	public static function projectDir() {
533
-		return Local::projectRoot();
534
-	}
535
-
536
-	/**
537
-	 * Renders a web UI for installing Elgg.
538
-	 *
539
-	 * @return void
540
-	 */
541
-	public static function install() {
542
-		ini_set('display_errors', 1);
543
-		$installer = new \ElggInstaller();
544
-		$installer->run();
545
-	}
546
-
547
-	/**
548
-	 * Elgg upgrade script.
549
-	 *
550
-	 * This script triggers any necessary upgrades. If the site has been upgraded
551
-	 * to the most recent version of the code, no upgrades are run but the caches
552
-	 * are flushed.
553
-	 *
554
-	 * Upgrades use a table {db_prefix}upgrade_lock as a mutex to prevent concurrent upgrades.
555
-	 *
556
-	 * The URL to forward to after upgrades are complete can be specified by setting $_GET['forward']
557
-	 * to a relative URL.
558
-	 *
559
-	 * @return void
560
-	 */
561
-	public static function upgrade() {
562
-		// we want to know if an error occurs
563
-		ini_set('display_errors', 1);
564
-		$is_cli = (php_sapi_name() === 'cli');
565
-
566
-		$forward = function ($url) use ($is_cli) {
567
-			if ($is_cli) {
568
-				fwrite(STDOUT, "Open $url in your browser to continue." . PHP_EOL);
569
-				return;
570
-			}
571
-
572
-			forward($url);
573
-		};
574
-
575
-		define('UPGRADING', 'upgrading');
576
-
577
-		self::migrate();
578
-		self::start();
579
-
580
-		// check security settings
581
-		if (!$is_cli && _elgg_config()->security_protect_upgrade && !elgg_is_admin_logged_in()) {
582
-			// only admin's or users with a valid token can run upgrade.php
583
-			elgg_signed_request_gatekeeper();
584
-		}
585
-
586
-		$site_url = _elgg_config()->url;
587
-		$site_host = parse_url($site_url, PHP_URL_HOST) . '/';
588
-
589
-		// turn any full in-site URLs into absolute paths
590
-		$forward_url = get_input('forward', '/admin', false);
591
-		$forward_url = str_replace([$site_url, $site_host], '/', $forward_url);
592
-
593
-		if (strpos($forward_url, '/') !== 0) {
594
-			$forward_url = '/' . $forward_url;
595
-		}
596
-
597
-		if ($is_cli || (get_input('upgrade') == 'upgrade')) {
598
-			$upgrader = _elgg_services()->upgrades;
599
-			$result = $upgrader->run();
600
-
601
-			if ($result['failure'] == true) {
602
-				register_error($result['reason']);
603
-				$forward($forward_url);
604
-			}
605
-
606
-			// Find unprocessed batch upgrade classes and save them as ElggUpgrade objects
607
-			$core_upgrades = (require self::elggDir()->getPath('engine/lib/upgrades/async-upgrades.php'));
608
-			$has_pending_upgrades = _elgg_services()->upgradeLocator->run($core_upgrades);
609
-
610
-			if ($has_pending_upgrades) {
611
-				// Forward to the list of pending upgrades
612
-				$forward_url = '/admin/upgrades';
613
-			}
614
-		} else {
615
-			$rewriteTester = new \ElggRewriteTester();
616
-			$url = elgg_get_site_url() . "__testing_rewrite?__testing_rewrite=1";
617
-			if (!$rewriteTester->runRewriteTest($url)) {
618
-				// see if there is a problem accessing the site at all
619
-				// due to ip restrictions for example
620
-				if (!$rewriteTester->runLocalhostAccessTest()) {
621
-					// note: translation may not be available until after upgrade
622
-					$msg = elgg_echo("installation:htaccess:localhost:connectionfailed");
623
-					if ($msg === "installation:htaccess:localhost:connectionfailed") {
624
-						$msg = "Elgg cannot connect to itself to test rewrite rules properly. Check "
625
-								. "that curl is working and there are no IP restrictions preventing "
626
-								. "localhost connections.";
627
-					}
628
-					echo $msg;
629
-					exit;
630
-				}
631
-
632
-				// note: translation may not be available until after upgrade
633
-				$msg = elgg_echo("installation:htaccess:needs_upgrade");
634
-				if ($msg === "installation:htaccess:needs_upgrade") {
635
-					$msg = "You must update your .htaccess file (use install/config/htaccess.dist as a guide).";
636
-				}
637
-				echo $msg;
638
-				exit;
639
-			}
640
-
641
-			$vars = [
642
-				'forward' => $forward_url
643
-			];
644
-
645
-			// reset cache to have latest translations available during upgrade
646
-			elgg_reset_system_cache();
647
-
648
-			echo elgg_view_page(elgg_echo('upgrading'), '', 'upgrade', $vars);
649
-			exit;
650
-		}
651
-
652
-		$forward($forward_url);
653
-	}
654
-
655
-	/**
656
-	 * Runs database migrations
657
-	 *
658
-	 * @throws InstallationException
659
-	 * @return bool
660
-	 */
661
-	public static function migrate() {
662
-		$conf = self::elggDir()->getPath('engine/schema/settings.php');
663
-		if (!$conf) {
664
-			throw new Exception('Settings file is required to run database migrations.');
665
-		}
666
-
667
-		$app = new \Phinx\Console\PhinxApplication();
668
-		$wrapper = new \Phinx\Wrapper\TextWrapper($app, [
669
-			'configuration' => $conf,
670
-		]);
671
-		$log = $wrapper->getMigrate();
672
-		error_log($log);
673
-
674
-		return true;
675
-	}
676
-
677
-	/**
678
-	 * Returns configuration array for database migrations
679
-	 * @return array
680
-	 */
681
-	public static function getMigrationSettings() {
682
-
683
-		$config = Config::factory();
684
-		$db_config = DbConfig::fromElggConfig($config);
685
-		if ($db_config->isDatabaseSplit()) {
686
-			$conn = $db_config->getConnectionConfig(DbConfig::WRITE);
687
-		} else {
688
-			$conn = $db_config->getConnectionConfig();
689
-		}
690
-
691
-		return [
692
-			"paths" => [
693
-				"migrations" => Paths::elgg() . 'engine/schema/migrations/',
694
-			],
695
-			"environments" => [
696
-				"default_migration_table" => "{$conn['prefix']}migrations",
697
-				"default_database" => "prod",
698
-				"prod" => [
699
-					"adapter" => "mysql",
700
-					"host" => $conn['host'],
701
-					"name" => $conn['database'],
702
-					"user" => $conn['user'],
703
-					"pass" => $conn['password'],
704
-					"charset" => $conn['encoding'],
705
-					"table_prefix" => $conn['prefix'],
706
-				],
707
-			],
708
-		];
709
-	}
710
-
711
-	/**
712
-	 * Allow plugins to rewrite the path.
713
-	 *
714
-	 * @return void
715
-	 */
716
-	private function allowPathRewrite() {
717
-		$request = $this->_services->request;
718
-		$new = $this->_services->router->allowRewrite($request);
719
-		if ($new === $request) {
720
-			return;
721
-		}
722
-
723
-		$this->_services->setValue('request', $new);
724
-		$this->_services->context->initialize($new);
725
-	}
726
-
727
-	/**
728
-	 * Flag this application as running for testing (PHPUnit)
729
-	 *
730
-	 * @param bool $testing Is testing application
731
-	 * @return void
732
-	 */
733
-	public static function setTestingApplication($testing = true) {
734
-		self::$testing_app = $testing;
735
-	}
736
-
737
-	/**
738
-	 * Checks if the application is running in PHPUnit
739
-	 * @return bool
740
-	 */
741
-	public static function isTestingApplication() {
742
-		return (bool) self::$testing_app;
743
-	}
744
-
745
-	/**
746
-	 * Intercepts, logs, and displays uncaught exceptions.
747
-	 *
748
-	 * To use a viewtype other than failsafe, create the views:
749
-	 *  <viewtype>/messages/exceptions/admin_exception
750
-	 *  <viewtype>/messages/exceptions/exception
751
-	 * See the json viewtype for an example.
752
-	 *
753
-	 * @warning This function should never be called directly.
754
-	 *
755
-	 * @see http://www.php.net/set-exception-handler
756
-	 *
757
-	 * @param \Exception|\Error $exception The exception/error being handled
758
-	 *
759
-	 * @return void
760
-	 * @access private
761
-	 */
762
-	public function handleExceptions($exception) {
763
-		$timestamp = time();
764
-		error_log("Exception at time $timestamp: $exception");
765
-
766
-		// Wipe any existing output buffer
767
-		ob_end_clean();
768
-
769
-		// make sure the error isn't cached
770
-		header("Cache-Control: no-cache, must-revalidate", true);
771
-		header('Expires: Fri, 05 Feb 1982 00:00:00 -0500', true);
772
-
773
-		if ($exception instanceof \InstallationException) {
774
-			forward('/install.php');
775
-		}
776
-
777
-		if (!self::isCoreLoaded()) {
778
-			http_response_code(500);
779
-			echo "Exception loading Elgg core. Check log at time $timestamp";
780
-			return;
781
-		}
782
-
783
-		try {
784
-			// allow custom scripts to trigger on exception
785
-			// value in settings.php should be a system path to a file to include
786
-			$exception_include = $this->_services->config->exception_include;
787
-
788
-			if ($exception_include && is_file($exception_include)) {
789
-				ob_start();
790
-
791
-				// don't isolate, these scripts may use the local $exception var.
792
-				include $exception_include;
793
-
794
-				$exception_output = ob_get_clean();
795
-
796
-				// if content is returned from the custom handler we will output
797
-				// that instead of our default failsafe view
798
-				if (!empty($exception_output)) {
799
-					echo $exception_output;
800
-					exit;
801
-				}
802
-			}
803
-
804
-			if (elgg_is_xhr()) {
805
-				elgg_set_viewtype('json');
806
-				$response = new \Symfony\Component\HttpFoundation\JsonResponse(null, 500);
807
-			} else {
808
-				elgg_set_viewtype('failsafe');
809
-				$response = new \Symfony\Component\HttpFoundation\Response('', 500);
810
-			}
811
-
812
-			if (elgg_is_admin_logged_in()) {
813
-				$body = elgg_view("messages/exceptions/admin_exception", [
814
-					'object' => $exception,
815
-					'ts' => $timestamp
816
-				]);
817
-			} else {
818
-				$body = elgg_view("messages/exceptions/exception", [
819
-					'object' => $exception,
820
-					'ts' => $timestamp
821
-				]);
822
-			}
823
-
824
-			$response->setContent(elgg_view_page(elgg_echo('exception:title'), $body));
825
-			$response->send();
826
-		} catch (\Exception $e) {
827
-			$timestamp = time();
828
-			$message = $e->getMessage();
829
-			http_response_code(500);
830
-			echo "Fatal error in exception handler. Check log for Exception at time $timestamp";
831
-			error_log("Exception at time $timestamp : fatal error in exception handler : $message");
832
-		}
833
-	}
834
-
835
-	/**
836
-	 * Intercepts catchable PHP errors.
837
-	 *
838
-	 * @warning This function should never be called directly.
839
-	 *
840
-	 * @internal
841
-	 * For catchable fatal errors, throws an Exception with the error.
842
-	 *
843
-	 * For non-fatal errors, depending upon the debug settings, either
844
-	 * log the error or ignore it.
845
-	 *
846
-	 * @see http://www.php.net/set-error-handler
847
-	 *
848
-	 * @param int    $errno    The level of the error raised
849
-	 * @param string $errmsg   The error message
850
-	 * @param string $filename The filename the error was raised in
851
-	 * @param int    $linenum  The line number the error was raised at
852
-	 * @param array  $vars     An array that points to the active symbol table where error occurred
853
-	 *
854
-	 * @return true
855
-	 * @throws \Exception
856
-	 * @access private
857
-	 */
858
-	public function handleErrors($errno, $errmsg, $filename, $linenum, $vars) {
859
-		$error = date("Y-m-d H:i:s (T)") . ": \"$errmsg\" in file $filename (line $linenum)";
860
-
861
-		$log = function ($message, $level) {
862
-			if (self::isCoreLoaded()) {
863
-				return elgg_log($message, $level);
864
-			}
865
-
866
-			return false;
867
-		};
868
-
869
-		switch ($errno) {
870
-			case E_USER_ERROR:
871
-				if (!$log("PHP: $error", 'ERROR')) {
872
-					error_log("PHP ERROR: $error");
873
-				}
874
-				if (self::isCoreLoaded()) {
875
-					register_error("ERROR: $error");
876
-				}
877
-
878
-				// Since this is a fatal error, we want to stop any further execution but do so gracefully.
879
-				throw new \Exception($error);
880
-				break;
881
-
882
-			case E_WARNING :
883
-			case E_USER_WARNING :
884
-			case E_RECOVERABLE_ERROR: // (e.g. type hint violation)
885
-
886
-				// check if the error wasn't suppressed by the error control operator (@)
887
-				if (error_reporting() && !$log("PHP: $error", 'WARNING')) {
888
-					error_log("PHP WARNING: $error");
889
-				}
890
-				break;
891
-
892
-			default:
893
-				if (function_exists('_elgg_config')) {
894
-					$debug = _elgg_config()->debug;
895
-				} else {
896
-					$debug = isset($GLOBALS['CONFIG']->debug) ? $GLOBALS['CONFIG']->debug : null;
897
-				}
898
-				if ($debug !== 'NOTICE') {
899
-					return true;
900
-				}
901
-
902
-				if (!$log("PHP (errno $errno): $error", 'NOTICE')) {
903
-					error_log("PHP NOTICE: $error");
904
-				}
905
-		}
906
-
907
-		return true;
908
-	}
909
-
910
-	/**
911
-	 * Does nothing.
912
-	 *
913
-	 * @return void
914
-	 * @deprecated
915
-	 */
916
-	public function loadSettings() {
917
-		trigger_error(__METHOD__ . ' is no longer needed and will be removed.');
918
-	}
919
-
920
-	/**
921
-	 * Get all engine/lib library filenames
922
-	 *
923
-	 * @note We can't just pull in all directory files because some users leave old files in place.
924
-	 *
925
-	 * @return string[]
926
-	 */
927
-	private static function getEngineLibs() {
928
-		return [
929
-			'access.php',
930
-			'actions.php',
931
-			'admin.php',
932
-			'annotations.php',
933
-			'autoloader.php',
934
-			'cache.php',
935
-			'comments.php',
936
-			'configuration.php',
937
-			'constants.php',
938
-			'cron.php',
939
-			'database.php',
940
-			'deprecated-3.0.php',
941
-			'elgglib.php',
942
-			'entities.php',
943
-			'filestore.php',
944
-			'group.php',
945
-			'input.php',
946
-			'languages.php',
947
-			'mb_wrapper.php',
948
-			'memcache.php',
949
-			'metadata.php',
950
-			'metastrings.php',
951
-			'navigation.php',
952
-			'notification.php',
953
-			'output.php',
954
-			'pagehandler.php',
955
-			'pageowner.php',
956
-			'pam.php',
957
-			'plugins.php',
958
-			'private_settings.php',
959
-			'relationships.php',
960
-			'river.php',
961
-			'sessions.php',
962
-			'statistics.php',
963
-			'system_log.php',
964
-			'tags.php',
965
-			'upgrade.php',
966
-			'user_settings.php',
967
-			'users.php',
968
-			'views.php',
969
-			'widgets.php',
970
-		];
971
-	}
31
+    const DEFAULT_LANG = 'en';
32
+    const DEFAULT_LIMIT = 10;
33
+
34
+    /**
35
+     * @var ServiceProvider
36
+     *
37
+     * @internal DO NOT USE
38
+     */
39
+    public $_services;
40
+
41
+    /**
42
+     * @var bool
43
+     */
44
+    private static $testing_app;
45
+
46
+    /**
47
+     * Property names of the service provider to be exposed via __get()
48
+     *
49
+     * E.g. the presence of `'foo' => true` in the list would allow _elgg_services()->foo to
50
+     * be accessed via elgg()->foo.
51
+     *
52
+     * @var string[]
53
+     */
54
+    private static $public_services = [
55
+        //'config' => true,
56
+        'menus' => true,
57
+        'table_columns' => true,
58
+    ];
59
+
60
+    /**
61
+     * Reference to the loaded Application returned by elgg()
62
+     *
63
+     * @internal Do not use this. use elgg() to access the application
64
+     * @access private
65
+     * @var Application
66
+     */
67
+    public static $_instance;
68
+
69
+    /**
70
+     * Constructor
71
+     *
72
+     * Upon construction, no actions are taken to load or boot Elgg.
73
+     *
74
+     * @param ServiceProvider $services Elgg services provider
75
+     * @throws ConfigurationException
76
+     */
77
+    public function __construct(ServiceProvider $services) {
78
+        $this->_services = $services;
79
+
80
+        $this->initConfig();
81
+    }
82
+
83
+    /**
84
+     * Validate, normalize, fill in missing values, and lock some
85
+     *
86
+     * @return void
87
+     * @throws ConfigurationException
88
+     */
89
+    private function initConfig() {
90
+        $config = $this->_services->config;
91
+
92
+        if ($config->elgg_config_locks === null) {
93
+            $config->elgg_config_locks = true;
94
+        }
95
+
96
+        if ($config->elgg_config_locks) {
97
+            $lock = function ($name) use ($config) {
98
+                $config->lock($name);
99
+            };
100
+        } else {
101
+            // the installer needs to build an application with defaults then update
102
+            // them after they're validated, so we don't want to lock them.
103
+            $lock = function () {
104
+            };
105
+        }
106
+
107
+        $this->_services->timer->begin([]);
108
+
109
+        // Until DB loads, let's log problems
110
+        if ($config->debug === null) {
111
+            $config->debug = 'NOTICE';
112
+        }
113
+
114
+        if ($config->dataroot) {
115
+            $config->dataroot = rtrim($config->dataroot, '\\/') . DIRECTORY_SEPARATOR;
116
+        } else {
117
+            if (!$config->installer_running) {
118
+                throw new ConfigurationException('Config value "dataroot" is required.');
119
+            }
120
+        }
121
+        $lock('dataroot');
122
+
123
+        if ($config->cacheroot) {
124
+            $config->cacheroot = rtrim($config->cacheroot, '\\/') . DIRECTORY_SEPARATOR;
125
+        } else {
126
+            $config->cacheroot = $config->dataroot;
127
+        }
128
+        $lock('cacheroot');
129
+
130
+        if ($config->wwwroot) {
131
+            $config->wwwroot = rtrim($config->wwwroot, '/') . '/';
132
+        } else {
133
+            $config->wwwroot = $this->_services->request->sniffElggUrl();
134
+        }
135
+        $lock('wwwroot');
136
+
137
+        if (!$config->language) {
138
+            $config->language = self::DEFAULT_LANG;
139
+        }
140
+
141
+        if ($config->default_limit) {
142
+            $lock('default_limit');
143
+        } else {
144
+            $config->default_limit = self::DEFAULT_LIMIT;
145
+        }
146
+
147
+        $locked_props = [
148
+            'site_guid' => 1,
149
+            'path' => Paths::project(),
150
+            'plugins_path' => Paths::project() . "mod/",
151
+            'pluginspath' => Paths::project() . "mod/",
152
+            'url' => $config->wwwroot,
153
+        ];
154
+        foreach ($locked_props as $name => $value) {
155
+            $config->$name = $value;
156
+            $lock($name);
157
+        }
158
+
159
+        // move sensitive credentials into isolated services
160
+        $this->_services->dbConfig;
161
+
162
+        // If the site secret is in the settings file, let's move it to that component
163
+        // right away to keep this value out of config.
164
+        $secret = SiteSecret::fromConfig($config);
165
+        if ($secret) {
166
+            $this->_services->setValue('siteSecret', $secret);
167
+            $config->elgg_config_set_secret = true;
168
+        }
169
+
170
+        $config->boot_complete = false;
171
+    }
172
+
173
+    /**
174
+     * Get the DB credentials.
175
+     *
176
+     * We no longer leave DB credentials in the config in case it gets accidentally dumped.
177
+     *
178
+     * @return \Elgg\Database\DbConfig
179
+     */
180
+    public function getDbConfig() {
181
+        return $this->_services->dbConfig;
182
+    }
183
+
184
+    /**
185
+     * Define all Elgg global functions and constants, wire up boot events, but don't boot
186
+     *
187
+     * This includes all the .php files in engine/lib (not upgrades). If a script returns a function,
188
+     * it is queued and executed at the end.
189
+     *
190
+     * @return void
191
+     * @access private
192
+     * @internal
193
+     * @throws \InstallationException
194
+     */
195
+    public function loadCore() {
196
+        if (self::isCoreLoaded()) {
197
+            return;
198
+        }
199
+
200
+        $setups = [];
201
+        $path = Paths::elgg() . 'engine/lib';
202
+
203
+        // include library files, capturing setup functions
204
+        foreach (self::getEngineLibs() as $file) {
205
+            $return = Includer::includeFile("$path/$file");
206
+            if (!$return) {
207
+                throw new \InstallationException("Elgg lib file failed include: engine/lib/$file");
208
+            }
209
+            if ($return instanceof \Closure) {
210
+                $setups[$file] = $return;
211
+            }
212
+        }
213
+
214
+        // store instance to be returned by elgg()
215
+        self::$_instance = $this;
216
+
217
+        $hooks = $this->_services->hooks;
218
+        $events = $hooks->getEvents();
219
+
220
+        // run setups
221
+        foreach ($setups as $func) {
222
+            $func($events, $hooks);
223
+        }
224
+    }
225
+
226
+    /**
227
+     * Start and boot the core
228
+     *
229
+     * @return self
230
+     */
231
+    public static function start() {
232
+        $app = self::factory();
233
+        $app->bootCore();
234
+        return $app;
235
+    }
236
+
237
+    /**
238
+     * Are Elgg's global functions loaded?
239
+     *
240
+     * @return bool
241
+     */
242
+    public static function isCoreLoaded() {
243
+        return function_exists('elgg');
244
+    }
245
+
246
+    /**
247
+     * Bootstrap the Elgg engine, loads plugins, and calls initial system events
248
+     *
249
+     * This method loads the full Elgg engine, checks the installation
250
+     * state, and triggers a series of events to finish booting Elgg:
251
+     * 	- {@elgg_event boot system}
252
+     * 	- {@elgg_event init system}
253
+     * 	- {@elgg_event ready system}
254
+     *
255
+     * If Elgg is not fully installed, the browser will be redirected to an installation page.
256
+     *
257
+     * @return void
258
+     */
259
+    public function bootCore() {
260
+        $config = $this->_services->config;
261
+
262
+        if ($this->isTestingApplication()) {
263
+            throw new \RuntimeException('Unit tests should not call ' . __METHOD__);
264
+        }
265
+
266
+        if ($config->boot_complete) {
267
+            return;
268
+        }
269
+
270
+        // in case not loaded already
271
+        $this->loadCore();
272
+
273
+        if (!$this->_services->db) {
274
+            // no database boot!
275
+            elgg_views_boot();
276
+            $this->_services->session->start();
277
+            $this->_services->translator->loadTranslations();
278
+
279
+            actions_init();
280
+            _elgg_init();
281
+            _elgg_input_init();
282
+            _elgg_nav_init();
283
+
284
+            $config->boot_complete = true;
285
+            $config->lock('boot_complete');
286
+            return;
287
+        }
288
+
289
+        // Connect to database, load language files, load configuration, init session
290
+        $this->_services->boot->boot($this->_services);
291
+
292
+        elgg_views_boot();
293
+
294
+        // Load the plugins that are active
295
+        $this->_services->plugins->load();
296
+
297
+        if (Paths::project() != Paths::elgg()) {
298
+            // Elgg is installed as a composer dep, so try to treat the root directory
299
+            // as a custom plugin that is always loaded last and can't be disabled...
300
+            if (!$config->system_cache_loaded) {
301
+                // configure view locations for the custom plugin (not Elgg core)
302
+                $viewsFile = Paths::project() . 'views.php';
303
+                if (is_file($viewsFile)) {
304
+                    $viewsSpec = Includer::includeFile($viewsFile);
305
+                    if (is_array($viewsSpec)) {
306
+                        $this->_services->views->mergeViewsSpec($viewsSpec);
307
+                    }
308
+                }
309
+
310
+                // find views for the custom plugin (not Elgg core)
311
+                $this->_services->views->registerPluginViews(Paths::project());
312
+            }
313
+
314
+            if (!$config->i18n_loaded_from_cache) {
315
+                $this->_services->translator->registerPluginTranslations(Paths::project());
316
+            }
317
+
318
+            // This is root directory start.php
319
+            $root_start = Paths::project() . "start.php";
320
+            if (is_file($root_start)) {
321
+                require $root_start;
322
+            }
323
+        }
324
+
325
+        // after plugins are started we know which viewtypes are populated
326
+        $this->_services->views->clampViewtypeToPopulatedViews();
327
+
328
+        $this->allowPathRewrite();
329
+
330
+        $events = $this->_services->hooks->getEvents();
331
+
332
+        // Allows registering handlers strictly before all init, system handlers
333
+        $events->trigger('plugins_boot', 'system');
334
+
335
+        // Complete the boot process for both engine and plugins
336
+        $events->trigger('init', 'system');
337
+
338
+        $config->boot_complete = true;
339
+        $config->lock('boot_complete');
340
+
341
+        // System loaded and ready
342
+        $events->trigger('ready', 'system');
343
+    }
344
+
345
+    /**
346
+     * Get a Database wrapper for performing queries without booting Elgg
347
+     *
348
+     * If settings has not been loaded, it will be loaded to configure the DB connection.
349
+     *
350
+     * @note Before boot, the Database instance will not yet be bound to a Logger.
351
+     *
352
+     * @return \Elgg\Application\Database
353
+     */
354
+    public function getDb() {
355
+        return $this->_services->publicDb;
356
+    }
357
+
358
+    /**
359
+     * Get an undefined property
360
+     *
361
+     * @param string $name The property name accessed
362
+     *
363
+     * @return mixed
364
+     */
365
+    public function __get($name) {
366
+        if (isset(self::$public_services[$name])) {
367
+            return $this->_services->{$name};
368
+        }
369
+        trigger_error("Undefined property: " . __CLASS__ . ":\${$name}");
370
+    }
371
+
372
+    /**
373
+     * Creates a new, trivial instance of Elgg\Application and set it as the singleton instance.
374
+     * If the singleton is already set, it's returned.
375
+     *
376
+     * @param array $spec Specification for initial call.
377
+     * @return self
378
+     * @throws ConfigurationException
379
+     */
380
+    public static function factory(array $spec = []) {
381
+        if (self::$_instance !== null) {
382
+            return self::$_instance;
383
+        }
384
+
385
+        $defaults = [
386
+            'service_provider' => null,
387
+            'config' => null,
388
+            'settings_path' => null,
389
+            'handle_exceptions' => true,
390
+            'handle_shutdown' => true,
391
+            'set_global_config' => true,
392
+            'set_start_time' => true,
393
+            'request' => null,
394
+            'set_global_instance' => true,
395
+        ];
396
+        $spec = array_merge($defaults, $spec);
397
+
398
+        if ($spec['set_start_time']) {
399
+            /**
400
+             * The time with microseconds when the Elgg engine was started.
401
+             *
402
+             * @global float
403
+             */
404
+            if (!isset($GLOBALS['START_MICROTIME'])) {
405
+                $GLOBALS['START_MICROTIME'] = microtime(true);
406
+            }
407
+        }
408
+
409
+        if (!$spec['service_provider']) {
410
+            if (!$spec['config']) {
411
+                $spec['config'] = Config::factory($spec['settings_path']);
412
+            }
413
+            $spec['service_provider'] = new ServiceProvider($spec['config']);
414
+        }
415
+
416
+        if ($spec['request']) {
417
+            if ($spec['request'] instanceof Request) {
418
+                $spec['service_provider']->setValue('request', $spec['request']);
419
+            } else {
420
+                throw new \InvalidArgumentException("Given request is not a " . Request::class);
421
+            }
422
+        }
423
+
424
+        $app = new self($spec['service_provider']);
425
+
426
+        if ($spec['handle_exceptions']) {
427
+            set_error_handler([$app, 'handleErrors']);
428
+            set_exception_handler([$app, 'handleExceptions']);
429
+        }
430
+
431
+        if ($spec['handle_shutdown']) {
432
+            // we need to register for shutdown before Symfony registers the
433
+            // session_write_close() function. https://github.com/Elgg/Elgg/issues/9243
434
+            register_shutdown_function(function () {
435
+                // There are cases where we may exit before this function is defined
436
+                if (function_exists('_elgg_shutdown_hook')) {
437
+                    _elgg_shutdown_hook();
438
+                }
439
+            });
440
+        }
441
+
442
+        if ($spec['set_global_config']) {
443
+            global $CONFIG;
444
+
445
+            // this will be buggy be at least PHP will log failures
446
+            $CONFIG = $spec['service_provider']->config;
447
+        }
448
+
449
+        if ($spec['set_global_instance']) {
450
+            self::$_instance = $app;
451
+        }
452
+
453
+        return $app;
454
+    }
455
+
456
+    /**
457
+     * Elgg's front controller. Handles basically all incoming URL requests.
458
+     *
459
+     * @return bool True if Elgg will handle the request, false if the server should (PHP-CLI server)
460
+     */
461
+    public static function index() {
462
+        $req = Request::createFromGlobals();
463
+        /** @var Request $req */
464
+
465
+        if ($req->isRewriteCheck()) {
466
+            echo Request::REWRITE_TEST_OUTPUT;
467
+            return true;
468
+        }
469
+
470
+        return self::factory(['request' => $req])->run();
471
+    }
472
+
473
+    /**
474
+     * Routes the request, booting core if not yet booted
475
+     *
476
+     * @return bool False if Elgg wants the PHP CLI server to handle the request
477
+     */
478
+    public function run() {
479
+        $config = $this->_services->config;
480
+        $request = $this->_services->request;
481
+
482
+        if ($request->isCliServer()) {
483
+            if ($request->isCliServable(Paths::project())) {
484
+                return false;
485
+            }
486
+
487
+            // overwrite value from settings
488
+            $www_root = rtrim($request->getSchemeAndHttpHost() . $request->getBaseUrl(), '/') . '/';
489
+            $config->wwwroot = $www_root;
490
+            $config->wwwroot_cli_server = $www_root;
491
+        }
492
+
493
+        if (0 === strpos($request->getElggPath(), '/cache/')) {
494
+            $this->_services->cacheHandler->handleRequest($request, $this)->prepare($request)->send();
495
+            return true;
496
+        }
497
+
498
+        if (0 === strpos($request->getElggPath(), '/serve-file/')) {
499
+            $this->_services->serveFileHandler->getResponse($request)->send();
500
+            return true;
501
+        }
502
+
503
+        $this->bootCore();
504
+
505
+        // TODO use formal Response object instead
506
+        // This is to set the charset to UTF-8.
507
+        header("Content-Type: text/html;charset=utf-8", true);
508
+
509
+        // re-fetch new request from services in case it was replaced by route:rewrite
510
+        $request = $this->_services->request;
511
+
512
+        if (!$this->_services->router->route($request)) {
513
+            forward('', '404');
514
+        }
515
+    }
516
+
517
+    /**
518
+     * Returns a directory that points to the root of Elgg, but not necessarily
519
+     * the install root. See `self::root()` for that.
520
+     *
521
+     * @return Directory
522
+     */
523
+    public static function elggDir() {
524
+        return Local::elggRoot();
525
+    }
526
+
527
+    /**
528
+     * Returns a directory that points to the project root, where composer is installed.
529
+     *
530
+     * @return Directory
531
+     */
532
+    public static function projectDir() {
533
+        return Local::projectRoot();
534
+    }
535
+
536
+    /**
537
+     * Renders a web UI for installing Elgg.
538
+     *
539
+     * @return void
540
+     */
541
+    public static function install() {
542
+        ini_set('display_errors', 1);
543
+        $installer = new \ElggInstaller();
544
+        $installer->run();
545
+    }
546
+
547
+    /**
548
+     * Elgg upgrade script.
549
+     *
550
+     * This script triggers any necessary upgrades. If the site has been upgraded
551
+     * to the most recent version of the code, no upgrades are run but the caches
552
+     * are flushed.
553
+     *
554
+     * Upgrades use a table {db_prefix}upgrade_lock as a mutex to prevent concurrent upgrades.
555
+     *
556
+     * The URL to forward to after upgrades are complete can be specified by setting $_GET['forward']
557
+     * to a relative URL.
558
+     *
559
+     * @return void
560
+     */
561
+    public static function upgrade() {
562
+        // we want to know if an error occurs
563
+        ini_set('display_errors', 1);
564
+        $is_cli = (php_sapi_name() === 'cli');
565
+
566
+        $forward = function ($url) use ($is_cli) {
567
+            if ($is_cli) {
568
+                fwrite(STDOUT, "Open $url in your browser to continue." . PHP_EOL);
569
+                return;
570
+            }
571
+
572
+            forward($url);
573
+        };
574
+
575
+        define('UPGRADING', 'upgrading');
576
+
577
+        self::migrate();
578
+        self::start();
579
+
580
+        // check security settings
581
+        if (!$is_cli && _elgg_config()->security_protect_upgrade && !elgg_is_admin_logged_in()) {
582
+            // only admin's or users with a valid token can run upgrade.php
583
+            elgg_signed_request_gatekeeper();
584
+        }
585
+
586
+        $site_url = _elgg_config()->url;
587
+        $site_host = parse_url($site_url, PHP_URL_HOST) . '/';
588
+
589
+        // turn any full in-site URLs into absolute paths
590
+        $forward_url = get_input('forward', '/admin', false);
591
+        $forward_url = str_replace([$site_url, $site_host], '/', $forward_url);
592
+
593
+        if (strpos($forward_url, '/') !== 0) {
594
+            $forward_url = '/' . $forward_url;
595
+        }
596
+
597
+        if ($is_cli || (get_input('upgrade') == 'upgrade')) {
598
+            $upgrader = _elgg_services()->upgrades;
599
+            $result = $upgrader->run();
600
+
601
+            if ($result['failure'] == true) {
602
+                register_error($result['reason']);
603
+                $forward($forward_url);
604
+            }
605
+
606
+            // Find unprocessed batch upgrade classes and save them as ElggUpgrade objects
607
+            $core_upgrades = (require self::elggDir()->getPath('engine/lib/upgrades/async-upgrades.php'));
608
+            $has_pending_upgrades = _elgg_services()->upgradeLocator->run($core_upgrades);
609
+
610
+            if ($has_pending_upgrades) {
611
+                // Forward to the list of pending upgrades
612
+                $forward_url = '/admin/upgrades';
613
+            }
614
+        } else {
615
+            $rewriteTester = new \ElggRewriteTester();
616
+            $url = elgg_get_site_url() . "__testing_rewrite?__testing_rewrite=1";
617
+            if (!$rewriteTester->runRewriteTest($url)) {
618
+                // see if there is a problem accessing the site at all
619
+                // due to ip restrictions for example
620
+                if (!$rewriteTester->runLocalhostAccessTest()) {
621
+                    // note: translation may not be available until after upgrade
622
+                    $msg = elgg_echo("installation:htaccess:localhost:connectionfailed");
623
+                    if ($msg === "installation:htaccess:localhost:connectionfailed") {
624
+                        $msg = "Elgg cannot connect to itself to test rewrite rules properly. Check "
625
+                                . "that curl is working and there are no IP restrictions preventing "
626
+                                . "localhost connections.";
627
+                    }
628
+                    echo $msg;
629
+                    exit;
630
+                }
631
+
632
+                // note: translation may not be available until after upgrade
633
+                $msg = elgg_echo("installation:htaccess:needs_upgrade");
634
+                if ($msg === "installation:htaccess:needs_upgrade") {
635
+                    $msg = "You must update your .htaccess file (use install/config/htaccess.dist as a guide).";
636
+                }
637
+                echo $msg;
638
+                exit;
639
+            }
640
+
641
+            $vars = [
642
+                'forward' => $forward_url
643
+            ];
644
+
645
+            // reset cache to have latest translations available during upgrade
646
+            elgg_reset_system_cache();
647
+
648
+            echo elgg_view_page(elgg_echo('upgrading'), '', 'upgrade', $vars);
649
+            exit;
650
+        }
651
+
652
+        $forward($forward_url);
653
+    }
654
+
655
+    /**
656
+     * Runs database migrations
657
+     *
658
+     * @throws InstallationException
659
+     * @return bool
660
+     */
661
+    public static function migrate() {
662
+        $conf = self::elggDir()->getPath('engine/schema/settings.php');
663
+        if (!$conf) {
664
+            throw new Exception('Settings file is required to run database migrations.');
665
+        }
666
+
667
+        $app = new \Phinx\Console\PhinxApplication();
668
+        $wrapper = new \Phinx\Wrapper\TextWrapper($app, [
669
+            'configuration' => $conf,
670
+        ]);
671
+        $log = $wrapper->getMigrate();
672
+        error_log($log);
673
+
674
+        return true;
675
+    }
676
+
677
+    /**
678
+     * Returns configuration array for database migrations
679
+     * @return array
680
+     */
681
+    public static function getMigrationSettings() {
682
+
683
+        $config = Config::factory();
684
+        $db_config = DbConfig::fromElggConfig($config);
685
+        if ($db_config->isDatabaseSplit()) {
686
+            $conn = $db_config->getConnectionConfig(DbConfig::WRITE);
687
+        } else {
688
+            $conn = $db_config->getConnectionConfig();
689
+        }
690
+
691
+        return [
692
+            "paths" => [
693
+                "migrations" => Paths::elgg() . 'engine/schema/migrations/',
694
+            ],
695
+            "environments" => [
696
+                "default_migration_table" => "{$conn['prefix']}migrations",
697
+                "default_database" => "prod",
698
+                "prod" => [
699
+                    "adapter" => "mysql",
700
+                    "host" => $conn['host'],
701
+                    "name" => $conn['database'],
702
+                    "user" => $conn['user'],
703
+                    "pass" => $conn['password'],
704
+                    "charset" => $conn['encoding'],
705
+                    "table_prefix" => $conn['prefix'],
706
+                ],
707
+            ],
708
+        ];
709
+    }
710
+
711
+    /**
712
+     * Allow plugins to rewrite the path.
713
+     *
714
+     * @return void
715
+     */
716
+    private function allowPathRewrite() {
717
+        $request = $this->_services->request;
718
+        $new = $this->_services->router->allowRewrite($request);
719
+        if ($new === $request) {
720
+            return;
721
+        }
722
+
723
+        $this->_services->setValue('request', $new);
724
+        $this->_services->context->initialize($new);
725
+    }
726
+
727
+    /**
728
+     * Flag this application as running for testing (PHPUnit)
729
+     *
730
+     * @param bool $testing Is testing application
731
+     * @return void
732
+     */
733
+    public static function setTestingApplication($testing = true) {
734
+        self::$testing_app = $testing;
735
+    }
736
+
737
+    /**
738
+     * Checks if the application is running in PHPUnit
739
+     * @return bool
740
+     */
741
+    public static function isTestingApplication() {
742
+        return (bool) self::$testing_app;
743
+    }
744
+
745
+    /**
746
+     * Intercepts, logs, and displays uncaught exceptions.
747
+     *
748
+     * To use a viewtype other than failsafe, create the views:
749
+     *  <viewtype>/messages/exceptions/admin_exception
750
+     *  <viewtype>/messages/exceptions/exception
751
+     * See the json viewtype for an example.
752
+     *
753
+     * @warning This function should never be called directly.
754
+     *
755
+     * @see http://www.php.net/set-exception-handler
756
+     *
757
+     * @param \Exception|\Error $exception The exception/error being handled
758
+     *
759
+     * @return void
760
+     * @access private
761
+     */
762
+    public function handleExceptions($exception) {
763
+        $timestamp = time();
764
+        error_log("Exception at time $timestamp: $exception");
765
+
766
+        // Wipe any existing output buffer
767
+        ob_end_clean();
768
+
769
+        // make sure the error isn't cached
770
+        header("Cache-Control: no-cache, must-revalidate", true);
771
+        header('Expires: Fri, 05 Feb 1982 00:00:00 -0500', true);
772
+
773
+        if ($exception instanceof \InstallationException) {
774
+            forward('/install.php');
775
+        }
776
+
777
+        if (!self::isCoreLoaded()) {
778
+            http_response_code(500);
779
+            echo "Exception loading Elgg core. Check log at time $timestamp";
780
+            return;
781
+        }
782
+
783
+        try {
784
+            // allow custom scripts to trigger on exception
785
+            // value in settings.php should be a system path to a file to include
786
+            $exception_include = $this->_services->config->exception_include;
787
+
788
+            if ($exception_include && is_file($exception_include)) {
789
+                ob_start();
790
+
791
+                // don't isolate, these scripts may use the local $exception var.
792
+                include $exception_include;
793
+
794
+                $exception_output = ob_get_clean();
795
+
796
+                // if content is returned from the custom handler we will output
797
+                // that instead of our default failsafe view
798
+                if (!empty($exception_output)) {
799
+                    echo $exception_output;
800
+                    exit;
801
+                }
802
+            }
803
+
804
+            if (elgg_is_xhr()) {
805
+                elgg_set_viewtype('json');
806
+                $response = new \Symfony\Component\HttpFoundation\JsonResponse(null, 500);
807
+            } else {
808
+                elgg_set_viewtype('failsafe');
809
+                $response = new \Symfony\Component\HttpFoundation\Response('', 500);
810
+            }
811
+
812
+            if (elgg_is_admin_logged_in()) {
813
+                $body = elgg_view("messages/exceptions/admin_exception", [
814
+                    'object' => $exception,
815
+                    'ts' => $timestamp
816
+                ]);
817
+            } else {
818
+                $body = elgg_view("messages/exceptions/exception", [
819
+                    'object' => $exception,
820
+                    'ts' => $timestamp
821
+                ]);
822
+            }
823
+
824
+            $response->setContent(elgg_view_page(elgg_echo('exception:title'), $body));
825
+            $response->send();
826
+        } catch (\Exception $e) {
827
+            $timestamp = time();
828
+            $message = $e->getMessage();
829
+            http_response_code(500);
830
+            echo "Fatal error in exception handler. Check log for Exception at time $timestamp";
831
+            error_log("Exception at time $timestamp : fatal error in exception handler : $message");
832
+        }
833
+    }
834
+
835
+    /**
836
+     * Intercepts catchable PHP errors.
837
+     *
838
+     * @warning This function should never be called directly.
839
+     *
840
+     * @internal
841
+     * For catchable fatal errors, throws an Exception with the error.
842
+     *
843
+     * For non-fatal errors, depending upon the debug settings, either
844
+     * log the error or ignore it.
845
+     *
846
+     * @see http://www.php.net/set-error-handler
847
+     *
848
+     * @param int    $errno    The level of the error raised
849
+     * @param string $errmsg   The error message
850
+     * @param string $filename The filename the error was raised in
851
+     * @param int    $linenum  The line number the error was raised at
852
+     * @param array  $vars     An array that points to the active symbol table where error occurred
853
+     *
854
+     * @return true
855
+     * @throws \Exception
856
+     * @access private
857
+     */
858
+    public function handleErrors($errno, $errmsg, $filename, $linenum, $vars) {
859
+        $error = date("Y-m-d H:i:s (T)") . ": \"$errmsg\" in file $filename (line $linenum)";
860
+
861
+        $log = function ($message, $level) {
862
+            if (self::isCoreLoaded()) {
863
+                return elgg_log($message, $level);
864
+            }
865
+
866
+            return false;
867
+        };
868
+
869
+        switch ($errno) {
870
+            case E_USER_ERROR:
871
+                if (!$log("PHP: $error", 'ERROR')) {
872
+                    error_log("PHP ERROR: $error");
873
+                }
874
+                if (self::isCoreLoaded()) {
875
+                    register_error("ERROR: $error");
876
+                }
877
+
878
+                // Since this is a fatal error, we want to stop any further execution but do so gracefully.
879
+                throw new \Exception($error);
880
+                break;
881
+
882
+            case E_WARNING :
883
+            case E_USER_WARNING :
884
+            case E_RECOVERABLE_ERROR: // (e.g. type hint violation)
885
+
886
+                // check if the error wasn't suppressed by the error control operator (@)
887
+                if (error_reporting() && !$log("PHP: $error", 'WARNING')) {
888
+                    error_log("PHP WARNING: $error");
889
+                }
890
+                break;
891
+
892
+            default:
893
+                if (function_exists('_elgg_config')) {
894
+                    $debug = _elgg_config()->debug;
895
+                } else {
896
+                    $debug = isset($GLOBALS['CONFIG']->debug) ? $GLOBALS['CONFIG']->debug : null;
897
+                }
898
+                if ($debug !== 'NOTICE') {
899
+                    return true;
900
+                }
901
+
902
+                if (!$log("PHP (errno $errno): $error", 'NOTICE')) {
903
+                    error_log("PHP NOTICE: $error");
904
+                }
905
+        }
906
+
907
+        return true;
908
+    }
909
+
910
+    /**
911
+     * Does nothing.
912
+     *
913
+     * @return void
914
+     * @deprecated
915
+     */
916
+    public function loadSettings() {
917
+        trigger_error(__METHOD__ . ' is no longer needed and will be removed.');
918
+    }
919
+
920
+    /**
921
+     * Get all engine/lib library filenames
922
+     *
923
+     * @note We can't just pull in all directory files because some users leave old files in place.
924
+     *
925
+     * @return string[]
926
+     */
927
+    private static function getEngineLibs() {
928
+        return [
929
+            'access.php',
930
+            'actions.php',
931
+            'admin.php',
932
+            'annotations.php',
933
+            'autoloader.php',
934
+            'cache.php',
935
+            'comments.php',
936
+            'configuration.php',
937
+            'constants.php',
938
+            'cron.php',
939
+            'database.php',
940
+            'deprecated-3.0.php',
941
+            'elgglib.php',
942
+            'entities.php',
943
+            'filestore.php',
944
+            'group.php',
945
+            'input.php',
946
+            'languages.php',
947
+            'mb_wrapper.php',
948
+            'memcache.php',
949
+            'metadata.php',
950
+            'metastrings.php',
951
+            'navigation.php',
952
+            'notification.php',
953
+            'output.php',
954
+            'pagehandler.php',
955
+            'pageowner.php',
956
+            'pam.php',
957
+            'plugins.php',
958
+            'private_settings.php',
959
+            'relationships.php',
960
+            'river.php',
961
+            'sessions.php',
962
+            'statistics.php',
963
+            'system_log.php',
964
+            'tags.php',
965
+            'upgrade.php',
966
+            'user_settings.php',
967
+            'users.php',
968
+            'views.php',
969
+            'widgets.php',
970
+        ];
971
+    }
972 972
 }
Please login to merge, or discard this patch.
engine/lib/autoloader.php 1 patch
Indentation   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -14,7 +14,7 @@  discard block
 block discarded – undo
14 14
  * @access private
15 15
  */
16 16
 function _elgg_services() {
17
-	return elgg()->_services;
17
+    return elgg()->_services;
18 18
 }
19 19
 
20 20
 /**
@@ -24,7 +24,7 @@  discard block
 block discarded – undo
24 24
  * @access private
25 25
  */
26 26
 function _elgg_config() {
27
-	return _elgg_services()->config;
27
+    return _elgg_services()->config;
28 28
 }
29 29
 
30 30
 /**
@@ -33,7 +33,7 @@  discard block
 block discarded – undo
33 33
  * @access private
34 34
  */
35 35
 function _elgg_delete_autoload_cache() {
36
-	_elgg_services()->autoloadManager->deleteCache();
36
+    _elgg_services()->autoloadManager->deleteCache();
37 37
 }
38 38
 
39 39
 
@@ -41,5 +41,5 @@  discard block
 block discarded – undo
41 41
  * @see \Elgg\Application::loadCore Do not do work here. Just register for events.
42 42
  */
43 43
 return function(\Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) {
44
-	$events->registerHandler('upgrade', 'all', '_elgg_delete_autoload_cache', 600);
44
+    $events->registerHandler('upgrade', 'all', '_elgg_delete_autoload_cache', 600);
45 45
 };
Please login to merge, or discard this patch.
engine/classes/Elgg/Di/ServiceProvider.php 1 patch
Indentation   +400 added lines, -400 removed lines patch added patch discarded remove patch
@@ -95,404 +95,404 @@
 block discarded – undo
95 95
  */
96 96
 class ServiceProvider extends DiContainer {
97 97
 
98
-	/**
99
-	 * Constructor
100
-	 *
101
-	 * @param Config $config Elgg Config service
102
-	 */
103
-	public function __construct(Config $config) {
104
-
105
-		$this->setFactory('autoloadManager', function(ServiceProvider $c) {
106
-			$manager = new \Elgg\AutoloadManager($c->classLoader);
107
-			if (!$c->config->AutoloaderManager_skip_storage) {
108
-				$manager->setStorage($c->fileCache);
109
-				$manager->loadCache();
110
-			}
111
-			return $manager;
112
-		});
113
-
114
-		$this->setFactory('accessCache', function(ServiceProvider $c) {
115
-			return new \ElggStaticVariableCache('access');
116
-		});
117
-
118
-		$this->setFactory('accessCollections', function(ServiceProvider $c) {
119
-			return new \Elgg\Database\AccessCollections(
120
-					$c->config, $c->db, $c->entityTable, $c->accessCache, $c->hooks, $c->session, $c->translator);
121
-		});
122
-
123
-		$this->setFactory('actions', function(ServiceProvider $c) {
124
-			return new \Elgg\ActionsService($c->config, $c->session, $c->crypto);
125
-		});
126
-
127
-		$this->setClassName('adminNotices', \Elgg\Database\AdminNotices::class);
128
-
129
-		$this->setFactory('ajax', function(ServiceProvider $c) {
130
-			return new \Elgg\Ajax\Service($c->hooks, $c->systemMessages, $c->input, $c->amdConfig);
131
-		});
132
-
133
-		$this->setFactory('amdConfig', function(ServiceProvider $c) {
134
-			$obj = new \Elgg\Amd\Config($c->hooks);
135
-			$obj->setBaseUrl($c->simpleCache->getRoot());
136
-			return $obj;
137
-		});
138
-
139
-		$this->setFactory('annotations', function(ServiceProvider $c) {
140
-			return new \Elgg\Database\Annotations($c->db, $c->session, $c->hooks->getEvents());
141
-		});
142
-
143
-		$this->setClassName('autoP', \ElggAutoP::class);
144
-
145
-		$this->setFactory('boot', function(ServiceProvider $c) {
146
-			$boot = new \Elgg\BootService();
147
-			if ($c->config->enable_profiling) {
148
-				$boot->setTimer($c->timer);
149
-			}
150
-			return $boot;
151
-		});
152
-
153
-		$this->setFactory('batchUpgrader', function(ServiceProvider $c) {
154
-			return new \Elgg\BatchUpgrader($c->config);
155
-		});
156
-
157
-		$this->setFactory('cacheHandler', function(ServiceProvider $c) {
158
-			$simplecache_enabled = $c->config->simplecache_enabled;
159
-			if ($simplecache_enabled === null) {
160
-				$simplecache_enabled = $c->configTable->get('simplecache_enabled');
161
-			}
162
-			return new \Elgg\Application\CacheHandler($c->config, $c->request, $simplecache_enabled);
163
-		});
164
-
165
-		$this->setFactory('classLoader', function(ServiceProvider $c) {
166
-			$loader = new \Elgg\ClassLoader(new \Elgg\ClassMap());
167
-			$loader->register();
168
-			return $loader;
169
-		});
170
-
171
-		$this->setValue('config', $config);
172
-
173
-		$this->setFactory('configTable', function(ServiceProvider $c) {
174
-			return new \Elgg\Database\ConfigTable($c->db, $c->boot, $c->logger);
175
-		});
176
-
177
-		$this->setFactory('context', function(ServiceProvider $c) {
178
-			$context = new \Elgg\Context();
179
-			$context->initialize($c->request);
180
-			return $context;
181
-		});
182
-
183
-		$this->setClassName('crypto', \ElggCrypto::class);
184
-
185
-		$this->setFactory('db', function(ServiceProvider $c) {
186
-			$db = new \Elgg\Database($c->dbConfig);
187
-			$db->setLogger($c->logger);
188
-
189
-			if ($c->config->profiling_sql) {
190
-				$db->setTimer($c->timer);
191
-			}
192
-
193
-			return $db;
194
-		});
195
-
196
-		$this->setFactory('dbConfig', function(ServiceProvider $c) {
197
-			$config = $c->config;
198
-			$db_config = \Elgg\Database\DbConfig::fromElggConfig($config);
199
-
200
-			// get this stuff out of config!
201
-			unset($config->db);
202
-			unset($config->dbname);
203
-			unset($config->dbhost);
204
-			unset($config->dbuser);
205
-			unset($config->dbpass);
206
-
207
-			return $db_config;
208
-		});
209
-
210
-		$this->setFactory('deprecation', function(ServiceProvider $c) {
211
-			return new \Elgg\DeprecationService($c->logger);
212
-		});
213
-
214
-		$this->setFactory('emails', function(ServiceProvider $c) {
215
-			return new \Elgg\EmailService($c->config, $c->hooks, $c->mailer, $c->logger);
216
-		});
217
-
218
-		$this->setFactory('entityCache', function(ServiceProvider $c) {
219
-			return new \Elgg\Cache\EntityCache($c->session, $c->metadataCache);
220
-		});
221
-
222
-		$this->setFactory('entityPreloader', function(ServiceProvider $c) {
223
-			return new \Elgg\EntityPreloader($c->entityCache, $c->entityTable);
224
-		});
225
-
226
-		$this->setFactory('entityTable', function(ServiceProvider $c) {
227
-			return new \Elgg\Database\EntityTable(
228
-				$c->config,
229
-				$c->db,
230
-				$c->entityCache,
231
-				$c->metadataCache,
232
-				$c->subtypeTable,
233
-				$c->hooks->getEvents(),
234
-				$c->session,
235
-				$c->translator,
236
-				$c->logger
237
-			);
238
-		});
239
-
240
-		$this->setClassName('externalFiles', \Elgg\Assets\ExternalFiles::class);
241
-
242
-		$this->setFactory('fileCache', function(ServiceProvider $c) {
243
-			return new \ElggFileCache($c->config->cacheroot . 'system_cache/');
244
-		});
245
-
246
-		$this->setFactory('filestore', function(ServiceProvider $c) {
247
-			return new \ElggDiskFilestore($c->config->dataroot);
248
-		});
249
-
250
-		$this->setFactory('forms', function(ServiceProvider $c) {
251
-			return new \Elgg\FormsService($c->views, $c->logger);
252
-		});
253
-
254
-		$this->setClassName('handlers', \Elgg\HandlersService::class);
255
-
256
-		$this->setFactory('hmac', function(ServiceProvider $c) {
257
-			return new \Elgg\Security\HmacFactory($c->siteSecret, $c->crypto);
258
-		});
259
-
260
-		$this->setFactory('hooks', function(ServiceProvider $c) {
261
-			$events = new \Elgg\EventsService($c->handlers);
262
-			if ($c->config->enable_profiling) {
263
-				$events->setTimer($c->timer);
264
-			}
265
-			return new \Elgg\PluginHooksService($events);
266
-		});
267
-
268
-		$this->setFactory('iconService', function(ServiceProvider $c) {
269
-			return new \Elgg\EntityIconService($c->config, $c->hooks, $c->request, $c->logger, $c->entityTable);
270
-		});
271
-
272
-		$this->setClassName('input', \Elgg\Http\Input::class);
273
-
274
-		$this->setFactory('imageService', function(ServiceProvider $c) {
275
-			$imagine = new \Imagine\Gd\Imagine();
276
-			return new \Elgg\ImageService($imagine, $c->config);
277
-		});
278
-
279
-		$this->setFactory('logger', function (ServiceProvider $c) {
280
-			$logger = new \Elgg\Logger($c->hooks, $c->context);
281
-			$logger->setLevel($c->config->debug);
282
-			return $logger;
283
-		});
284
-
285
-		// TODO(evan): Support configurable transports...
286
-		$this->setClassName('mailer', 'Zend\Mail\Transport\Sendmail');
287
-
288
-		$this->setFactory('menus', function(ServiceProvider $c) {
289
-			return new \Elgg\Menu\Service($c->hooks, $c->config);
290
-		});
291
-
292
-		$this->setFactory('metadataCache', function (ServiceProvider $c) {
293
-			$cache = _elgg_get_memcache('metadata');
294
-			return new \Elgg\Cache\MetadataCache($cache);
295
-		});
296
-
297
-		$this->setFactory('memcacheStashPool', function(ServiceProvider $c) {
298
-			if (!$c->config->memcache) {
299
-				return null;
300
-			}
301
-
302
-			$servers = $c->config->memcache_servers;
303
-			if (!$servers) {
304
-				return null;
305
-			}
306
-			$driver = new \Stash\Driver\Memcache([
307
-				'servers' => $servers,
308
-			]);
309
-			return new \Stash\Pool($driver);
310
-		});
311
-
312
-		$this->setFactory('metadataTable', function(ServiceProvider $c) {
313
-			// TODO(ewinslow): Use Pool instead of MetadataCache for caching
314
-			return new \Elgg\Database\MetadataTable(
315
-				$c->metadataCache, $c->db, $c->entityTable, $c->hooks->getEvents(), $c->session);
316
-		});
317
-
318
-		$this->setFactory('mutex', function(ServiceProvider $c) {
319
-			return new \Elgg\Database\Mutex(
320
-				$c->db,
321
-				$c->logger
322
-			);
323
-		});
324
-
325
-		$this->setFactory('notifications', function(ServiceProvider $c) {
326
-			// @todo move queue in service provider
327
-			$queue_name = \Elgg\Notifications\NotificationsService::QUEUE_NAME;
328
-			$queue = new \Elgg\Queue\DatabaseQueue($queue_name, $c->db);
329
-			$sub = new \Elgg\Notifications\SubscriptionsService($c->db);
330
-			return new \Elgg\Notifications\NotificationsService($sub, $queue, $c->hooks, $c->session, $c->translator, $c->entityTable, $c->logger);
331
-		});
332
-
333
-		$this->setClassName('nullCache', \Elgg\Cache\NullCache::class);
334
-
335
-		$this->setFactory('persistentLogin', function(ServiceProvider $c) {
336
-			$global_cookies_config = $c->config->getCookieConfig();
337
-			$cookie_config = $global_cookies_config['remember_me'];
338
-			$cookie_name = $cookie_config['name'];
339
-			$cookie_token = $c->request->cookies->get($cookie_name, '');
340
-			return new \Elgg\PersistentLoginService(
341
-				$c->db, $c->session, $c->crypto, $cookie_config, $cookie_token);
342
-		});
343
-
344
-		$this->setClassName('passwords', \Elgg\PasswordService::class);
345
-
346
-		$this->setFactory('plugins', function(ServiceProvider $c) {
347
-			$pool = new Pool\InMemory();
348
-			$plugins = new \Elgg\Database\Plugins($pool, $c->pluginSettingsCache);
349
-			if ($c->config->enable_profiling) {
350
-				$plugins->setTimer($c->timer);
351
-			}
352
-			return $plugins;
353
-		});
354
-
355
-		$this->setClassName('pluginSettingsCache', \Elgg\Cache\PluginSettingsCache::class);
356
-
357
-		$this->setFactory('privateSettings', function(ServiceProvider $c) {
358
-			return new \Elgg\Database\PrivateSettingsTable($c->db, $c->entityTable, $c->pluginSettingsCache);
359
-		});
360
-
361
-		$this->setFactory('publicDb', function(ServiceProvider $c) {
362
-			return new \Elgg\Application\Database($c->db);
363
-		});
364
-
365
-		$this->setFactory('queryCounter', function(ServiceProvider $c) {
366
-			return new \Elgg\Database\QueryCounter($c->db);
367
-		}, false);
368
-
369
-		$this->setFactory('redirects', function(ServiceProvider $c) {
370
-			$url = current_page_url();
371
-			$is_xhr = $c->request->isXmlHttpRequest();
372
-			return new \Elgg\RedirectService($c->session, $is_xhr, $c->config->wwwroot, $url);
373
-		});
374
-
375
-		$this->setFactory('relationshipsTable', function(ServiceProvider $c) {
376
-			return new \Elgg\Database\RelationshipsTable($c->db, $c->entityTable, $c->metadataTable, $c->hooks->getEvents());
377
-		});
378
-
379
-		$this->setFactory('request', [\Elgg\Http\Request::class, 'createFromGlobals']);
380
-
381
-		$this->setFactory('responseFactory', function(ServiceProvider $c) {
382
-			if (PHP_SAPI === 'cli') {
383
-				$transport = new \Elgg\Http\OutputBufferTransport();
384
-			} else {
385
-				$transport = new \Elgg\Http\HttpProtocolTransport();
386
-			}
387
-			return new \Elgg\Http\ResponseFactory($c->request, $c->hooks, $c->ajax, $transport);
388
-		});
389
-
390
-		$this->setFactory('router', function(ServiceProvider $c) {
391
-			// TODO(evan): Init routes from plugins or cache
392
-			$router = new \Elgg\Router($c->hooks);
393
-			if ($c->config->enable_profiling) {
394
-				$router->setTimer($c->timer);
395
-			}
396
-			return $router;
397
-		});
398
-
399
-		$this->setFactory('seeder', function(ServiceProvider $c) {
400
-			return new \Elgg\Database\Seeder($c->hooks);
401
-		});
402
-
403
-		$this->setFactory('serveFileHandler', function(ServiceProvider $c) {
404
-			return new \Elgg\Application\ServeFileHandler($c->hmac, $c->config);
405
-		});
406
-
407
-		$this->setFactory('session', function(ServiceProvider $c) {
408
-			return \ElggSession::fromDatabase($c->config, $c->db);
409
-		});
410
-
411
-		$this->setClassName('urlSigner', \Elgg\Security\UrlSigner::class);
412
-
413
-		$this->setFactory('simpleCache', function(ServiceProvider $c) {
414
-			return new \Elgg\Cache\SimpleCache($c->config);
415
-		});
416
-
417
-		/**
418
-		 * If the key is in the settings file, this is injected early.
419
-		 *
420
-		 * @see \Elgg\Application::initConfig
421
-		 */
422
-		$this->setFactory('siteSecret', function(ServiceProvider $c) {
423
-			return \Elgg\Database\SiteSecret::fromDatabase($c->configTable);
424
-		});
425
-
426
-		$this->setClassName('stickyForms', \Elgg\Forms\StickyForms::class);
427
-
428
-		$this->setFactory('subtypeTable', function(ServiceProvider $c) {
429
-			return new \Elgg\Database\SubtypeTable($c->db);
430
-		});
431
-
432
-		$this->setFactory('systemCache', function (ServiceProvider $c) {
433
-			$cache = new \Elgg\Cache\SystemCache($c->fileCache, $c->config);
434
-			if ($c->config->enable_profiling) {
435
-				$cache->setTimer($c->timer);
436
-			}
437
-			return $cache;
438
-		});
439
-
440
-		$this->setFactory('systemMessages', function(ServiceProvider $c) {
441
-			return new \Elgg\SystemMessagesService($c->session);
442
-		});
443
-
444
-		$this->setClassName('table_columns', \Elgg\Views\TableColumn\ColumnFactory::class);
445
-
446
-		$this->setClassName('timer', \Elgg\Timer::class);
447
-
448
-		$this->setFactory('translator', function(ServiceProvider $c) {
449
-			return new \Elgg\I18n\Translator($c->config);
450
-		});
451
-
452
-		$this->setFactory('uploads', function(ServiceProvider $c) {
453
-			return new \Elgg\UploadService($c->request);
454
-		});
455
-
456
-		$this->setFactory('upgrades', function(ServiceProvider $c) {
457
-			return new \Elgg\UpgradeService(
458
-				$c->translator,
459
-				$c->hooks,
460
-				$c->config,
461
-				$c->logger,
462
-				$c->mutex
463
-			);
464
-		});
465
-
466
-		$this->setFactory('userCapabilities', function(ServiceProvider $c) {
467
-			return new \Elgg\UserCapabilities($c->hooks, $c->entityTable, $c->session);
468
-		});
469
-
470
-		$this->setFactory('usersTable', function(ServiceProvider $c) {
471
-			return new \Elgg\Database\UsersTable(
472
-				$c->config,
473
-				$c->db,
474
-				$c->entityTable,
475
-				$c->entityCache,
476
-				$c->hooks->getEvents()
477
-			);
478
-		});
479
-
480
-		$this->setFactory('upgradeLocator', function(ServiceProvider $c) {
481
-			return new \Elgg\Upgrade\Locator(
482
-				$c->plugins,
483
-				$c->logger,
484
-				$c->privateSettings
485
-			);
486
-		});
487
-
488
-		$this->setFactory('views', function(ServiceProvider $c) {
489
-			return new \Elgg\ViewsService($c->hooks, $c->logger, $c->input);
490
-		});
491
-
492
-		$this->setFactory('viewCacher', function(ServiceProvider $c) {
493
-			return new \Elgg\Cache\ViewCacher($c->views, $c->config);
494
-		});
495
-
496
-		$this->setClassName('widgets', \Elgg\WidgetsService::class);
497
-	}
98
+    /**
99
+     * Constructor
100
+     *
101
+     * @param Config $config Elgg Config service
102
+     */
103
+    public function __construct(Config $config) {
104
+
105
+        $this->setFactory('autoloadManager', function(ServiceProvider $c) {
106
+            $manager = new \Elgg\AutoloadManager($c->classLoader);
107
+            if (!$c->config->AutoloaderManager_skip_storage) {
108
+                $manager->setStorage($c->fileCache);
109
+                $manager->loadCache();
110
+            }
111
+            return $manager;
112
+        });
113
+
114
+        $this->setFactory('accessCache', function(ServiceProvider $c) {
115
+            return new \ElggStaticVariableCache('access');
116
+        });
117
+
118
+        $this->setFactory('accessCollections', function(ServiceProvider $c) {
119
+            return new \Elgg\Database\AccessCollections(
120
+                    $c->config, $c->db, $c->entityTable, $c->accessCache, $c->hooks, $c->session, $c->translator);
121
+        });
122
+
123
+        $this->setFactory('actions', function(ServiceProvider $c) {
124
+            return new \Elgg\ActionsService($c->config, $c->session, $c->crypto);
125
+        });
126
+
127
+        $this->setClassName('adminNotices', \Elgg\Database\AdminNotices::class);
128
+
129
+        $this->setFactory('ajax', function(ServiceProvider $c) {
130
+            return new \Elgg\Ajax\Service($c->hooks, $c->systemMessages, $c->input, $c->amdConfig);
131
+        });
132
+
133
+        $this->setFactory('amdConfig', function(ServiceProvider $c) {
134
+            $obj = new \Elgg\Amd\Config($c->hooks);
135
+            $obj->setBaseUrl($c->simpleCache->getRoot());
136
+            return $obj;
137
+        });
138
+
139
+        $this->setFactory('annotations', function(ServiceProvider $c) {
140
+            return new \Elgg\Database\Annotations($c->db, $c->session, $c->hooks->getEvents());
141
+        });
142
+
143
+        $this->setClassName('autoP', \ElggAutoP::class);
144
+
145
+        $this->setFactory('boot', function(ServiceProvider $c) {
146
+            $boot = new \Elgg\BootService();
147
+            if ($c->config->enable_profiling) {
148
+                $boot->setTimer($c->timer);
149
+            }
150
+            return $boot;
151
+        });
152
+
153
+        $this->setFactory('batchUpgrader', function(ServiceProvider $c) {
154
+            return new \Elgg\BatchUpgrader($c->config);
155
+        });
156
+
157
+        $this->setFactory('cacheHandler', function(ServiceProvider $c) {
158
+            $simplecache_enabled = $c->config->simplecache_enabled;
159
+            if ($simplecache_enabled === null) {
160
+                $simplecache_enabled = $c->configTable->get('simplecache_enabled');
161
+            }
162
+            return new \Elgg\Application\CacheHandler($c->config, $c->request, $simplecache_enabled);
163
+        });
164
+
165
+        $this->setFactory('classLoader', function(ServiceProvider $c) {
166
+            $loader = new \Elgg\ClassLoader(new \Elgg\ClassMap());
167
+            $loader->register();
168
+            return $loader;
169
+        });
170
+
171
+        $this->setValue('config', $config);
172
+
173
+        $this->setFactory('configTable', function(ServiceProvider $c) {
174
+            return new \Elgg\Database\ConfigTable($c->db, $c->boot, $c->logger);
175
+        });
176
+
177
+        $this->setFactory('context', function(ServiceProvider $c) {
178
+            $context = new \Elgg\Context();
179
+            $context->initialize($c->request);
180
+            return $context;
181
+        });
182
+
183
+        $this->setClassName('crypto', \ElggCrypto::class);
184
+
185
+        $this->setFactory('db', function(ServiceProvider $c) {
186
+            $db = new \Elgg\Database($c->dbConfig);
187
+            $db->setLogger($c->logger);
188
+
189
+            if ($c->config->profiling_sql) {
190
+                $db->setTimer($c->timer);
191
+            }
192
+
193
+            return $db;
194
+        });
195
+
196
+        $this->setFactory('dbConfig', function(ServiceProvider $c) {
197
+            $config = $c->config;
198
+            $db_config = \Elgg\Database\DbConfig::fromElggConfig($config);
199
+
200
+            // get this stuff out of config!
201
+            unset($config->db);
202
+            unset($config->dbname);
203
+            unset($config->dbhost);
204
+            unset($config->dbuser);
205
+            unset($config->dbpass);
206
+
207
+            return $db_config;
208
+        });
209
+
210
+        $this->setFactory('deprecation', function(ServiceProvider $c) {
211
+            return new \Elgg\DeprecationService($c->logger);
212
+        });
213
+
214
+        $this->setFactory('emails', function(ServiceProvider $c) {
215
+            return new \Elgg\EmailService($c->config, $c->hooks, $c->mailer, $c->logger);
216
+        });
217
+
218
+        $this->setFactory('entityCache', function(ServiceProvider $c) {
219
+            return new \Elgg\Cache\EntityCache($c->session, $c->metadataCache);
220
+        });
221
+
222
+        $this->setFactory('entityPreloader', function(ServiceProvider $c) {
223
+            return new \Elgg\EntityPreloader($c->entityCache, $c->entityTable);
224
+        });
225
+
226
+        $this->setFactory('entityTable', function(ServiceProvider $c) {
227
+            return new \Elgg\Database\EntityTable(
228
+                $c->config,
229
+                $c->db,
230
+                $c->entityCache,
231
+                $c->metadataCache,
232
+                $c->subtypeTable,
233
+                $c->hooks->getEvents(),
234
+                $c->session,
235
+                $c->translator,
236
+                $c->logger
237
+            );
238
+        });
239
+
240
+        $this->setClassName('externalFiles', \Elgg\Assets\ExternalFiles::class);
241
+
242
+        $this->setFactory('fileCache', function(ServiceProvider $c) {
243
+            return new \ElggFileCache($c->config->cacheroot . 'system_cache/');
244
+        });
245
+
246
+        $this->setFactory('filestore', function(ServiceProvider $c) {
247
+            return new \ElggDiskFilestore($c->config->dataroot);
248
+        });
249
+
250
+        $this->setFactory('forms', function(ServiceProvider $c) {
251
+            return new \Elgg\FormsService($c->views, $c->logger);
252
+        });
253
+
254
+        $this->setClassName('handlers', \Elgg\HandlersService::class);
255
+
256
+        $this->setFactory('hmac', function(ServiceProvider $c) {
257
+            return new \Elgg\Security\HmacFactory($c->siteSecret, $c->crypto);
258
+        });
259
+
260
+        $this->setFactory('hooks', function(ServiceProvider $c) {
261
+            $events = new \Elgg\EventsService($c->handlers);
262
+            if ($c->config->enable_profiling) {
263
+                $events->setTimer($c->timer);
264
+            }
265
+            return new \Elgg\PluginHooksService($events);
266
+        });
267
+
268
+        $this->setFactory('iconService', function(ServiceProvider $c) {
269
+            return new \Elgg\EntityIconService($c->config, $c->hooks, $c->request, $c->logger, $c->entityTable);
270
+        });
271
+
272
+        $this->setClassName('input', \Elgg\Http\Input::class);
273
+
274
+        $this->setFactory('imageService', function(ServiceProvider $c) {
275
+            $imagine = new \Imagine\Gd\Imagine();
276
+            return new \Elgg\ImageService($imagine, $c->config);
277
+        });
278
+
279
+        $this->setFactory('logger', function (ServiceProvider $c) {
280
+            $logger = new \Elgg\Logger($c->hooks, $c->context);
281
+            $logger->setLevel($c->config->debug);
282
+            return $logger;
283
+        });
284
+
285
+        // TODO(evan): Support configurable transports...
286
+        $this->setClassName('mailer', 'Zend\Mail\Transport\Sendmail');
287
+
288
+        $this->setFactory('menus', function(ServiceProvider $c) {
289
+            return new \Elgg\Menu\Service($c->hooks, $c->config);
290
+        });
291
+
292
+        $this->setFactory('metadataCache', function (ServiceProvider $c) {
293
+            $cache = _elgg_get_memcache('metadata');
294
+            return new \Elgg\Cache\MetadataCache($cache);
295
+        });
296
+
297
+        $this->setFactory('memcacheStashPool', function(ServiceProvider $c) {
298
+            if (!$c->config->memcache) {
299
+                return null;
300
+            }
301
+
302
+            $servers = $c->config->memcache_servers;
303
+            if (!$servers) {
304
+                return null;
305
+            }
306
+            $driver = new \Stash\Driver\Memcache([
307
+                'servers' => $servers,
308
+            ]);
309
+            return new \Stash\Pool($driver);
310
+        });
311
+
312
+        $this->setFactory('metadataTable', function(ServiceProvider $c) {
313
+            // TODO(ewinslow): Use Pool instead of MetadataCache for caching
314
+            return new \Elgg\Database\MetadataTable(
315
+                $c->metadataCache, $c->db, $c->entityTable, $c->hooks->getEvents(), $c->session);
316
+        });
317
+
318
+        $this->setFactory('mutex', function(ServiceProvider $c) {
319
+            return new \Elgg\Database\Mutex(
320
+                $c->db,
321
+                $c->logger
322
+            );
323
+        });
324
+
325
+        $this->setFactory('notifications', function(ServiceProvider $c) {
326
+            // @todo move queue in service provider
327
+            $queue_name = \Elgg\Notifications\NotificationsService::QUEUE_NAME;
328
+            $queue = new \Elgg\Queue\DatabaseQueue($queue_name, $c->db);
329
+            $sub = new \Elgg\Notifications\SubscriptionsService($c->db);
330
+            return new \Elgg\Notifications\NotificationsService($sub, $queue, $c->hooks, $c->session, $c->translator, $c->entityTable, $c->logger);
331
+        });
332
+
333
+        $this->setClassName('nullCache', \Elgg\Cache\NullCache::class);
334
+
335
+        $this->setFactory('persistentLogin', function(ServiceProvider $c) {
336
+            $global_cookies_config = $c->config->getCookieConfig();
337
+            $cookie_config = $global_cookies_config['remember_me'];
338
+            $cookie_name = $cookie_config['name'];
339
+            $cookie_token = $c->request->cookies->get($cookie_name, '');
340
+            return new \Elgg\PersistentLoginService(
341
+                $c->db, $c->session, $c->crypto, $cookie_config, $cookie_token);
342
+        });
343
+
344
+        $this->setClassName('passwords', \Elgg\PasswordService::class);
345
+
346
+        $this->setFactory('plugins', function(ServiceProvider $c) {
347
+            $pool = new Pool\InMemory();
348
+            $plugins = new \Elgg\Database\Plugins($pool, $c->pluginSettingsCache);
349
+            if ($c->config->enable_profiling) {
350
+                $plugins->setTimer($c->timer);
351
+            }
352
+            return $plugins;
353
+        });
354
+
355
+        $this->setClassName('pluginSettingsCache', \Elgg\Cache\PluginSettingsCache::class);
356
+
357
+        $this->setFactory('privateSettings', function(ServiceProvider $c) {
358
+            return new \Elgg\Database\PrivateSettingsTable($c->db, $c->entityTable, $c->pluginSettingsCache);
359
+        });
360
+
361
+        $this->setFactory('publicDb', function(ServiceProvider $c) {
362
+            return new \Elgg\Application\Database($c->db);
363
+        });
364
+
365
+        $this->setFactory('queryCounter', function(ServiceProvider $c) {
366
+            return new \Elgg\Database\QueryCounter($c->db);
367
+        }, false);
368
+
369
+        $this->setFactory('redirects', function(ServiceProvider $c) {
370
+            $url = current_page_url();
371
+            $is_xhr = $c->request->isXmlHttpRequest();
372
+            return new \Elgg\RedirectService($c->session, $is_xhr, $c->config->wwwroot, $url);
373
+        });
374
+
375
+        $this->setFactory('relationshipsTable', function(ServiceProvider $c) {
376
+            return new \Elgg\Database\RelationshipsTable($c->db, $c->entityTable, $c->metadataTable, $c->hooks->getEvents());
377
+        });
378
+
379
+        $this->setFactory('request', [\Elgg\Http\Request::class, 'createFromGlobals']);
380
+
381
+        $this->setFactory('responseFactory', function(ServiceProvider $c) {
382
+            if (PHP_SAPI === 'cli') {
383
+                $transport = new \Elgg\Http\OutputBufferTransport();
384
+            } else {
385
+                $transport = new \Elgg\Http\HttpProtocolTransport();
386
+            }
387
+            return new \Elgg\Http\ResponseFactory($c->request, $c->hooks, $c->ajax, $transport);
388
+        });
389
+
390
+        $this->setFactory('router', function(ServiceProvider $c) {
391
+            // TODO(evan): Init routes from plugins or cache
392
+            $router = new \Elgg\Router($c->hooks);
393
+            if ($c->config->enable_profiling) {
394
+                $router->setTimer($c->timer);
395
+            }
396
+            return $router;
397
+        });
398
+
399
+        $this->setFactory('seeder', function(ServiceProvider $c) {
400
+            return new \Elgg\Database\Seeder($c->hooks);
401
+        });
402
+
403
+        $this->setFactory('serveFileHandler', function(ServiceProvider $c) {
404
+            return new \Elgg\Application\ServeFileHandler($c->hmac, $c->config);
405
+        });
406
+
407
+        $this->setFactory('session', function(ServiceProvider $c) {
408
+            return \ElggSession::fromDatabase($c->config, $c->db);
409
+        });
410
+
411
+        $this->setClassName('urlSigner', \Elgg\Security\UrlSigner::class);
412
+
413
+        $this->setFactory('simpleCache', function(ServiceProvider $c) {
414
+            return new \Elgg\Cache\SimpleCache($c->config);
415
+        });
416
+
417
+        /**
418
+         * If the key is in the settings file, this is injected early.
419
+         *
420
+         * @see \Elgg\Application::initConfig
421
+         */
422
+        $this->setFactory('siteSecret', function(ServiceProvider $c) {
423
+            return \Elgg\Database\SiteSecret::fromDatabase($c->configTable);
424
+        });
425
+
426
+        $this->setClassName('stickyForms', \Elgg\Forms\StickyForms::class);
427
+
428
+        $this->setFactory('subtypeTable', function(ServiceProvider $c) {
429
+            return new \Elgg\Database\SubtypeTable($c->db);
430
+        });
431
+
432
+        $this->setFactory('systemCache', function (ServiceProvider $c) {
433
+            $cache = new \Elgg\Cache\SystemCache($c->fileCache, $c->config);
434
+            if ($c->config->enable_profiling) {
435
+                $cache->setTimer($c->timer);
436
+            }
437
+            return $cache;
438
+        });
439
+
440
+        $this->setFactory('systemMessages', function(ServiceProvider $c) {
441
+            return new \Elgg\SystemMessagesService($c->session);
442
+        });
443
+
444
+        $this->setClassName('table_columns', \Elgg\Views\TableColumn\ColumnFactory::class);
445
+
446
+        $this->setClassName('timer', \Elgg\Timer::class);
447
+
448
+        $this->setFactory('translator', function(ServiceProvider $c) {
449
+            return new \Elgg\I18n\Translator($c->config);
450
+        });
451
+
452
+        $this->setFactory('uploads', function(ServiceProvider $c) {
453
+            return new \Elgg\UploadService($c->request);
454
+        });
455
+
456
+        $this->setFactory('upgrades', function(ServiceProvider $c) {
457
+            return new \Elgg\UpgradeService(
458
+                $c->translator,
459
+                $c->hooks,
460
+                $c->config,
461
+                $c->logger,
462
+                $c->mutex
463
+            );
464
+        });
465
+
466
+        $this->setFactory('userCapabilities', function(ServiceProvider $c) {
467
+            return new \Elgg\UserCapabilities($c->hooks, $c->entityTable, $c->session);
468
+        });
469
+
470
+        $this->setFactory('usersTable', function(ServiceProvider $c) {
471
+            return new \Elgg\Database\UsersTable(
472
+                $c->config,
473
+                $c->db,
474
+                $c->entityTable,
475
+                $c->entityCache,
476
+                $c->hooks->getEvents()
477
+            );
478
+        });
479
+
480
+        $this->setFactory('upgradeLocator', function(ServiceProvider $c) {
481
+            return new \Elgg\Upgrade\Locator(
482
+                $c->plugins,
483
+                $c->logger,
484
+                $c->privateSettings
485
+            );
486
+        });
487
+
488
+        $this->setFactory('views', function(ServiceProvider $c) {
489
+            return new \Elgg\ViewsService($c->hooks, $c->logger, $c->input);
490
+        });
491
+
492
+        $this->setFactory('viewCacher', function(ServiceProvider $c) {
493
+            return new \Elgg\Cache\ViewCacher($c->views, $c->config);
494
+        });
495
+
496
+        $this->setClassName('widgets', \Elgg\WidgetsService::class);
497
+    }
498 498
 }
Please login to merge, or discard this patch.
engine/classes/Elgg/Application/CacheHandler.php 1 patch
Indentation   +348 added lines, -348 removed lines patch added patch discarded remove patch
@@ -15,358 +15,358 @@
 block discarded – undo
15 15
  */
16 16
 class CacheHandler {
17 17
 	
18
-	public static $extensions = [
19
-		'bmp' => "image/bmp",
20
-		'css' => "text/css",
21
-		'gif' => "image/gif",
22
-		'html' => "text/html",
23
-		'ico' => "image/x-icon",
24
-		'jpeg' => "image/jpeg",
25
-		'jpg' => "image/jpeg",
26
-		'js' => "application/javascript",
27
-		'json' => "application/json",
28
-		'png' => "image/png",
29
-		'svg' => "image/svg+xml",
30
-		'swf' => "application/x-shockwave-flash",
31
-		'tiff' => "image/tiff",
32
-		'webp' => "image/webp",
33
-		'xml' => "text/xml",
34
-		'eot' => "application/vnd.ms-fontobject",
35
-		'ttf' => "application/font-ttf",
36
-		'woff' => "application/font-woff",
37
-		'woff2' => "application/font-woff2",
38
-		'otf' => "application/font-otf",
39
-	];
40
-
41
-	public static $utf8_content_types = [
42
-		"text/css",
43
-		"text/html",
44
-		"application/javascript",
45
-		"application/json",
46
-		"image/svg+xml",
47
-		"text/xml",
48
-	];
49
-
50
-	/** @var Config */
51
-	private $config;
52
-
53
-	/** @var Request */
54
-	private $request;
55
-
56
-	/**
57
-	 * Constructor
58
-	 *
59
-	 * @param Config  $config              Elgg configuration
60
-	 * @param Request $request             HTTP request
61
-	 * @param bool    $simplecache_enabled Is the simplecache enabled?
62
-	 */
63
-	public function __construct(Config $config, Request $request, $simplecache_enabled) {
64
-		$this->config = $config;
65
-		$this->request = $request;
66
-		$this->simplecache_enabled = $simplecache_enabled;
67
-	}
68
-
69
-	/**
70
-	 * Handle a request for a cached view
71
-	 *
72
-	 * @param Request     $request Elgg request
73
-	 * @param Application $app     Elgg application
74
-	 * @return Response (unprepared)
75
-	 */
76
-	public function handleRequest(Request $request, Application $app) {
77
-		$config = $this->config;
78
-
79
-		$parsed = $this->parsePath($request->getElggPath());
80
-		if (!$parsed) {
81
-			return $this->send403();
82
-		}
18
+    public static $extensions = [
19
+        'bmp' => "image/bmp",
20
+        'css' => "text/css",
21
+        'gif' => "image/gif",
22
+        'html' => "text/html",
23
+        'ico' => "image/x-icon",
24
+        'jpeg' => "image/jpeg",
25
+        'jpg' => "image/jpeg",
26
+        'js' => "application/javascript",
27
+        'json' => "application/json",
28
+        'png' => "image/png",
29
+        'svg' => "image/svg+xml",
30
+        'swf' => "application/x-shockwave-flash",
31
+        'tiff' => "image/tiff",
32
+        'webp' => "image/webp",
33
+        'xml' => "text/xml",
34
+        'eot' => "application/vnd.ms-fontobject",
35
+        'ttf' => "application/font-ttf",
36
+        'woff' => "application/font-woff",
37
+        'woff2' => "application/font-woff2",
38
+        'otf' => "application/font-otf",
39
+    ];
40
+
41
+    public static $utf8_content_types = [
42
+        "text/css",
43
+        "text/html",
44
+        "application/javascript",
45
+        "application/json",
46
+        "image/svg+xml",
47
+        "text/xml",
48
+    ];
49
+
50
+    /** @var Config */
51
+    private $config;
52
+
53
+    /** @var Request */
54
+    private $request;
55
+
56
+    /**
57
+     * Constructor
58
+     *
59
+     * @param Config  $config              Elgg configuration
60
+     * @param Request $request             HTTP request
61
+     * @param bool    $simplecache_enabled Is the simplecache enabled?
62
+     */
63
+    public function __construct(Config $config, Request $request, $simplecache_enabled) {
64
+        $this->config = $config;
65
+        $this->request = $request;
66
+        $this->simplecache_enabled = $simplecache_enabled;
67
+    }
68
+
69
+    /**
70
+     * Handle a request for a cached view
71
+     *
72
+     * @param Request     $request Elgg request
73
+     * @param Application $app     Elgg application
74
+     * @return Response (unprepared)
75
+     */
76
+    public function handleRequest(Request $request, Application $app) {
77
+        $config = $this->config;
78
+
79
+        $parsed = $this->parsePath($request->getElggPath());
80
+        if (!$parsed) {
81
+            return $this->send403();
82
+        }
83 83
 		
84
-		$ts = $parsed['ts'];
85
-		$view = $parsed['view'];
86
-		$viewtype = $parsed['viewtype'];
87
-
88
-		$content_type = $this->getContentType($view);
89
-		if (empty($content_type)) {
90
-			return $this->send403("Asset must have a valid file extension");
91
-		}
92
-
93
-		$response = Response::create();
94
-		if (in_array($content_type, self::$utf8_content_types)) {
95
-			$response->headers->set('Content-Type', "$content_type;charset=utf-8", true);
96
-		} else {
97
-			$response->headers->set('Content-Type', $content_type, true);
98
-		}
99
-
100
-		if (!$this->simplecache_enabled) {
101
-			$app->bootCore();
102
-			header_remove('Cache-Control');
103
-			header_remove('Pragma');
104
-			header_remove('Expires');
105
-
106
-			if (!$this->isCacheableView($view)) {
107
-				return $this->send403("Requested view is not an asset");
108
-			}
109
-
110
-			$content = $this->getProcessedView($view, $viewtype);
111
-			if ($content === false) {
112
-				return $this->send403();
113
-			}
114
-
115
-			$etag = '"' . md5($content) . '"';
116
-			$this->setRevalidateHeaders($etag, $response);
117
-			if ($this->is304($etag)) {
118
-				return Response::create()->setNotModified();
119
-			}
120
-
121
-			return $response->setContent($content);
122
-		}
123
-
124
-		$etag = "\"$ts\"";
125
-		if ($this->is304($etag)) {
126
-			return Response::create()->setNotModified();
127
-		}
128
-
129
-		// trust the client but check for an existing cache file
130
-		$filename = $config->cacheroot . "views_simplecache/$ts/$viewtype/$view";
131
-		if (file_exists($filename)) {
132
-			$this->sendCacheHeaders($etag, $response);
133
-			return BinaryFileResponse::create($filename, 200, $response->headers->all());
134
-		}
135
-
136
-		// the hard way
137
-		$app->bootCore();
138
-		header_remove('Cache-Control');
139
-		header_remove('Pragma');
140
-		header_remove('Expires');
141
-
142
-		elgg_set_viewtype($viewtype);
143
-		if (!$this->isCacheableView($view)) {
144
-			return $this->send403("Requested view is not an asset");
145
-		}
146
-
147
-		$lastcache = (int) $config->lastcache;
148
-
149
-		$filename = $config->cacheroot . "views_simplecache/$lastcache/$viewtype/$view";
150
-
151
-		if ($lastcache == $ts) {
152
-			$this->sendCacheHeaders($etag, $response);
153
-
154
-			$content = $this->getProcessedView($view, $viewtype);
155
-
156
-			$dir_name = dirname($filename);
157
-			if (!is_dir($dir_name)) {
158
-				// PHP and the server accessing the cache symlink may be a different user. And here
159
-				// it's safe to make everything readable anyway.
160
-				mkdir($dir_name, 0775, true);
161
-			}
162
-
163
-			file_put_contents($filename, $content);
164
-			chmod($filename, 0664);
165
-		} else {
166
-			// if wrong timestamp, don't send HTTP cache
167
-			$content = $this->getProcessedView($view, $viewtype);
168
-		}
169
-
170
-		return $response->setContent($content);
171
-	}
172
-
173
-	/**
174
-	 * Parse a request
175
-	 *
176
-	 * @param string $path Request URL path
177
-	 * @return array Cache parameters (empty array if failure)
178
-	 */
179
-	public function parsePath($path) {
180
-		// no '..'
181
-		if (false !== strpos($path, '..')) {
182
-			return [];
183
-		}
184
-		// only alphanumeric characters plus /, ., -, and _
185
-		if (preg_match('#[^a-zA-Z0-9/\.\-_]#', $path)) {
186
-			return [];
187
-		}
188
-
189
-		// testing showed regex to be marginally faster than array / string functions over 100000 reps
190
-		// it won't make a difference in real life and regex is easier to read.
191
-		// <ts>/<viewtype>/<name/of/view.and.dots>.<type>
192
-		if (!preg_match('#^/cache/([0-9]+)/([^/]+)/(.+)$#', $path, $matches)) {
193
-			return [];
194
-		}
195
-
196
-		return [
197
-			'ts' => $matches[1],
198
-			'viewtype' => $matches[2],
199
-			'view' => $matches[3],
200
-		];
201
-	}
202
-
203
-	/**
204
-	 * Is the view cacheable. Language views are handled specially.
205
-	 *
206
-	 * @param string $view View name
207
-	 *
208
-	 * @return bool
209
-	 */
210
-	protected function isCacheableView($view) {
211
-		if (preg_match('~^languages/(.*)\.js$~', $view, $m)) {
212
-			return in_array($m[1],  _elgg_services()->translator->getAllLanguageCodes());
213
-		}
214
-		return _elgg_services()->views->isCacheableView($view);
215
-	}
216
-
217
-	/**
218
-	 * Send cache headers
219
-	 *
220
-	 * @param string $etag ETag value
221
-	 * @return void
222
-	 */
223
-	protected function sendCacheHeaders($etag, Response $response) {
224
-		$response->setSharedMaxAge(86400 * 30 * 6);
225
-		$response->setMaxAge(86400 * 30 * 6);
226
-		$response->headers->set('ETag', $etag);
227
-	}
228
-
229
-	/**
230
-	 * Send revalidate cache headers
231
-	 *
232
-	 * @param string $etag ETag value
233
-	 * @return void
234
-	 */
235
-	protected function setRevalidateHeaders($etag, Response $response) {
236
-		$response->headers->set('Cache-Control', "public, max-age=0, must-revalidate", true);
237
-		$response->headers->set('ETag', $etag);
238
-	}
239
-
240
-	/**
241
-	 * Send a 304 and exit() if the ETag matches the request
242
-	 *
243
-	 * @param string $etag ETag value
244
-	 * @return bool
245
-	 */
246
-	protected function is304($etag) {
247
-		$if_none_match = $this->request->headers->get('If-None-Match');
248
-		if ($if_none_match === null) {
249
-			return false;
250
-		}
251
-
252
-		// strip -gzip and leading /W
253
-		$if_none_match = trim($if_none_match);
254
-		if (0 === strpos($if_none_match, 'W/')) {
255
-			$if_none_match = substr($if_none_match, 2);
256
-		}
257
-		$if_none_match = str_replace('-gzip', '', $if_none_match);
258
-
259
-		return ($if_none_match === $etag);
260
-	}
261
-
262
-	/**
263
-	 * Get the content type
264
-	 *
265
-	 * @param string $view The view name
266
-	 *
267
-	 * @return string|null
268
-	 * @access private
269
-	 */
270
-	public function getContentType($view) {
271
-		$extension = $this->getViewFileType($view);
84
+        $ts = $parsed['ts'];
85
+        $view = $parsed['view'];
86
+        $viewtype = $parsed['viewtype'];
87
+
88
+        $content_type = $this->getContentType($view);
89
+        if (empty($content_type)) {
90
+            return $this->send403("Asset must have a valid file extension");
91
+        }
92
+
93
+        $response = Response::create();
94
+        if (in_array($content_type, self::$utf8_content_types)) {
95
+            $response->headers->set('Content-Type', "$content_type;charset=utf-8", true);
96
+        } else {
97
+            $response->headers->set('Content-Type', $content_type, true);
98
+        }
99
+
100
+        if (!$this->simplecache_enabled) {
101
+            $app->bootCore();
102
+            header_remove('Cache-Control');
103
+            header_remove('Pragma');
104
+            header_remove('Expires');
105
+
106
+            if (!$this->isCacheableView($view)) {
107
+                return $this->send403("Requested view is not an asset");
108
+            }
109
+
110
+            $content = $this->getProcessedView($view, $viewtype);
111
+            if ($content === false) {
112
+                return $this->send403();
113
+            }
114
+
115
+            $etag = '"' . md5($content) . '"';
116
+            $this->setRevalidateHeaders($etag, $response);
117
+            if ($this->is304($etag)) {
118
+                return Response::create()->setNotModified();
119
+            }
120
+
121
+            return $response->setContent($content);
122
+        }
123
+
124
+        $etag = "\"$ts\"";
125
+        if ($this->is304($etag)) {
126
+            return Response::create()->setNotModified();
127
+        }
128
+
129
+        // trust the client but check for an existing cache file
130
+        $filename = $config->cacheroot . "views_simplecache/$ts/$viewtype/$view";
131
+        if (file_exists($filename)) {
132
+            $this->sendCacheHeaders($etag, $response);
133
+            return BinaryFileResponse::create($filename, 200, $response->headers->all());
134
+        }
135
+
136
+        // the hard way
137
+        $app->bootCore();
138
+        header_remove('Cache-Control');
139
+        header_remove('Pragma');
140
+        header_remove('Expires');
141
+
142
+        elgg_set_viewtype($viewtype);
143
+        if (!$this->isCacheableView($view)) {
144
+            return $this->send403("Requested view is not an asset");
145
+        }
146
+
147
+        $lastcache = (int) $config->lastcache;
148
+
149
+        $filename = $config->cacheroot . "views_simplecache/$lastcache/$viewtype/$view";
150
+
151
+        if ($lastcache == $ts) {
152
+            $this->sendCacheHeaders($etag, $response);
153
+
154
+            $content = $this->getProcessedView($view, $viewtype);
155
+
156
+            $dir_name = dirname($filename);
157
+            if (!is_dir($dir_name)) {
158
+                // PHP and the server accessing the cache symlink may be a different user. And here
159
+                // it's safe to make everything readable anyway.
160
+                mkdir($dir_name, 0775, true);
161
+            }
162
+
163
+            file_put_contents($filename, $content);
164
+            chmod($filename, 0664);
165
+        } else {
166
+            // if wrong timestamp, don't send HTTP cache
167
+            $content = $this->getProcessedView($view, $viewtype);
168
+        }
169
+
170
+        return $response->setContent($content);
171
+    }
172
+
173
+    /**
174
+     * Parse a request
175
+     *
176
+     * @param string $path Request URL path
177
+     * @return array Cache parameters (empty array if failure)
178
+     */
179
+    public function parsePath($path) {
180
+        // no '..'
181
+        if (false !== strpos($path, '..')) {
182
+            return [];
183
+        }
184
+        // only alphanumeric characters plus /, ., -, and _
185
+        if (preg_match('#[^a-zA-Z0-9/\.\-_]#', $path)) {
186
+            return [];
187
+        }
188
+
189
+        // testing showed regex to be marginally faster than array / string functions over 100000 reps
190
+        // it won't make a difference in real life and regex is easier to read.
191
+        // <ts>/<viewtype>/<name/of/view.and.dots>.<type>
192
+        if (!preg_match('#^/cache/([0-9]+)/([^/]+)/(.+)$#', $path, $matches)) {
193
+            return [];
194
+        }
195
+
196
+        return [
197
+            'ts' => $matches[1],
198
+            'viewtype' => $matches[2],
199
+            'view' => $matches[3],
200
+        ];
201
+    }
202
+
203
+    /**
204
+     * Is the view cacheable. Language views are handled specially.
205
+     *
206
+     * @param string $view View name
207
+     *
208
+     * @return bool
209
+     */
210
+    protected function isCacheableView($view) {
211
+        if (preg_match('~^languages/(.*)\.js$~', $view, $m)) {
212
+            return in_array($m[1],  _elgg_services()->translator->getAllLanguageCodes());
213
+        }
214
+        return _elgg_services()->views->isCacheableView($view);
215
+    }
216
+
217
+    /**
218
+     * Send cache headers
219
+     *
220
+     * @param string $etag ETag value
221
+     * @return void
222
+     */
223
+    protected function sendCacheHeaders($etag, Response $response) {
224
+        $response->setSharedMaxAge(86400 * 30 * 6);
225
+        $response->setMaxAge(86400 * 30 * 6);
226
+        $response->headers->set('ETag', $etag);
227
+    }
228
+
229
+    /**
230
+     * Send revalidate cache headers
231
+     *
232
+     * @param string $etag ETag value
233
+     * @return void
234
+     */
235
+    protected function setRevalidateHeaders($etag, Response $response) {
236
+        $response->headers->set('Cache-Control', "public, max-age=0, must-revalidate", true);
237
+        $response->headers->set('ETag', $etag);
238
+    }
239
+
240
+    /**
241
+     * Send a 304 and exit() if the ETag matches the request
242
+     *
243
+     * @param string $etag ETag value
244
+     * @return bool
245
+     */
246
+    protected function is304($etag) {
247
+        $if_none_match = $this->request->headers->get('If-None-Match');
248
+        if ($if_none_match === null) {
249
+            return false;
250
+        }
251
+
252
+        // strip -gzip and leading /W
253
+        $if_none_match = trim($if_none_match);
254
+        if (0 === strpos($if_none_match, 'W/')) {
255
+            $if_none_match = substr($if_none_match, 2);
256
+        }
257
+        $if_none_match = str_replace('-gzip', '', $if_none_match);
258
+
259
+        return ($if_none_match === $etag);
260
+    }
261
+
262
+    /**
263
+     * Get the content type
264
+     *
265
+     * @param string $view The view name
266
+     *
267
+     * @return string|null
268
+     * @access private
269
+     */
270
+    public function getContentType($view) {
271
+        $extension = $this->getViewFileType($view);
272 272
 		
273
-		if (isset(self::$extensions[$extension])) {
274
-			return self::$extensions[$extension];
275
-		} else {
276
-			return null;
277
-		}
278
-	}
273
+        if (isset(self::$extensions[$extension])) {
274
+            return self::$extensions[$extension];
275
+        } else {
276
+            return null;
277
+        }
278
+    }
279 279
 	
280
-	/**
281
-	 * Returns the type of output expected from the view.
282
-	 *
283
-	 *  - view/name.extension returns "extension" if "extension" is valid
284
-	 *  - css/view return "css"
285
-	 *  - js/view return "js"
286
-	 *  - Otherwise, returns "unknown"
287
-	 *
288
-	 * @param string $view The view name
289
-	 * @return string
290
-	 * @access private
291
-	 */
292
-	public function getViewFileType($view) {
293
-		$extension = (new \SplFileInfo($view))->getExtension();
294
-		$hasValidExtension = isset(self::$extensions[$extension]);
295
-
296
-		if ($hasValidExtension) {
297
-			return $extension;
298
-		}
280
+    /**
281
+     * Returns the type of output expected from the view.
282
+     *
283
+     *  - view/name.extension returns "extension" if "extension" is valid
284
+     *  - css/view return "css"
285
+     *  - js/view return "js"
286
+     *  - Otherwise, returns "unknown"
287
+     *
288
+     * @param string $view The view name
289
+     * @return string
290
+     * @access private
291
+     */
292
+    public function getViewFileType($view) {
293
+        $extension = (new \SplFileInfo($view))->getExtension();
294
+        $hasValidExtension = isset(self::$extensions[$extension]);
295
+
296
+        if ($hasValidExtension) {
297
+            return $extension;
298
+        }
299 299
 		
300
-		if (preg_match('~(?:^|/)(css|js)(?:$|/)~', $view, $m)) {
301
-			return $m[1];
302
-		}
300
+        if (preg_match('~(?:^|/)(css|js)(?:$|/)~', $view, $m)) {
301
+            return $m[1];
302
+        }
303 303
 		
304
-		return 'unknown';
305
-	}
306
-
307
-	/**
308
-	 * Get the contents of a view for caching
309
-	 *
310
-	 * @param string $view     The view name
311
-	 * @param string $viewtype The viewtype
312
-	 * @return string|false
313
-	 * @see CacheHandler::renderView()
314
-	 */
315
-	protected function getProcessedView($view, $viewtype) {
316
-		$content = $this->renderView($view, $viewtype);
317
-		if ($content === false) {
318
-			return false;
319
-		}
320
-
321
-		if ($this->simplecache_enabled) {
322
-			$hook_name = 'simplecache:generate';
323
-		} else {
324
-			$hook_name = 'cache:generate';
325
-		}
326
-		$hook_type = $this->getViewFileType($view);
327
-		$hook_params = [
328
-			'view' => $view,
329
-			'viewtype' => $viewtype,
330
-			'view_content' => $content,
331
-		];
332
-		return \_elgg_services()->hooks->trigger($hook_name, $hook_type, $hook_params, $content);
333
-	}
334
-
335
-	/**
336
-	 * Render a view for caching. Language views are handled specially.
337
-	 *
338
-	 * @param string $view     The view name
339
-	 * @param string $viewtype The viewtype
340
-	 * @return string|false
341
-	 */
342
-	protected function renderView($view, $viewtype) {
343
-		elgg_set_viewtype($viewtype);
344
-
345
-		if ($viewtype === 'default' && preg_match("#^languages/(.*?)\\.js$#", $view, $matches)) {
346
-			$view = "languages.js";
347
-			$vars = ['language' => $matches[1]];
348
-		} else {
349
-			$vars = [];
350
-		}
351
-
352
-		if (!elgg_view_exists($view)) {
353
-			return false;
354
-		}
355
-
356
-		// disable error reporting so we don't cache problems
357
-		$this->config->debug = null;
358
-
359
-		return elgg_view($view, $vars);
360
-	}
361
-
362
-	/**
363
-	 * Send an error message to requestor
364
-	 *
365
-	 * @param string $msg Optional message text
366
-	 * @return Response
367
-	 */
368
-	protected function send403($msg = 'Cache error: bad request') {
369
-		return Response::create($msg, 403);
370
-	}
304
+        return 'unknown';
305
+    }
306
+
307
+    /**
308
+     * Get the contents of a view for caching
309
+     *
310
+     * @param string $view     The view name
311
+     * @param string $viewtype The viewtype
312
+     * @return string|false
313
+     * @see CacheHandler::renderView()
314
+     */
315
+    protected function getProcessedView($view, $viewtype) {
316
+        $content = $this->renderView($view, $viewtype);
317
+        if ($content === false) {
318
+            return false;
319
+        }
320
+
321
+        if ($this->simplecache_enabled) {
322
+            $hook_name = 'simplecache:generate';
323
+        } else {
324
+            $hook_name = 'cache:generate';
325
+        }
326
+        $hook_type = $this->getViewFileType($view);
327
+        $hook_params = [
328
+            'view' => $view,
329
+            'viewtype' => $viewtype,
330
+            'view_content' => $content,
331
+        ];
332
+        return \_elgg_services()->hooks->trigger($hook_name, $hook_type, $hook_params, $content);
333
+    }
334
+
335
+    /**
336
+     * Render a view for caching. Language views are handled specially.
337
+     *
338
+     * @param string $view     The view name
339
+     * @param string $viewtype The viewtype
340
+     * @return string|false
341
+     */
342
+    protected function renderView($view, $viewtype) {
343
+        elgg_set_viewtype($viewtype);
344
+
345
+        if ($viewtype === 'default' && preg_match("#^languages/(.*?)\\.js$#", $view, $matches)) {
346
+            $view = "languages.js";
347
+            $vars = ['language' => $matches[1]];
348
+        } else {
349
+            $vars = [];
350
+        }
351
+
352
+        if (!elgg_view_exists($view)) {
353
+            return false;
354
+        }
355
+
356
+        // disable error reporting so we don't cache problems
357
+        $this->config->debug = null;
358
+
359
+        return elgg_view($view, $vars);
360
+    }
361
+
362
+    /**
363
+     * Send an error message to requestor
364
+     *
365
+     * @param string $msg Optional message text
366
+     * @return Response
367
+     */
368
+    protected function send403($msg = 'Cache error: bad request') {
369
+        return Response::create($msg, 403);
370
+    }
371 371
 }
372 372
 
Please login to merge, or discard this patch.