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, |
|
|
|
|
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); |
|
|
|
|
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
|
|
|
|
This check looks for type mismatches where the missing type is
false
. This is usually indicative of an error condtion.Consider the follow example
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 returnedfalse
before passing on the value to another function or method that may not be able to handle afalse
.