Completed
Push — master ( d82b6b...206636 )
by Nazar
08:39
created

Installer   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 368
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 92.31%

Importance

Changes 4
Bugs 1 Features 2
Metric Value
dl 0
loc 368
ccs 132
cts 143
cp 0.9231
rs 10
c 4
b 1
f 2
wmc 29
lcom 1
cbo 1

8 Methods

Rating   Name   Duplication   Size   Complexity  
B install() 0 50 4
A pre_installation_checks() 0 8 3
B initialize_core_config() 0 24 2
A initialize_db_structure() 0 9 2
C initialize_filesystem() 0 36 8
B extract() 0 19 5
B initialize_system_config() 0 85 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 4
	public 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 4
		static::pre_installation_checks($source, $target, $db_engine);
83
		// Needed to be defined before connecting to the database
84 4
		defined('DEBUG') || define('DEBUG', false);
85 4
		$file_index_map = static::initialize_filesystem($source);
86 4
		static::extract($file_index_map, $source, $target);
87 4
		$domain     = parse_url($url, PHP_URL_HOST);
88 4
		$public_key = hash('sha512', random_bytes(1000));
89 4
		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 4
		$cdb = "cs\\DB\\$db_engine";
106 4
		$cdb = new $cdb($db_name, $db_user, $db_password, $db_host, $db_prefix);
107 4
		if (!is_object($cdb) || !$cdb->connected()) {
108
			throw new RuntimeException("Can't connect to database! Installation aborted.");
109
		}
110 4
		static::initialize_db_structure($cdb, $source, $db_engine);
111 4
		static::initialize_system_config($cdb, $source, $site_name, $url, $admin_email, $language, $domain, $timezone, $mode);
112 4
		static::create_root_administrator($cdb, $admin_email, $admin_password, $public_key);
113 4
		unset($cdb);
114 4
	}
115
	/**
116
	 * @param string $source
117
	 * @param string $target
118
	 * @param string $db_engine
119
	 *
120
	 * @throws RuntimeException
121
	 */
122 4
	protected static function pre_installation_checks ($source, $target, $db_engine) {
123 4
		if (file_exists("$target/config/main.json")) {
124
			throw new RuntimeException('"config/main.json" file already present! Installation aborted.');
125
		}
126 4
		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 4
	}
130
	/**
131
	 * @param string $source
132
	 *
133
	 * @return array[]
134
	 */
135 4
	protected static function initialize_filesystem ($source) {
136 4
		$file_index_map = json_decode(file_get_contents("$source/fs_installer.json"), true);
137 4
		require_once "$source/fs/".$file_index_map['core/thirdparty/upf.php'];
138 4
		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 4
		spl_autoload_unregister(array_reverse(spl_autoload_functions())[0]);
141
		/**
142
		 * Special autoloader for installer
143
		 */
144 4
		spl_autoload_register(
145 4
			function ($class) use ($file_index_map, $source) {
146 4
				$prepared_class_name = ltrim($class, '\\');
147 4
				if (strpos($prepared_class_name, 'cs\\') === 0) {
148 4
					$prepared_class_name = substr($prepared_class_name, 3);
149
				}
150 4
				$prepared_class_name = explode('\\', $prepared_class_name);
151 4
				$namespace           = count($prepared_class_name) > 1 ? implode('/', array_slice($prepared_class_name, 0, -1)) : '';
152 4
				$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 4
					strlen($file = @$file_index_map[str_replace('//', '/', "core/classes/$namespace/$class_name.php")]) ||    //Core classes
158 4
					strlen($file = @$file_index_map[str_replace('//', '/', "core/thirdparty/$namespace/$class_name.php")]) || //Third party classes
159 4
					strlen($file = @$file_index_map[str_replace('//', '/', "core/traits/$namespace/$class_name.php")]) ||     //Core traits
160 4
					strlen($file = @$file_index_map[str_replace('//', '/', "core/engines/$namespace/$class_name.php")]) ||    //Core engines
161 4
					strlen($file = @$file_index_map[str_replace('//', '/', "$namespace/$class_name.php")])                    //Classes in modules
162
				) {
163 4
					require_once "$source/fs/$file";
164 4
					return true;
165
				}
166
				return false;
167 4
			}
168
		);
169 4
		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 4
	protected static function extract ($file_index_map, $source, $target) {
179
		/**
180
		 * Extracting of engine's files
181
		 */
182 4
		foreach ($file_index_map as $file_path => $file_index) {
183 4
			$dir = dirname("$target/$file_path");
184 4
			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 4
			if (!copy("$source/fs/$file_index", "$target/$file_path")) {
188 4
				throw new RuntimeException("Can't extract system files from the archive, creating file $target/$file_path failed! Installation aborted.");
189
			}
190
		}
191 4
		file_put_json("$target/core/fs.json", array_keys(file_get_json("$source/fs.json")));
192
		/**
193
		 * Make CLI executable
194
		 */
195 4
		chmod("$target/cli", 0770);
196 4
	}
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 4
	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 4
		$db_password = str_replace('"', '\\"', $db_password);
226 4
		$config      = str_replace(
227 4
			['@domain', '@timezone', '@db_host', '@db_type', '@db_name', '@db_user', '@db_password', '@db_prefix', '@language', '@public_key'],
228 4
			[$domain, $timezone, $db_host, $db_engine, $db_name, $db_user, $db_password, $db_prefix, $language, $public_key],
229 4
			self::MAIN_CONFIG_STUB
230
		);
231 4
		if (!file_put_contents("$target/config/main.json", $config)) {
232
			throw new RuntimeException("Can't write core system configuration! Installation aborted.");
233
		}
234 4
		chmod("$target/config/main.json", 0600);
235 4
	}
236
	/**
237
	 * @param DB\_Abstract $cdb
238
	 * @param string       $source
239
	 * @param string       $db_engine
240
	 *
241
	 * @throws RuntimeException
242
	 */
243 4
	protected static function initialize_db_structure ($cdb, $source, $db_engine) {
244 4
		$query = array_filter(
245 4
			explode(';', file_get_contents("$source/DB/$db_engine.sql")),
246 4
			'trim'
247
		);
248 4
		if (!$cdb->q($query)) {
249
			throw new RuntimeException("Can't import system tables structure for selected database engine! Installation aborted.");
250
		}
251 4
	}
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 4
	protected static function initialize_system_config ($cdb, $source, $site_name, $url, $admin_email, $language, $domain, $timezone, $mode) {
266
		$config     = [
267 4
			'name'                              => $site_name,
268 4
			'url'                               => [$url],
269 4
			'admin_email'                       => $admin_email,
270 4
			'closed_title'                      => 'Site closed',
271 4
			'closed_text'                       => '<p>Site closed for maintenance</p>',
272 4
			'site_mode'                         => 1,
273 4
			'title_delimiter'                   => ' | ',
274 4
			'title_reverse'                     => 0,
275 4
			'cache_compress_js_css'             => 1,
276 4
			'frontend_load_optimization'        => 1,
277 4
			'vulcanization'                     => 1,
278 4
			'put_js_after_body'                 => 1,
279 4
			'disable_webcomponents'             => 0,
280 4
			'theme'                             => 'CleverStyle',
281 4
			'language'                          => $language,
282 4
			'active_languages'                  => [$language],
283 4
			'multilingual'                      => 0,
284 4
			'db_balance'                        => 0,
285 4
			'db_mirror_mode'                    => DB::MIRROR_MODE_MASTER_MASTER,
286 4
			'cookie_prefix'                     => '',
287 4
			'cookie_domain'                     => [$domain],
288 4
			'inserts_limit'                     => 1000,
289
			'key_expire'                        => 60 * 2,
290 4
			'gravatar_support'                  => 0,
291
			'session_expire'                    => 3600 * 24 * 30,
292 4
			'update_ratio'                      => 75,
293 4
			'sign_in_attempts_block_count'      => 0,
294 4
			'sign_in_attempts_block_time'       => 5,
295 4
			'timezone'                          => $timezone,
296 4
			'password_min_length'               => 4,
297 4
			'password_min_strength'             => 3,
298 4
			'smtp'                              => 0,
299 4
			'smtp_host'                         => '',
300 4
			'smtp_port'                         => '',
301 4
			'smtp_secure'                       => '',
302 4
			'smtp_auth'                         => 0,
303 4
			'smtp_user'                         => '',
304 4
			'smtp_password'                     => '',
305 4
			'mail_from'                         => $admin_email,
306 4
			'mail_from_name'                    => "Administrator of $site_name",
307 4
			'allow_user_registration'           => 1,
308 4
			'require_registration_confirmation' => 1,
309 4
			'auto_sign_in_after_registration'   => 1,
310 4
			'registration_confirmation_time'    => 1,
311 4
			'mail_signature'                    => '',
312 4
			'remember_user_ip'                  => 0,
313 4
			'simple_admin_mode'                 => $mode,
314 4
			'default_module'                    => Config::SYSTEM_MODULE
315
		];
316
		$components = [
317
			'modules' => [
318
				'System' => [
319 4
					'active' => Config\Module_Properties::ENABLED,
320
					'db'     => [
321
						'keys'  => '0',
322
						'users' => '0',
323
						'texts' => '0'
324
					]
325
				]
326 4
			],
327
			'blocks'  => []
328
		];
329 4
		foreach (file_get_json("$source/modules.json") as $module) {
330
			$components['modules'][$module] = [
331
				'active'  => Config\Module_Properties::UNINSTALLED,
332
				'db'      => [],
333
				'storage' => []
334
			];
335
		}
336 4
		$result = $cdb->q(
337
			"INSERT INTO `[prefix]config` (
338
				`domain`, `core`, `db`, `storage`, `components`
339
			) VALUES (
340
				'%s', '%s', '[]', '[]', '%s'
341 4
			)",
342
			$domain,
343
			_json_encode($config),
344
			_json_encode($components)
345
		);
346 4
		if (!$result) {
347
			throw new RuntimeException("Can't import system configuration into database! Installation aborted.");
348
		}
349 4
	}
350
	/**
351
	 * @param DB\_Abstract $cdb
352
	 * @param string       $admin_email
353
	 * @param string       $admin_password
354
	 * @param string       $public_key
355
	 *
356
	 * @throws RuntimeException
357
	 */
358 4
	protected static function create_root_administrator ($cdb, $admin_email, $admin_password, $public_key) {
359 4
		$admin_email = strtolower($admin_email);
360 4
		$admin_login = strstr($admin_email, '@', true);
361 4
		$result      = $cdb->q(
362
			"INSERT INTO `[prefix]users` (
363
				`login`, `login_hash`, `password_hash`, `email`, `email_hash`, `reg_date`, `reg_ip`, `status`
364
			) VALUES (
365
				'%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s'
366 4
			)",
367
			$admin_login,
368 4
			hash('sha224', $admin_login),
369 4
			password_hash(hash('sha512', hash('sha512', $admin_password).$public_key), PASSWORD_DEFAULT),
370
			$admin_email,
371 4
			hash('sha224', $admin_email),
372
			time(),
373 4
			'127.0.0.1',
374 4
			User::STATUS_ACTIVE
375
		);
376 4
		if (!$result) {
377
			throw new RuntimeException("Can't register root administrator user! Installation aborted.");
378
		}
379 4
	}
380
}
381