Completed
Push — master ( fc0340...72ef30 )
by Nazar
03:52
created

Installer   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 383
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 4
Bugs 1 Features 0
Metric Value
c 4
b 1
f 0
dl 0
loc 383
rs 9.6
wmc 32
lcom 1
cbo 1

8 Methods

Rating   Name   Duplication   Size   Complexity  
B install() 0 52 4
A pre_installation_checks() 0 8 3
C initialize_filesystem() 0 39 8
C extract() 0 26 8
B initialize_core_config() 0 25 2
A initialize_db_structure() 0 9 2
B initialize_system_config() 0 84 3
A create_root_administrator() 0 22 2
1
<?php
2
/**
3
 * @package    CleverStyle CMS
4
 * @subpackage Installer
5
 * @author     Nazar Mokrynskyi <[email protected]>
6
 * @copyright  Copyright (c) 2016, Nazar Mokrynskyi
7
 * @license    MIT License, see license.txt
8
 */
9
namespace cs;
10
use
11
	Exception;
12
13
class Installer {
14
	const MAIN_CONFIG_STUB = /** @lang JSON */
15
		<<<CONFIG
16
{
17
//Domain of main mirror
18
	"domain"			: "@domain",
19
//Base timezone
20
	"timezone"			: "@timezone",
21
//Settings of main DB
22
	"db_host"			: "@db_host",
23
	"db_type"			: "@db_type",
24
	"db_name"			: "@db_name",
25
	"db_user"			: "@db_user",
26
	"db_password"		: "@db_password",
27
	"db_prefix"			: "@db_prefix",
28
	"db_charset"		: "@db_charset",
29
//Settings of main Storage
30
	"storage_type"		: "Local",
31
	"storage_url"		: "",
32
	"storage_host"		: "localhost",
33
	"storage_user"		: "",
34
	"storage_password"	: "",
35
//Base language
36
	"language"			: "@language",
37
//Cache engine
38
	"cache_engine"		: "FileSystem",
39
//Settings of Memcached cache engine
40
	"memcache_host"		: "127.0.0.1",
41
	"memcache_port"		: "11211",
42
//Any length
43
	"public_key"		: "@public_key"
44
}
45
46
CONFIG;
47
	/**
48
	 * @param string $source
49
	 * @param string $target
50
	 * @param string $site_name
51
	 * @param string $url
52
	 * @param string $timezone
53
	 * @param string $db_host
54
	 * @param string $db_engine
55
	 * @param string $db_name
56
	 * @param string $db_user
57
	 * @param string $db_password
58
	 * @param string $db_prefix
59
	 * @param string $db_charset
60
	 * @param string $language
61
	 * @param string $admin_email
62
	 * @param string $admin_password
63
	 * @param int    $mode
64
	 *
65
	 * @throws Exception
66
	 */
67
	static function install (
68
		$source,
69
		$target,
70
		$site_name,
71
		$url,
72
		$timezone,
73
		$db_host,
74
		$db_engine,
75
		$db_name,
76
		$db_user,
77
		$db_password,
78
		$db_prefix,
79
		$db_charset,
80
		$language,
81
		$admin_email,
82
		$admin_password,
83
		$mode
84
	) {
85
		static::pre_installation_checks($source, $target, $db_engine);
86
		// Needed to be defined before connecting to the database
87
		defined('DEBUG') || define('DEBUG', false);
88
		$file_index_map = static::initialize_filesystem($source);
89
		static::extract($file_index_map, $source, $target);
90
		$domain     = parse_url($url, PHP_URL_HOST);
91
		$public_key = hash('sha512', random_bytes(1000));
92
		static::initialize_core_config(
93
			$target,
94
			$domain,
1 ignored issue
show
Security Bug introduced by
It seems like $domain defined by parse_url($url, PHP_URL_HOST) on line 90 can also be of type false; however, cs\Installer::initialize_core_config() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
95
			$timezone,
96
			$db_host,
97
			$db_engine,
98
			$db_name,
99
			$db_user,
100
			$db_password,
101
			$db_prefix,
102
			$db_charset,
103
			$language,
104
			$public_key
105
		);
106
		/**
107
		 * @var \cs\DB\_Abstract $cdb
108
		 */
109
		$cdb = "cs\\DB\\$db_engine";
110
		$cdb = new $cdb($db_name, $db_user, $db_password, $db_host, $db_charset, $db_prefix);
111
		if (!is_object($cdb) || !$cdb->connected()) {
112
			throw new Exception("Can't connect to database! Installation aborted.");
113
		}
114
		static::initialize_db_structure($cdb, $source, $db_engine);
115
		static::initialize_system_config($cdb, $source, $site_name, $url, $admin_email, $language, $domain, $timezone, $mode);
1 ignored issue
show
Security Bug introduced by
It seems like $domain defined by parse_url($url, PHP_URL_HOST) on line 90 can also be of type false; however, cs\Installer::initialize_system_config() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
116
		static::create_root_administrator($cdb, $admin_email, $admin_password, $public_key);
117
		unset($cdb);
118
	}
119
	/**
120
	 * @param string $source
121
	 * @param string $target
122
	 * @param string $db_engine
123
	 *
124
	 * @throws Exception
125
	 */
126
	protected static function pre_installation_checks ($source, $target, $db_engine) {
127
		if (file_exists("$target/config/main.json")) {
128
			throw new Exception('"config/main.json" file already present! Installation aborted.');
129
		}
130
		if (!file_exists("$source/install/DB/$db_engine.sql")) {
131
			throw new Exception("Can't find system tables structure for selected database engine! Installation aborted.");
132
		}
133
	}
134
	/**
135
	 * @param string $source
136
	 *
137
	 * @return array[]
138
	 */
139
	protected static function initialize_filesystem ($source) {
140
		$file_index_map = json_decode(file_get_contents("$source/fs.json"), true);
141
		/**
142
		 * Special autoloader for installer
143
		 */
144
		spl_autoload_register(
145
			function ($class) use ($file_index_map, $source) {
146
				$prepared_class_name = ltrim($class, '\\');
147
				if (strpos($prepared_class_name, 'cs\\') === 0) {
148
					$prepared_class_name = substr($prepared_class_name, 3);
149
				}
150
				$prepared_class_name = explode('\\', $prepared_class_name);
151
				$namespace           = count($prepared_class_name) > 1 ? implode('/', array_slice($prepared_class_name, 0, -1)) : '';
152
				$class_name          = array_pop($prepared_class_name);
153
				/**
154
				 * Try to load classes from different places. If not found in one place - try in another.
155
				 */
156
				if (
157
					strlen($file = @$file_index_map[str_replace('//', '/', "core/classes/$namespace/$class_name.php")]) ||    //Core classes
158
					strlen($file = @$file_index_map[str_replace('//', '/', "core/thirdparty/$namespace/$class_name.php")]) || //Third party classes
159
					strlen($file = @$file_index_map[str_replace('//', '/', "core/traits/$namespace/$class_name.php")]) ||     //Core traits
160
					strlen($file = @$file_index_map[str_replace('//', '/', "core/engines/$namespace/$class_name.php")]) ||    //Core engines
161
					strlen($file = @$file_index_map[str_replace('//', '/', "components/$namespace/$class_name.php")])         //Classes in modules and plugins
162
				) {
163
					/** @noinspection UntrustedInclusionInspection */
164
					require_once "$source/fs/$file";
165
					return true;
166
				}
167
				return false;
168
			},
169
			true,
170
			true
171
		);
172
		require_once "$source/fs/".$file_index_map['core/thirdparty/upf.php'];
173
		require_once "$source/fs/".$file_index_map['core/functions.php'];
174
		// Remove default autoloader, since we have special autoloader suitable for operating inside installer where default will fail hard
175
		spl_autoload_unregister(spl_autoload_functions()[0]);
176
		return $file_index_map;
177
	}
178
	/**
179
	 * @param array[] $file_index_map
180
	 * @param string  $source
181
	 * @param string  $target
182
	 *
183
	 * @throws Exception
184
	 */
185
	protected static function extract ($file_index_map, $source, $target) {
186
		/**
187
		 * Extracting of engine's files
188
		 */
189
		foreach ($file_index_map as $file_path => $file_index) {
190
			$dir = dirname("$target/$file_path");
191
			if (!@mkdir($dir, 0770, true) && !is_dir($dir)) {
192
				throw new Exception("Can't extract system files from the archive, creating directory $dir failed! Installation aborted.");
193
			}
194
			/**
195
			 * TODO: copy() + file_exists() is a hack for HHVM, when bug fixed upstream (copying of empty files) this should be simplified
196
			 */
197
			copy("$source/fs/$file_index", "$target/$file_path");
198
			if (!file_exists("$target/$file_path")) {
199
				throw new Exception("Can't extract system files from the archive, creating file $target/$file_path failed! Installation aborted.");
200
			}
201
		}
202
		if (
203
			(
204
				!@mkdir("$target/storage", 0770) && !is_dir("$target/storage")
205
			) ||
206
			!file_put_contents("$target/storage/.htaccess", "Deny from all\nRewriteEngine Off\n<Files *>\n\tSetHandler default-handler\n</Files>")
207
		) {
208
			throw new Exception("Can't extract system files from the archive! Installation aborted.");
209
		}
210
	}
211
	/**
212
	 * @param string $target
213
	 * @param string $domain
214
	 * @param string $timezone
215
	 * @param string $db_host
216
	 * @param string $db_engine
217
	 * @param string $db_name
218
	 * @param string $db_user
219
	 * @param string $db_password
220
	 * @param string $db_prefix
221
	 * @param string $db_charset
222
	 * @param string $language
223
	 * @param string $public_key
224
	 *
225
	 * @throws Exception
226
	 */
227
	protected static function initialize_core_config (
228
		$target,
229
		$domain,
230
		$timezone,
231
		$db_host,
232
		$db_engine,
233
		$db_name,
234
		$db_user,
235
		$db_password,
236
		$db_prefix,
237
		$db_charset,
238
		$language,
239
		$public_key
240
	) {
241
		$db_password = str_replace('"', '\\"', $db_password);
242
		$config      = str_replace(
243
			['@domain', '@timezone', '@db_host', '@db_type', '@db_name', '@db_user', '@db_password', '@db_prefix', '@db_charset', '@language', '@public_key'],
244
			[$domain, $timezone, $db_host, $db_engine, $db_name, $db_user, $db_password, $db_prefix, $db_charset, $language, $public_key],
245
			self::MAIN_CONFIG_STUB
246
		);
247
		if (!file_put_contents("$target/config/main.json", $config)) {
248
			throw new Exception("Can't write core system configuration! Installation aborted.");
249
		}
250
		chmod("$target/config/main.json", 0600);
251
	}
252
	/**
253
	 * @param DB\_Abstract $cdb
254
	 * @param string       $source
255
	 * @param string       $db_engine
256
	 *
257
	 * @throws Exception
258
	 */
259
	protected static function initialize_db_structure ($cdb, $source, $db_engine) {
260
		$query = array_filter(
261
			explode(';', file_get_contents("$source/install/DB/$db_engine.sql")),
262
			'_trim'
263
		);
264
		if (!$cdb->q($query)) {
265
			throw new Exception("Can't import system tables structure for selected database engine! Installation aborted.");
266
		}
267
	}
268
	/**
269
	 * @param DB\_Abstract $cdb
270
	 * @param string       $source
271
	 * @param string       $site_name
272
	 * @param string       $url
273
	 * @param string       $admin_email
274
	 * @param string       $language
275
	 * @param string       $domain
276
	 * @param string       $timezone
277
	 * @param int          $mode
278
	 *
279
	 * @throws Exception
280
	 */
281
	protected static function initialize_system_config ($cdb, $source, $site_name, $url, $admin_email, $language, $domain, $timezone, $mode) {
282
		$config     = [
283
			'name'                              => $site_name,
284
			'url'                               => [$url],
285
			'admin_email'                       => $admin_email,
286
			'closed_title'                      => 'Site closed',
287
			'closed_text'                       => '<p>Site closed for maintenance</p>',
288
			'site_mode'                         => 1,
289
			'title_delimiter'                   => ' | ',
290
			'title_reverse'                     => 0,
291
			'cache_compress_js_css'             => 1,
292
			'vulcanization'                     => 1,
293
			'put_js_after_body'                 => 1,
294
			'theme'                             => 'CleverStyle',
295
			'language'                          => $language,
296
			'active_languages'                  => [$language],
297
			'multilingual'                      => 0,
298
			'db_balance'                        => 0,
299
			'db_mirror_mode'                    => DB::MIRROR_MODE_MASTER_MASTER,
300
			'cookie_prefix'                     => '',
301
			'cookie_domain'                     => [$domain],
302
			'inserts_limit'                     => 1000,
303
			'key_expire'                        => 60 * 2,
304
			'gravatar_support'                  => 0,
305
			'session_expire'                    => 3600 * 24 * 30,
306
			'update_ratio'                      => 75,
307
			'sign_in_attempts_block_count'      => 0,
308
			'sign_in_attempts_block_time'       => 5,
309
			'timezone'                          => $timezone,
310
			'password_min_length'               => 4,
311
			'password_min_strength'             => 3,
312
			'smtp'                              => 0,
313
			'smtp_host'                         => '',
314
			'smtp_port'                         => '',
315
			'smtp_secure'                       => '',
316
			'smtp_auth'                         => 0,
317
			'smtp_user'                         => '',
318
			'smtp_password'                     => '',
319
			'mail_from'                         => $admin_email,
320
			'mail_from_name'                    => "Administrator of $site_name",
321
			'allow_user_registration'           => 1,
322
			'require_registration_confirmation' => 1,
323
			'auto_sign_in_after_registration'   => 1,
324
			'registration_confirmation_time'    => 1,
325
			'mail_signature'                    => '',
326
			'remember_user_ip'                  => 0,
327
			'simple_admin_mode'                 => $mode,
328
			'default_module'                    => Config::SYSTEM_MODULE
329
		];
330
		$components = [
331
			'modules' => [
332
				'System' => [
333
					'active' => Config\Module_Properties::ENABLED,
334
					'db'     => [
335
						'keys'  => '0',
336
						'users' => '0',
337
						'texts' => '0'
338
					]
339
				]
340
			],
341
			'plugins' => [],
342
			'blocks'  => []
343
		];
344
		foreach (file_get_json("$source/modules.json") as $module) {
345
			$components['modules'][$module] = [
346
				'active'  => Config\Module_Properties::UNINSTALLED,
347
				'db'      => [],
348
				'storage' => []
349
			];
350
		}
351
		$result = $cdb->q(
352
			"INSERT INTO `[prefix]config` (
353
				`domain`, `core`, `db`, `storage`, `components`
354
			) VALUES (
355
				'%s', '%s', '[]', '[]', '%s'
356
			)",
357
			$domain,
358
			_json_encode($config),
359
			_json_encode($components)
360
		);
361
		if (!$result) {
362
			throw new Exception("Can't import system configuration into database! Installation aborted.");
363
		}
364
	}
365
	/**
366
	 * @param DB\_Abstract $cdb
367
	 * @param string       $admin_email
368
	 * @param string       $admin_password
369
	 * @param string       $public_key
370
	 *
371
	 * @throws Exception
372
	 */
373
	protected static function create_root_administrator ($cdb, $admin_email, $admin_password, $public_key) {
374
		$admin_email = strtolower($admin_email);
375
		$admin_login = strstr($admin_email, '@', true);
376
		$result      = $cdb->q(
377
			"INSERT INTO `[prefix]users` (
378
				`login`, `login_hash`, `password_hash`, `email`, `email_hash`, `reg_date`, `reg_ip`, `status`
379
			) VALUES (
380
				'%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s'
381
			)",
382
			$admin_login,
383
			hash('sha224', $admin_login),
384
			password_hash(hash('sha512', hash('sha512', $admin_password).$public_key), PASSWORD_DEFAULT),
385
			$admin_email,
386
			hash('sha224', $admin_email),
387
			time(),
388
			'127.0.0.1',
389
			User::STATUS_ACTIVE
390
		);
391
		if (!$result) {
392
			throw new Exception("Can't register root administrator user! Installation aborted.");
393
		}
394
	}
395
}
396