1 | <?php |
||||
2 | /** |
||||
3 | * @package CleverStyle Framework |
||||
4 | * @subpackage Installer |
||||
5 | * @author Nazar Mokrynskyi <[email protected]> |
||||
6 | * @license 0BSD |
||||
7 | */ |
||||
8 | namespace cs; |
||||
9 | use |
||||
10 | RuntimeException; |
||||
11 | |||||
12 | class Installer { |
||||
13 | const MAIN_CONFIG_STUB = /** @lang JSON */ |
||||
14 | <<<CONFIG |
||||
15 | { |
||||
16 | //Domain of main mirror |
||||
17 | "domain" : "@domain", |
||||
18 | //Base timezone |
||||
19 | "timezone" : "@timezone", |
||||
20 | //Settings of main DB |
||||
21 | "db_host" : "@db_host", |
||||
22 | "db_driver" : "@db_driver", |
||||
23 | "db_name" : "@db_name", |
||||
24 | "db_user" : "@db_user", |
||||
25 | "db_password" : "@db_password", |
||||
26 | "db_prefix" : "@db_prefix", |
||||
27 | //Settings of main Storage |
||||
28 | "storage_driver" : "Local", |
||||
29 | "storage_url" : "", |
||||
30 | "storage_host" : "localhost", |
||||
31 | "storage_user" : "", |
||||
32 | "storage_password" : "", |
||||
33 | //Base language |
||||
34 | "language" : "@language", |
||||
35 | //Cache driver |
||||
36 | "cache_driver" : "FileSystem", |
||||
37 | //Settings of Memcached cache driver |
||||
38 | "memcache_host" : "127.0.0.1", |
||||
39 | "memcache_port" : "11211", |
||||
40 | //Any length |
||||
41 | "public_key" : "@public_key" |
||||
42 | } |
||||
43 | |||||
44 | CONFIG; |
||||
45 | /** |
||||
46 | * @param string $source |
||||
47 | * @param string $target |
||||
48 | * @param string $site_name |
||||
49 | * @param string $url |
||||
50 | * @param string $timezone |
||||
51 | * @param string $db_host |
||||
52 | * @param string $db_driver |
||||
53 | * @param string $db_name |
||||
54 | * @param string $db_user |
||||
55 | * @param string $db_password |
||||
56 | * @param string $db_prefix |
||||
57 | * @param string $language |
||||
58 | * @param string $admin_email |
||||
59 | * @param string $admin_password |
||||
60 | * @param int $mode |
||||
61 | * |
||||
62 | * @throws RuntimeException |
||||
63 | */ |
||||
64 | 5 | public static function install ( |
|||
65 | $source, |
||||
66 | $target, |
||||
67 | $site_name, |
||||
68 | $url, |
||||
69 | $timezone, |
||||
70 | $db_host, |
||||
71 | $db_driver, |
||||
72 | $db_name, |
||||
73 | $db_user, |
||||
74 | $db_password, |
||||
75 | $db_prefix, |
||||
76 | $language, |
||||
77 | $admin_email, |
||||
78 | $admin_password, |
||||
79 | $mode |
||||
80 | ) { |
||||
81 | 5 | static::pre_installation_checks($source, $target, $db_driver); |
|||
82 | 5 | $file_index_map = static::initialize_filesystem($source); |
|||
83 | 5 | static::extract($file_index_map, $source, $target); |
|||
84 | 5 | $domain = explode('/', $url)[2]; |
|||
85 | 5 | $public_key = hash('sha512', random_bytes(1000)); |
|||
86 | 5 | static::initialize_core_config( |
|||
87 | 5 | $target, |
|||
88 | 5 | $domain, |
|||
89 | 5 | $timezone, |
|||
90 | 5 | $db_host, |
|||
91 | 5 | $db_driver, |
|||
92 | 5 | $db_name, |
|||
93 | 5 | $db_user, |
|||
94 | 5 | $db_password, |
|||
95 | 5 | $db_prefix, |
|||
96 | 5 | $language, |
|||
97 | 5 | $public_key |
|||
98 | ); |
||||
99 | /** |
||||
100 | * @var \cs\DB\_Abstract $cdb |
||||
101 | */ |
||||
102 | 5 | $cdb = "cs\\DB\\$db_driver"; |
|||
103 | 5 | $cdb = new $cdb($db_name, $db_user, $db_password, $db_host, $db_prefix); |
|||
104 | 5 | if (!is_object($cdb) || !$cdb->connected()) { |
|||
105 | throw new RuntimeException("Can't connect to database! Installation aborted."); |
||||
106 | } |
||||
107 | 5 | static::initialize_db_structure($cdb, $source, $db_driver); |
|||
108 | 5 | static::initialize_system_config($cdb, $source, $site_name, $url, $admin_email, $language, $domain, $timezone, $mode); |
|||
109 | 5 | static::create_root_administrator($cdb, $admin_email, $admin_password, $public_key); |
|||
110 | 5 | unset($cdb); |
|||
111 | 5 | } |
|||
112 | /** |
||||
113 | * @param string $source |
||||
114 | * @param string $target |
||||
115 | * @param string $db_driver |
||||
116 | * |
||||
117 | * @throws RuntimeException |
||||
118 | */ |
||||
119 | 5 | protected static function pre_installation_checks ($source, $target, $db_driver) { |
|||
120 | 5 | if (file_exists("$target/config/main.json")) { |
|||
121 | throw new RuntimeException('"config/main.json" file already present! Installation aborted.'); |
||||
122 | } |
||||
123 | 5 | if (!file_exists("$source/DB/$db_driver.sql")) { |
|||
124 | throw new RuntimeException("Can't find system tables structure for selected database driver! Installation aborted."); |
||||
125 | } |
||||
126 | 5 | } |
|||
127 | /** |
||||
128 | * @param string $source |
||||
129 | * |
||||
130 | * @return array[] |
||||
131 | */ |
||||
132 | 5 | protected static function initialize_filesystem ($source) { |
|||
133 | 5 | $file_index_map = json_decode(file_get_contents("$source/fs_installer.json"), true); |
|||
134 | 5 | require_once "$source/fs/".$file_index_map['core/thirdparty/upf.php']; |
|||
135 | 5 | require_once "$source/fs/".$file_index_map['core/functions.php']; |
|||
136 | // Remove default autoloader, since we have special autoloader suitable for operating inside installer where default will fail hard |
||||
137 | 5 | spl_autoload_unregister(array_reverse(spl_autoload_functions())[0]); |
|||
138 | /** |
||||
139 | * Special autoloader for installer |
||||
140 | */ |
||||
141 | 5 | spl_autoload_register( |
|||
142 | 5 | function ($class) use ($file_index_map, $source) { |
|||
143 | 5 | $prepared_class_name = ltrim($class, '\\'); |
|||
144 | 5 | if (strpos($prepared_class_name, 'cs\\') === 0) { |
|||
145 | 5 | $prepared_class_name = substr($prepared_class_name, 3); |
|||
146 | } |
||||
147 | 5 | $prepared_class_name = explode('\\', $prepared_class_name); |
|||
148 | 5 | $namespace = count($prepared_class_name) > 1 ? implode('/', array_slice($prepared_class_name, 0, -1)) : ''; |
|||
149 | 5 | $class_name = array_pop($prepared_class_name); |
|||
150 | /** |
||||
151 | * Try to load classes from different places. If not found in one place - try in another. |
||||
152 | */ |
||||
153 | if ( |
||||
154 | 5 | strlen($file = @$file_index_map[str_replace('//', '/', "core/classes/$namespace/$class_name.php")]) || //Core classes |
|||
155 | 5 | strlen($file = @$file_index_map[str_replace('//', '/', "core/thirdparty/$namespace/$class_name.php")]) || //Third party classes |
|||
156 | 5 | strlen($file = @$file_index_map[str_replace('//', '/', "core/traits/$namespace/$class_name.php")]) || //Core traits |
|||
157 | 5 | strlen($file = @$file_index_map[str_replace('//', '/', "core/drivers/$namespace/$class_name.php")]) || //Core drivers |
|||
158 | 5 | strlen($file = @$file_index_map[str_replace('//', '/', "$namespace/$class_name.php")]) //Classes in modules |
|||
159 | ) { |
||||
160 | 5 | require_once "$source/fs/$file"; |
|||
161 | 5 | return true; |
|||
162 | } |
||||
163 | return false; |
||||
164 | 5 | } |
|||
165 | ); |
||||
166 | 5 | return $file_index_map; |
|||
167 | } |
||||
168 | /** |
||||
169 | * @param array[] $file_index_map |
||||
170 | * @param string $source |
||||
171 | * @param string $target |
||||
172 | * |
||||
173 | * @throws RuntimeException |
||||
174 | */ |
||||
175 | 5 | protected static function extract ($file_index_map, $source, $target) { |
|||
176 | /** |
||||
177 | * Extracting of system files |
||||
178 | */ |
||||
179 | 5 | foreach ($file_index_map as $file_path => $file_index) { |
|||
180 | 5 | $dir = dirname("$target/$file_path"); |
|||
181 | 5 | if (!@mkdir($dir, 0770, true) && !is_dir($dir)) { |
|||
182 | throw new RuntimeException("Can't extract system files from the archive, creating directory $dir failed! Installation aborted."); |
||||
183 | } |
||||
184 | 5 | if (!copy("$source/fs/$file_index", "$target/$file_path")) { |
|||
185 | 5 | throw new RuntimeException("Can't extract system files from the archive, creating file $target/$file_path failed! Installation aborted."); |
|||
186 | } |
||||
187 | } |
||||
188 | 5 | file_put_json("$target/core/fs.json", array_keys(file_get_json("$source/fs.json"))); |
|||
189 | /** |
||||
190 | * Make CLI executable |
||||
191 | */ |
||||
192 | 5 | chmod("$target/cli", 0770); |
|||
193 | 5 | } |
|||
194 | /** |
||||
195 | * @param string $target |
||||
196 | * @param string $domain |
||||
197 | * @param string $timezone |
||||
198 | * @param string $db_host |
||||
199 | * @param string $db_driver |
||||
200 | * @param string $db_name |
||||
201 | * @param string $db_user |
||||
202 | * @param string $db_password |
||||
203 | * @param string $db_prefix |
||||
204 | * @param string $language |
||||
205 | * @param string $public_key |
||||
206 | * |
||||
207 | * @throws RuntimeException |
||||
208 | */ |
||||
209 | 5 | protected static function initialize_core_config ( |
|||
210 | $target, |
||||
211 | $domain, |
||||
212 | $timezone, |
||||
213 | $db_host, |
||||
214 | $db_driver, |
||||
215 | $db_name, |
||||
216 | $db_user, |
||||
217 | $db_password, |
||||
218 | $db_prefix, |
||||
219 | $language, |
||||
220 | $public_key |
||||
221 | ) { |
||||
222 | 5 | $db_password = str_replace('"', '\\"', $db_password); |
|||
223 | 5 | $config = str_replace( |
|||
224 | 5 | ['@domain', '@timezone', '@db_host', '@db_driver', '@db_name', '@db_user', '@db_password', '@db_prefix', '@language', '@public_key'], |
|||
225 | 5 | [$domain, $timezone, $db_host, $db_driver, $db_name, $db_user, $db_password, $db_prefix, $language, $public_key], |
|||
226 | 5 | self::MAIN_CONFIG_STUB |
|||
227 | ); |
||||
228 | 5 | if (!file_put_contents("$target/config/main.json", $config)) { |
|||
229 | throw new RuntimeException("Can't write core system configuration! Installation aborted."); |
||||
230 | } |
||||
231 | 5 | chmod("$target/config/main.json", 0600); |
|||
232 | 5 | } |
|||
233 | /** |
||||
234 | * @param DB\_Abstract $cdb |
||||
235 | * @param string $source |
||||
236 | * @param string $db_driver |
||||
237 | * |
||||
238 | * @throws RuntimeException |
||||
239 | */ |
||||
240 | 5 | protected static function initialize_db_structure ($cdb, $source, $db_driver) { |
|||
241 | 5 | $query = array_filter( |
|||
242 | 5 | explode(';', file_get_contents("$source/DB/$db_driver.sql")), |
|||
243 | 5 | 'trim' |
|||
244 | ); |
||||
245 | 5 | if (!$cdb->q($query)) { |
|||
246 | throw new RuntimeException("Can't import system tables structure for selected database driver! Installation aborted."); |
||||
247 | } |
||||
248 | 5 | } |
|||
249 | /** |
||||
250 | * @param DB\_Abstract $cdb |
||||
251 | * @param string $source |
||||
252 | * @param string $site_name |
||||
253 | * @param string $url |
||||
254 | * @param string $admin_email |
||||
255 | * @param string $language |
||||
256 | * @param string $domain |
||||
257 | * @param string $timezone |
||||
258 | * @param int $mode |
||||
259 | * |
||||
260 | * @throws RuntimeException |
||||
261 | */ |
||||
262 | 5 | protected static function initialize_system_config ($cdb, $source, $site_name, $url, $admin_email, $language, $domain, $timezone, $mode) { |
|||
263 | $config = [ |
||||
264 | 5 | 'site_name' => $site_name, |
|||
265 | 5 | 'url' => [$url], |
|||
266 | 5 | 'admin_email' => $admin_email, |
|||
267 | 5 | 'language' => $language, |
|||
268 | 5 | 'active_languages' => [$language], |
|||
269 | 5 | 'cookie_domain' => [$domain], |
|||
270 | 5 | 'timezone' => $timezone, |
|||
271 | 5 | 'mail_from' => $admin_email, |
|||
272 | 5 | 'mail_from_name' => "Administrator of $site_name", |
|||
273 | 5 | 'simple_admin_mode' => $mode |
|||
274 | ]; |
||||
275 | $components = [ |
||||
276 | 5 | 'modules' => [ |
|||
277 | 'System' => [ |
||||
278 | 5 | 'active' => Config\Module_Properties::ENABLED, |
|||
279 | 'db' => [ |
||||
280 | 'keys' => 0, |
||||
281 | 'users' => 0, |
||||
282 | 'texts' => 0 |
||||
283 | ] |
||||
284 | ] |
||||
285 | ], |
||||
286 | 'blocks' => [] |
||||
287 | ]; |
||||
288 | 5 | foreach (file_get_json("$source/modules.json") as $module) { |
|||
289 | $components['modules'][$module] = [ |
||||
290 | 'active' => Config\Module_Properties::UNINSTALLED, |
||||
291 | 'db' => [], |
||||
292 | 'storage' => [] |
||||
293 | ]; |
||||
294 | } |
||||
295 | 5 | $result = $cdb->q( |
|||
296 | 5 | "INSERT INTO `[prefix]config` ( |
|||
297 | `domain`, `core`, `db`, `storage`, `components` |
||||
298 | ) VALUES ( |
||||
299 | '%s', '%s', '[]', '[]', '%s' |
||||
300 | )", |
||||
301 | 5 | $domain, |
|||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
302 | 5 | _json_encode($config), |
|||
303 | 5 | _json_encode($components) |
|||
304 | ); |
||||
305 | 5 | if (!$result) { |
|||
0 ignored issues
–
show
|
|||||
306 | throw new RuntimeException("Can't import system configuration into database! Installation aborted."); |
||||
307 | } |
||||
308 | 5 | } |
|||
309 | /** |
||||
310 | * @param DB\_Abstract $cdb |
||||
311 | * @param string $admin_email |
||||
312 | * @param string $admin_password |
||||
313 | * @param string $public_key |
||||
314 | * |
||||
315 | * @throws RuntimeException |
||||
316 | */ |
||||
317 | 5 | protected static function create_root_administrator ($cdb, $admin_email, $admin_password, $public_key) { |
|||
318 | 5 | $admin_email = strtolower($admin_email); |
|||
319 | 5 | $admin_login = strstr($admin_email, '@', true); |
|||
320 | 5 | $result = $cdb->q( |
|||
321 | 5 | "INSERT INTO `[prefix]users` ( |
|||
322 | `login`, `login_hash`, `password_hash`, `email`, `email_hash`, `reg_date`, `reg_ip`, `status` |
||||
323 | ) VALUES ( |
||||
324 | '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' |
||||
325 | )", |
||||
326 | 5 | $admin_login, |
|||
0 ignored issues
–
show
$admin_login of type string is incompatible with the type array expected by parameter $parameters of cs\DB\_Abstract::q() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
327 | 5 | hash('sha224', $admin_login), |
|||
328 | 5 | password_hash(hash('sha512', hash('sha512', $admin_password).$public_key), PASSWORD_DEFAULT), |
|||
329 | 5 | $admin_email, |
|||
330 | 5 | hash('sha224', $admin_email), |
|||
331 | 5 | time(), |
|||
0 ignored issues
–
show
time() of type integer is incompatible with the type array expected by parameter $parameters of cs\DB\_Abstract::q() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
332 | 5 | '127.0.0.1', |
|||
333 | 5 | User::STATUS_ACTIVE |
|||
334 | ); |
||||
335 | 5 | if (!$result) { |
|||
0 ignored issues
–
show
|
|||||
336 | throw new RuntimeException("Can't register root administrator user! Installation aborted."); |
||||
337 | } |
||||
338 | 5 | } |
|||
339 | } |
||||
340 |