Completed
Push — master ( b30b4c...b87914 )
by Morris
39:09 queued 24:27
created

Setup   F

Complexity

Total Complexity 53

Size/Duplication

Total Lines 497
Duplicated Lines 2.82 %

Coupling/Cohesion

Components 1
Dependencies 19

Importance

Changes 0
Metric Value
dl 14
loc 497
rs 3.4784
c 0
b 0
f 0
wmc 53
lcom 1
cbo 19

13 Methods

Rating   Name   Duplication   Size   Complexity  
A protectDataDirectory() 0 22 1
A getVendor() 0 6 1
A __construct() 14 14 1
A class_exists() 0 3 1
A is_callable() 0 3 1
A getAvailableDbDriversForPdo() 0 3 1
B getSupportedDatabases() 0 54 8
C getSystemInfo() 0 63 8
F install() 0 154 21
A validateDatabaseHost() 0 7 2
A installBackgroundJobs() 0 3 1
A pathToHtaccess() 0 3 1
B updateHtaccess() 0 68 6

How to fix   Duplicated Code    Complexity   

Duplicated Code

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

Common duplication problems, and corresponding solutions are:

Complex Class

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

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

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

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

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Administrator <Administrator@WINDOWS-2012>
6
 * @author Arthur Schiwon <[email protected]>
7
 * @author Bart Visscher <[email protected]>
8
 * @author Bernhard Posselt <[email protected]>
9
 * @author Brice Maron <[email protected]>
10
 * @author Christoph Wurst <[email protected]>
11
 * @author François Kubler <[email protected]>
12
 * @author Jakob Sack <[email protected]>
13
 * @author Joas Schilling <[email protected]>
14
 * @author Lukas Reschke <[email protected]>
15
 * @author Martin Mattel <[email protected]>
16
 * @author Morris Jobke <[email protected]>
17
 * @author Robin Appelman <[email protected]>
18
 * @author Roeland Jago Douma <[email protected]>
19
 * @author Sean Comeau <[email protected]>
20
 * @author Serge Martin <[email protected]>
21
 * @author Thomas Müller <[email protected]>
22
 * @author Vincent Petry <[email protected]>
23
 *
24
 * @license AGPL-3.0
25
 *
26
 * This code is free software: you can redistribute it and/or modify
27
 * it under the terms of the GNU Affero General Public License, version 3,
28
 * as published by the Free Software Foundation.
29
 *
30
 * This program is distributed in the hope that it will be useful,
31
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33
 * GNU Affero General Public License for more details.
34
 *
35
 * You should have received a copy of the GNU Affero General Public License, version 3,
36
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
37
 *
38
 */
39
40
namespace OC;
41
42
use bantu\IniGetWrapper\IniGetWrapper;
43
use Exception;
44
use OC\App\AppStore\Bundles\BundleFetcher;
45
use OC\Authentication\Token\DefaultTokenCleanupJob;
46
use OC\Authentication\Token\DefaultTokenProvider;
47
use OCP\Defaults;
48
use OCP\IL10N;
49
use OCP\ILogger;
50
use OCP\Security\ISecureRandom;
51
52
class Setup {
53
	/** @var SystemConfig */
54
	protected $config;
55
	/** @var IniGetWrapper */
56
	protected $iniWrapper;
57
	/** @var IL10N */
58
	protected $l10n;
59
	/** @var Defaults */
60
	protected $defaults;
61
	/** @var ILogger */
62
	protected $logger;
63
	/** @var ISecureRandom */
64
	protected $random;
65
66
	/**
67
	 * @param SystemConfig $config
68
	 * @param IniGetWrapper $iniWrapper
69
	 * @param IL10N $l10n
70
	 * @param Defaults $defaults
71
	 * @param ILogger $logger
72
	 * @param ISecureRandom $random
73
	 */
74 View Code Duplication
	public function __construct(SystemConfig $config,
75
						 IniGetWrapper $iniWrapper,
76
						 IL10N $l10n,
77
						 Defaults $defaults,
78
						 ILogger $logger,
79
						 ISecureRandom $random
80
		) {
81
		$this->config = $config;
82
		$this->iniWrapper = $iniWrapper;
83
		$this->l10n = $l10n;
84
		$this->defaults = $defaults;
85
		$this->logger = $logger;
86
		$this->random = $random;
87
	}
88
89
	static protected $dbSetupClasses = [
90
		'mysql' => \OC\Setup\MySQL::class,
91
		'pgsql' => \OC\Setup\PostgreSQL::class,
92
		'oci'   => \OC\Setup\OCI::class,
93
		'sqlite' => \OC\Setup\Sqlite::class,
94
		'sqlite3' => \OC\Setup\Sqlite::class,
95
	];
96
97
	/**
98
	 * Wrapper around the "class_exists" PHP function to be able to mock it
99
	 * @param string $name
100
	 * @return bool
101
	 */
102
	protected function class_exists($name) {
103
		return class_exists($name);
104
	}
105
106
	/**
107
	 * Wrapper around the "is_callable" PHP function to be able to mock it
108
	 * @param string $name
109
	 * @return bool
110
	 */
111
	protected function is_callable($name) {
112
		return is_callable($name);
113
	}
114
115
	/**
116
	 * Wrapper around \PDO::getAvailableDrivers
117
	 *
118
	 * @return array
119
	 */
120
	protected function getAvailableDbDriversForPdo() {
121
		return \PDO::getAvailableDrivers();
122
	}
123
124
	/**
125
	 * Get the available and supported databases of this instance
126
	 *
127
	 * @param bool $allowAllDatabases
128
	 * @return array
129
	 * @throws Exception
130
	 */
131
	public function getSupportedDatabases($allowAllDatabases = false) {
132
		$availableDatabases = [
133
			'sqlite' =>  [
134
				'type' => 'pdo',
135
				'call' => 'sqlite',
136
				'name' => 'SQLite',
137
			],
138
			'mysql' => [
139
				'type' => 'pdo',
140
				'call' => 'mysql',
141
				'name' => 'MySQL/MariaDB',
142
			],
143
			'pgsql' => [
144
				'type' => 'pdo',
145
				'call' => 'pgsql',
146
				'name' => 'PostgreSQL',
147
			],
148
			'oci' => [
149
				'type' => 'function',
150
				'call' => 'oci_connect',
151
				'name' => 'Oracle',
152
			],
153
		];
154
		if ($allowAllDatabases) {
155
			$configuredDatabases = array_keys($availableDatabases);
156
		} else {
157
			$configuredDatabases = $this->config->getValue('supportedDatabases',
158
				['sqlite', 'mysql', 'pgsql']);
159
		}
160
		if(!is_array($configuredDatabases)) {
161
			throw new Exception('Supported databases are not properly configured.');
162
		}
163
164
		$supportedDatabases = array();
165
166
		foreach($configuredDatabases as $database) {
167
			if(array_key_exists($database, $availableDatabases)) {
168
				$working = false;
169
				$type = $availableDatabases[$database]['type'];
170
				$call = $availableDatabases[$database]['call'];
171
172
				if ($type === 'function') {
173
					$working = $this->is_callable($call);
174
				} elseif($type === 'pdo') {
175
					$working = in_array($call, $this->getAvailableDbDriversForPdo(), true);
176
				}
177
				if($working) {
178
					$supportedDatabases[$database] = $availableDatabases[$database]['name'];
179
				}
180
			}
181
		}
182
183
		return $supportedDatabases;
184
	}
185
186
	/**
187
	 * Gathers system information like database type and does
188
	 * a few system checks.
189
	 *
190
	 * @return array of system info, including an "errors" value
191
	 * in case of errors/warnings
192
	 */
193
	public function getSystemInfo($allowAllDatabases = false) {
194
		$databases = $this->getSupportedDatabases($allowAllDatabases);
195
196
		$dataDir = $this->config->getValue('datadirectory', \OC::$SERVERROOT.'/data');
197
198
		$errors = [];
199
200
		// Create data directory to test whether the .htaccess works
201
		// Notice that this is not necessarily the same data directory as the one
202
		// that will effectively be used.
203
		if(!file_exists($dataDir)) {
204
			@mkdir($dataDir);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
205
		}
206
		$htAccessWorking = true;
207
		if (is_dir($dataDir) && is_writable($dataDir)) {
208
			// Protect data directory here, so we can test if the protection is working
209
			self::protectDataDirectory();
210
211
			try {
212
				$util = new \OC_Util();
213
				$htAccessWorking = $util->isHtaccessWorking(\OC::$server->getConfig());
214
			} catch (\OC\HintException $e) {
215
				$errors[] = [
216
					'error' => $e->getMessage(),
217
					'hint' => $e->getHint(),
218
				];
219
				$htAccessWorking = false;
220
			}
221
		}
222
223
		if (\OC_Util::runningOnMac()) {
224
			$errors[] = [
225
				'error' => $this->l10n->t(
226
					'Mac OS X is not supported and %s will not work properly on this platform. ' .
227
					'Use it at your own risk! ',
228
					[$this->defaults->getName()]
229
				),
230
				'hint' => $this->l10n->t('For the best results, please consider using a GNU/Linux server instead.'),
231
			];
232
		}
233
234
		if($this->iniWrapper->getString('open_basedir') !== '' && PHP_INT_SIZE === 4) {
235
			$errors[] = [
236
				'error' => $this->l10n->t(
237
					'It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. ' .
238
					'This will lead to problems with files over 4 GB and is highly discouraged.',
239
					[$this->defaults->getName()]
240
				),
241
				'hint' => $this->l10n->t('Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP.'),
242
			];
243
		}
244
245
		return array(
246
			'hasSQLite' => isset($databases['sqlite']),
247
			'hasMySQL' => isset($databases['mysql']),
248
			'hasPostgreSQL' => isset($databases['pgsql']),
249
			'hasOracle' => isset($databases['oci']),
250
			'databases' => $databases,
251
			'directory' => $dataDir,
252
			'htaccessWorking' => $htAccessWorking,
253
			'errors' => $errors,
254
		);
255
	}
256
257
	/**
258
	 * @param $options
259
	 * @return array
260
	 */
261
	public function install($options) {
262
		$l = $this->l10n;
263
264
		$error = array();
265
		$dbType = $options['dbtype'];
266
267
		if(empty($options['adminlogin'])) {
268
			$error[] = $l->t('Set an admin username.');
269
		}
270
		if(empty($options['adminpass'])) {
271
			$error[] = $l->t('Set an admin password.');
272
		}
273
		if(empty($options['directory'])) {
274
			$options['directory'] = \OC::$SERVERROOT."/data";
275
		}
276
277
		if (!isset(self::$dbSetupClasses[$dbType])) {
278
			$dbType = 'sqlite';
279
		}
280
281
		$username = htmlspecialchars_decode($options['adminlogin']);
282
		$password = htmlspecialchars_decode($options['adminpass']);
283
		$dataDir = htmlspecialchars_decode($options['directory']);
284
285
		$class = self::$dbSetupClasses[$dbType];
286
		/** @var \OC\Setup\AbstractDatabase $dbSetup */
287
		$dbSetup = new $class($l, $this->config, $this->logger, $this->random);
288
		$error = array_merge($error, $dbSetup->validate($options));
289
290
		// validate the data directory
291
		if ((!is_dir($dataDir) && !mkdir($dataDir)) || !is_writable($dataDir)) {
292
			$error[] = $l->t("Can't create or write into the data directory %s", array($dataDir));
293
		}
294
295
		if (!$this->validateDatabaseHost($options['dbhost'])) {
296
			$error[] = $l->t('Given database host is invalid and must not contain the port: %s', [$options['dbhost']]);
297
		}
298
299
		if (!empty($error)) {
300
			return $error;
301
		}
302
303
		$request = \OC::$server->getRequest();
304
305
		//no errors, good
306
		if(isset($options['trusted_domains'])
307
		    && is_array($options['trusted_domains'])) {
308
			$trustedDomains = $options['trusted_domains'];
309
		} else {
310
			$trustedDomains = [$request->getInsecureServerHost()];
311
		}
312
313
		//use sqlite3 when available, otherwise sqlite2 will be used.
314
		if ($dbType === 'sqlite' && class_exists('SQLite3')) {
315
			$dbType = 'sqlite3';
316
		}
317
318
		//generate a random salt that is used to salt the local user passwords
319
		$salt = $this->random->generate(30);
320
		// generate a secret
321
		$secret = $this->random->generate(48);
322
323
		//write the config file
324
		$this->config->setValues([
325
			'passwordsalt'		=> $salt,
326
			'secret'			=> $secret,
327
			'trusted_domains'	=> $trustedDomains,
328
			'datadirectory'		=> $dataDir,
329
			'overwrite.cli.url'	=> $request->getServerProtocol() . '://' . $request->getInsecureServerHost() . \OC::$WEBROOT,
330
			'dbtype'			=> $dbType,
331
			'version'			=> implode('.', \OCP\Util::getVersion()),
332
		]);
333
334
		try {
335
			$dbSetup->initialize($options);
336
			$dbSetup->setupDatabase($username);
337
			// apply necessary migrations
338
			$dbSetup->runMigrations();
339
		} catch (\OC\DatabaseSetupException $e) {
340
			$error[] = [
341
				'error' => $e->getMessage(),
342
				'hint' => $e->getHint(),
343
			];
344
			return $error;
345
		} catch (Exception $e) {
346
			$error[] = [
347
				'error' => 'Error while trying to create admin user: ' . $e->getMessage(),
348
				'hint' => '',
349
			];
350
			return $error;
351
		}
352
353
		//create the user and group
354
		$user =  null;
355
		try {
356
			$user = \OC::$server->getUserManager()->createUser($username, $password);
357
			if (!$user) {
358
				$error[] = "User <$username> could not be created.";
359
			}
360
		} catch(Exception $exception) {
361
			$error[] = $exception->getMessage();
362
		}
363
364
		if (empty($error)) {
365
			$config = \OC::$server->getConfig();
366
			$config->setAppValue('core', 'installedat', microtime(true));
367
			$config->setAppValue('core', 'lastupdatedat', microtime(true));
368
			$config->setAppValue('core', 'vendor', $this->getVendor());
369
370
			$group =\OC::$server->getGroupManager()->createGroup('admin');
371
			$group->addUser($user);
0 ignored issues
show
Bug introduced by
It seems like $user defined by \OC::$server->getUserMan...r($username, $password) on line 356 can also be of type false or null; however, OC\Group\Group::addUser() does only seem to accept object<OCP\IUser>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
372
373
			// Install shipped apps and specified app bundles
374
			Installer::installShippedApps();
375
			$installer = new Installer(
376
				\OC::$server->getAppFetcher(),
377
				\OC::$server->getHTTPClientService(),
378
				\OC::$server->getTempManager(),
379
				\OC::$server->getLogger(),
380
				\OC::$server->getConfig()
381
			);
382
			$bundleFetcher = new BundleFetcher(\OC::$server->getL10N('lib'));
383
			$defaultInstallationBundles = $bundleFetcher->getDefaultInstallationBundle();
384
			foreach($defaultInstallationBundles as $bundle) {
385
				try {
386
					$installer->installAppBundle($bundle);
387
				} catch (Exception $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
388
			}
389
390
			// create empty file in data dir, so we can later find
391
			// out that this is indeed an ownCloud data directory
392
			file_put_contents($config->getSystemValue('datadirectory', \OC::$SERVERROOT.'/data').'/.ocdata', '');
393
394
			// Update .htaccess files
395
			self::updateHtaccess();
396
			self::protectDataDirectory();
397
398
			self::installBackgroundJobs();
399
400
			//and we are done
401
			$config->setSystemValue('installed', true);
402
403
			// Create a session token for the newly created user
404
			// The token provider requires a working db, so it's not injected on setup
405
			/* @var $userSession User\Session */
406
			$userSession = \OC::$server->getUserSession();
407
			$defaultTokenProvider = \OC::$server->query(DefaultTokenProvider::class);
408
			$userSession->setTokenProvider($defaultTokenProvider);
409
			$userSession->login($username, $password);
410
			$userSession->createSessionToken($request, $userSession->getUser()->getUID(), $username, $password);
411
		}
412
413
		return $error;
414
	}
415
416
	/**
417
	 * @param string $host
418
	 * @return bool
419
	 */
420
	protected function validateDatabaseHost($host) {
421
		if (strpos($host, ':') === false) {
422
			return true;
423
		}
424
425
		return filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
426
	}
427
428
	public static function installBackgroundJobs() {
429
		\OC::$server->getJobList()->add(DefaultTokenCleanupJob::class);
430
	}
431
432
	/**
433
	 * @return string Absolute path to htaccess
434
	 */
435
	private function pathToHtaccess() {
436
		return \OC::$SERVERROOT.'/.htaccess';
437
	}
438
439
	/**
440
	 * Append the correct ErrorDocument path for Apache hosts
441
	 * @return bool True when success, False otherwise
442
	 */
443
	public static function updateHtaccess() {
444
		$config = \OC::$server->getSystemConfig();
445
446
		// For CLI read the value from overwrite.cli.url
447
		if(\OC::$CLI) {
448
			$webRoot = $config->getValue('overwrite.cli.url', '');
449
			if($webRoot === '') {
450
				return false;
451
			}
452
			$webRoot = parse_url($webRoot, PHP_URL_PATH);
453
			$webRoot = rtrim($webRoot, '/');
454
		} else {
455
			$webRoot = !empty(\OC::$WEBROOT) ? \OC::$WEBROOT : '/';
456
		}
457
458
		$setupHelper = new \OC\Setup($config, \OC::$server->getIniWrapper(),
459
			\OC::$server->getL10N('lib'), \OC::$server->query(Defaults::class), \OC::$server->getLogger(),
460
			\OC::$server->getSecureRandom());
461
462
		$htaccessContent = file_get_contents($setupHelper->pathToHtaccess());
463
		$content = "#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####\n";
464
		$htaccessContent = explode($content, $htaccessContent, 2)[0];
465
466
		//custom 403 error page
467
		$content.= "\nErrorDocument 403 ".$webRoot."/";
468
469
		//custom 404 error page
470
		$content.= "\nErrorDocument 404 ".$webRoot."/";
471
472
		// Add rewrite rules if the RewriteBase is configured
473
		$rewriteBase = $config->getValue('htaccess.RewriteBase', '');
474
		if($rewriteBase !== '') {
475
			$content .= "\n<IfModule mod_rewrite.c>";
476
			$content .= "\n  Options -MultiViews";
477
			$content .= "\n  RewriteRule ^core/js/oc.js$ index.php [PT,E=PATH_INFO:$1]";
478
			$content .= "\n  RewriteRule ^core/preview.png$ index.php [PT,E=PATH_INFO:$1]";
479
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !\\.(css|js|svg|gif|png|html|ttf|woff|ico|jpg|jpeg)$";
480
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !core/img/favicon.ico$";
481
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !core/img/manifest.json$";
482
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/remote.php";
483
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/public.php";
484
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/cron.php";
485
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/core/ajax/update.php";
486
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/status.php";
487
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/ocs/v1.php";
488
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/ocs/v2.php";
489
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/robots.txt";
490
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/updater/";
491
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/ocs-provider/";
492
			$content .= "\n  RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge/.*";
493
			$content .= "\n  RewriteRule . index.php [PT,E=PATH_INFO:$1]";
494
			$content .= "\n  RewriteBase " . $rewriteBase;
495
			$content .= "\n  <IfModule mod_env.c>";
496
			$content .= "\n    SetEnv front_controller_active true";
497
			$content .= "\n    <IfModule mod_dir.c>";
498
			$content .= "\n      DirectorySlash off";
499
			$content .= "\n    </IfModule>";
500
			$content .= "\n  </IfModule>";
501
			$content .= "\n</IfModule>";
502
		}
503
504
		if ($content !== '') {
505
			//suppress errors in case we don't have permissions for it
506
			return (bool) @file_put_contents($setupHelper->pathToHtaccess(), $htaccessContent.$content . "\n");
507
		}
508
509
		return false;
510
	}
511
512
	public static function protectDataDirectory() {
513
		//Require all denied
514
		$now =  date('Y-m-d H:i:s');
515
		$content = "# Generated by Nextcloud on $now\n";
516
		$content.= "# line below if for Apache 2.4\n";
517
		$content.= "<ifModule mod_authz_core.c>\n";
518
		$content.= "Require all denied\n";
519
		$content.= "</ifModule>\n\n";
520
		$content.= "# line below if for Apache 2.2\n";
521
		$content.= "<ifModule !mod_authz_core.c>\n";
522
		$content.= "deny from all\n";
523
		$content.= "Satisfy All\n";
524
		$content.= "</ifModule>\n\n";
525
		$content.= "# section for Apache 2.2 and 2.4\n";
526
		$content.= "<ifModule mod_autoindex.c>\n";
527
		$content.= "IndexIgnore *\n";
528
		$content.= "</ifModule>\n";
529
530
		$baseDir = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data');
531
		file_put_contents($baseDir . '/.htaccess', $content);
532
		file_put_contents($baseDir . '/index.html', '');
533
	}
534
535
	/**
536
	 * Return vendor from which this version was published
537
	 *
538
	 * @return string Get the vendor
539
	 *
540
	 * Copy of \OC\Updater::getVendor()
541
	 */
542
	private function getVendor() {
543
		// this should really be a JSON file
544
		require \OC::$SERVERROOT . '/version.php';
545
		/** @var string $vendor */
546
		return (string) $vendor;
0 ignored issues
show
Bug introduced by
The variable $vendor does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
547
	}
548
}
549