Completed
Push — master ( f3fec3...c7133e )
by Nazar
04:51
created

Installer   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 367
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 5
Bugs 1 Features 1
Metric Value
c 5
b 1
f 1
dl 0
loc 367
rs 10
wmc 29
lcom 1
cbo 1

8 Methods

Rating   Name   Duplication   Size   Complexity  
A pre_installation_checks() 0 8 3
C initialize_filesystem() 0 36 8
B install() 0 50 4
B extract() 0 19 5
B initialize_core_config() 0 24 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 Framework
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
	RuntimeException;
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
//Settings of main Storage
29
	"storage_type"		: "Local",
30
	"storage_url"		: "",
31
	"storage_host"		: "localhost",
32
	"storage_user"		: "",
33
	"storage_password"	: "",
34
//Base language
35
	"language"			: "@language",
36
//Cache engine
37
	"cache_engine"		: "FileSystem",
38
//Settings of Memcached cache engine
39
	"memcache_host"		: "127.0.0.1",
40
	"memcache_port"		: "11211",
41
//Any length
42
	"public_key"		: "@public_key"
43
}
44
45
CONFIG;
46
	/**
47
	 * @param string $source
48
	 * @param string $target
49
	 * @param string $site_name
50
	 * @param string $url
51
	 * @param string $timezone
52
	 * @param string $db_host
53
	 * @param string $db_engine
54
	 * @param string $db_name
55
	 * @param string $db_user
56
	 * @param string $db_password
57
	 * @param string $db_prefix
58
	 * @param string $language
59
	 * @param string $admin_email
60
	 * @param string $admin_password
61
	 * @param int    $mode
62
	 *
63
	 * @throws RuntimeException
64
	 */
65
	static function install (
66
		$source,
67
		$target,
68
		$site_name,
69
		$url,
70
		$timezone,
71
		$db_host,
72
		$db_engine,
73
		$db_name,
74
		$db_user,
75
		$db_password,
76
		$db_prefix,
77
		$language,
78
		$admin_email,
79
		$admin_password,
80
		$mode
81
	) {
82
		static::pre_installation_checks($source, $target, $db_engine);
83
		// Needed to be defined before connecting to the database
84
		defined('DEBUG') || define('DEBUG', false);
85
		$file_index_map = static::initialize_filesystem($source);
86
		static::extract($file_index_map, $source, $target);
87
		$domain     = parse_url($url, PHP_URL_HOST);
88
		$public_key = hash('sha512', random_bytes(1000));
89
		static::initialize_core_config(
90
			$target,
91
			$domain,
92
			$timezone,
93
			$db_host,
94
			$db_engine,
95
			$db_name,
96
			$db_user,
97
			$db_password,
98
			$db_prefix,
99
			$language,
100
			$public_key
101
		);
102
		/**
103
		 * @var \cs\DB\_Abstract $cdb
104
		 */
105
		$cdb = "cs\\DB\\$db_engine";
106
		$cdb = new $cdb($db_name, $db_user, $db_password, $db_host, $db_prefix);
107
		if (!is_object($cdb) || !$cdb->connected()) {
108
			throw new RuntimeException("Can't connect to database! Installation aborted.");
109
		}
110
		static::initialize_db_structure($cdb, $source, $db_engine);
111
		static::initialize_system_config($cdb, $source, $site_name, $url, $admin_email, $language, $domain, $timezone, $mode);
112
		static::create_root_administrator($cdb, $admin_email, $admin_password, $public_key);
113
		unset($cdb);
114
	}
115
	/**
116
	 * @param string $source
117
	 * @param string $target
118
	 * @param string $db_engine
119
	 *
120
	 * @throws RuntimeException
121
	 */
122
	protected static function pre_installation_checks ($source, $target, $db_engine) {
123
		if (file_exists("$target/config/main.json")) {
124
			throw new RuntimeException('"config/main.json" file already present! Installation aborted.');
125
		}
126
		if (!file_exists("$source/DB/$db_engine.sql")) {
127
			throw new RuntimeException("Can't find system tables structure for selected database engine! Installation aborted.");
128
		}
129
	}
130
	/**
131
	 * @param string $source
132
	 *
133
	 * @return array[]
134
	 */
135
	protected static function initialize_filesystem ($source) {
136
		$file_index_map = json_decode(file_get_contents("$source/fs_installer.json"), true);
137
		require_once "$source/fs/".$file_index_map['core/thirdparty/upf.php'];
138
		require_once "$source/fs/".$file_index_map['core/functions.php'];
139
		// Remove default autoloader, since we have special autoloader suitable for operating inside installer where default will fail hard
140
		spl_autoload_unregister(spl_autoload_functions()[0]);
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('//', '/', "$namespace/$class_name.php")])                    //Classes in modules
162
				) {
163
					require_once "$source/fs/$file";
164
					return true;
165
				}
166
				return false;
167
			}
168
		);
169
		return $file_index_map;
170
	}
171
	/**
172
	 * @param array[] $file_index_map
173
	 * @param string  $source
174
	 * @param string  $target
175
	 *
176
	 * @throws RuntimeException
177
	 */
178
	protected static function extract ($file_index_map, $source, $target) {
179
		/**
180
		 * Extracting of engine's files
181
		 */
182
		foreach ($file_index_map as $file_path => $file_index) {
183
			$dir = dirname("$target/$file_path");
184
			if (!@mkdir($dir, 0770, true) && !is_dir($dir)) {
185
				throw new RuntimeException("Can't extract system files from the archive, creating directory $dir failed! Installation aborted.");
186
			}
187
			if (!copy("$source/fs/$file_index", "$target/$file_path")) {
188
				throw new RuntimeException("Can't extract system files from the archive, creating file $target/$file_path failed! Installation aborted.");
189
			}
190
		}
191
		file_put_json("$target/core/fs.json", array_keys(file_get_json("$source/fs.json")));
192
		/**
193
		 * Make CLI executable
194
		 */
195
		chmod("$target/cli", 0770);
196
	}
197
	/**
198
	 * @param string $target
199
	 * @param string $domain
200
	 * @param string $timezone
201
	 * @param string $db_host
202
	 * @param string $db_engine
203
	 * @param string $db_name
204
	 * @param string $db_user
205
	 * @param string $db_password
206
	 * @param string $db_prefix
207
	 * @param string $language
208
	 * @param string $public_key
209
	 *
210
	 * @throws RuntimeException
211
	 */
212
	protected static function initialize_core_config (
213
		$target,
214
		$domain,
215
		$timezone,
216
		$db_host,
217
		$db_engine,
218
		$db_name,
219
		$db_user,
220
		$db_password,
221
		$db_prefix,
222
		$language,
223
		$public_key
224
	) {
225
		$db_password = str_replace('"', '\\"', $db_password);
226
		$config      = str_replace(
227
			['@domain', '@timezone', '@db_host', '@db_type', '@db_name', '@db_user', '@db_password', '@db_prefix', '@language', '@public_key'],
228
			[$domain, $timezone, $db_host, $db_engine, $db_name, $db_user, $db_password, $db_prefix, $language, $public_key],
229
			self::MAIN_CONFIG_STUB
230
		);
231
		if (!file_put_contents("$target/config/main.json", $config)) {
232
			throw new RuntimeException("Can't write core system configuration! Installation aborted.");
233
		}
234
		chmod("$target/config/main.json", 0600);
235
	}
236
	/**
237
	 * @param DB\_Abstract $cdb
238
	 * @param string       $source
239
	 * @param string       $db_engine
240
	 *
241
	 * @throws RuntimeException
242
	 */
243
	protected static function initialize_db_structure ($cdb, $source, $db_engine) {
244
		$query = array_filter(
245
			explode(';', file_get_contents("$source/DB/$db_engine.sql")),
246
			'trim'
247
		);
248
		if (!$cdb->q($query)) {
249
			throw new RuntimeException("Can't import system tables structure for selected database engine! Installation aborted.");
250
		}
251
	}
252
	/**
253
	 * @param DB\_Abstract $cdb
254
	 * @param string       $source
255
	 * @param string       $site_name
256
	 * @param string       $url
257
	 * @param string       $admin_email
258
	 * @param string       $language
259
	 * @param string       $domain
260
	 * @param string       $timezone
261
	 * @param int          $mode
262
	 *
263
	 * @throws RuntimeException
264
	 */
265
	protected static function initialize_system_config ($cdb, $source, $site_name, $url, $admin_email, $language, $domain, $timezone, $mode) {
266
		$config     = [
267
			'name'                              => $site_name,
268
			'url'                               => [$url],
269
			'admin_email'                       => $admin_email,
270
			'closed_title'                      => 'Site closed',
271
			'closed_text'                       => '<p>Site closed for maintenance</p>',
272
			'site_mode'                         => 1,
273
			'title_delimiter'                   => ' | ',
274
			'title_reverse'                     => 0,
275
			'cache_compress_js_css'             => 1,
276
			'frontend_load_optimization'        => 1,
277
			'vulcanization'                     => 1,
278
			'put_js_after_body'                 => 1,
279
			'theme'                             => 'CleverStyle',
280
			'language'                          => $language,
281
			'active_languages'                  => [$language],
282
			'multilingual'                      => 0,
283
			'db_balance'                        => 0,
284
			'db_mirror_mode'                    => DB::MIRROR_MODE_MASTER_MASTER,
285
			'cookie_prefix'                     => '',
286
			'cookie_domain'                     => [$domain],
287
			'inserts_limit'                     => 1000,
288
			'key_expire'                        => 60 * 2,
289
			'gravatar_support'                  => 0,
290
			'session_expire'                    => 3600 * 24 * 30,
291
			'update_ratio'                      => 75,
292
			'sign_in_attempts_block_count'      => 0,
293
			'sign_in_attempts_block_time'       => 5,
294
			'timezone'                          => $timezone,
295
			'password_min_length'               => 4,
296
			'password_min_strength'             => 3,
297
			'smtp'                              => 0,
298
			'smtp_host'                         => '',
299
			'smtp_port'                         => '',
300
			'smtp_secure'                       => '',
301
			'smtp_auth'                         => 0,
302
			'smtp_user'                         => '',
303
			'smtp_password'                     => '',
304
			'mail_from'                         => $admin_email,
305
			'mail_from_name'                    => "Administrator of $site_name",
306
			'allow_user_registration'           => 1,
307
			'require_registration_confirmation' => 1,
308
			'auto_sign_in_after_registration'   => 1,
309
			'registration_confirmation_time'    => 1,
310
			'mail_signature'                    => '',
311
			'remember_user_ip'                  => 0,
312
			'simple_admin_mode'                 => $mode,
313
			'default_module'                    => Config::SYSTEM_MODULE
314
		];
315
		$components = [
316
			'modules' => [
317
				'System' => [
318
					'active' => Config\Module_Properties::ENABLED,
319
					'db'     => [
320
						'keys'  => '0',
321
						'users' => '0',
322
						'texts' => '0'
323
					]
324
				]
325
			],
326
			'blocks'  => []
327
		];
328
		foreach (file_get_json("$source/modules.json") as $module) {
329
			$components['modules'][$module] = [
330
				'active'  => Config\Module_Properties::UNINSTALLED,
331
				'db'      => [],
332
				'storage' => []
333
			];
334
		}
335
		$result = $cdb->q(
336
			"INSERT INTO `[prefix]config` (
337
				`domain`, `core`, `db`, `storage`, `components`
338
			) VALUES (
339
				'%s', '%s', '[]', '[]', '%s'
340
			)",
341
			$domain,
342
			_json_encode($config),
343
			_json_encode($components)
344
		);
345
		if (!$result) {
346
			throw new RuntimeException("Can't import system configuration into database! Installation aborted.");
347
		}
348
	}
349
	/**
350
	 * @param DB\_Abstract $cdb
351
	 * @param string       $admin_email
352
	 * @param string       $admin_password
353
	 * @param string       $public_key
354
	 *
355
	 * @throws RuntimeException
356
	 */
357
	protected static function create_root_administrator ($cdb, $admin_email, $admin_password, $public_key) {
358
		$admin_email = strtolower($admin_email);
359
		$admin_login = strstr($admin_email, '@', true);
360
		$result      = $cdb->q(
361
			"INSERT INTO `[prefix]users` (
362
				`login`, `login_hash`, `password_hash`, `email`, `email_hash`, `reg_date`, `reg_ip`, `status`
363
			) VALUES (
364
				'%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s'
365
			)",
366
			$admin_login,
367
			hash('sha224', $admin_login),
368
			password_hash(hash('sha512', hash('sha512', $admin_password).$public_key), PASSWORD_DEFAULT),
369
			$admin_email,
370
			hash('sha224', $admin_email),
371
			time(),
372
			'127.0.0.1',
373
			User::STATUS_ACTIVE
374
		);
375
		if (!$result) {
376
			throw new RuntimeException("Can't register root administrator user! Installation aborted.");
377
		}
378
	}
379
}
380