Rev 1363 | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1359 | daniel-mar | 1 | <?php |
2 | |||
3 | /* |
||
4 | * OIDplus 2.0 |
||
5 | * Copyright 2019 - 2023 Daniel Marschall, ViaThinkSoft |
||
6 | * |
||
7 | * Licensed under the Apache License, Version 2.0 (the "License"); |
||
8 | * you may not use this file except in compliance with the License. |
||
9 | * You may obtain a copy of the License at |
||
10 | * |
||
11 | * http://www.apache.org/licenses/LICENSE-2.0 |
||
12 | * |
||
13 | * Unless required by applicable law or agreed to in writing, software |
||
14 | * distributed under the License is distributed on an "AS IS" BASIS, |
||
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||
16 | * See the License for the specific language governing permissions and |
||
17 | * limitations under the License. |
||
18 | */ |
||
19 | |||
20 | namespace ViaThinkSoft\OIDplus; |
||
21 | |||
22 | // phpcs:disable PSR1.Files.SideEffects |
||
23 | \defined('INSIDE_OIDPLUS') or die; |
||
24 | // phpcs:enable PSR1.Files.SideEffects |
||
25 | |||
26 | const BACKUP_RECOVERY_SPECIAL_TEST = false; // ONLY FOR TESTING BACKUP/RESTORE PROCEDURE DURING DEVELOPMENT |
||
27 | |||
28 | class OIDplusPageAdminDatabaseBackup extends OIDplusPagePluginAdmin |
||
29 | { |
||
30 | |||
31 | /** |
||
32 | * @param string $id |
||
33 | * @param array $out |
||
34 | * @param bool $handled |
||
35 | * @return void |
||
36 | * @throws OIDplusException |
||
37 | */ |
||
38 | public function gui(string $id, array &$out, bool &$handled) { |
||
39 | $ary = explode('$', $id); |
||
40 | if (isset($ary[1])) { |
||
41 | $id = $ary[0]; |
||
42 | $tab = $ary[1]; |
||
43 | } else { |
||
44 | $tab = 'export'; |
||
45 | } |
||
46 | |||
47 | if ($id === 'oidplus:database_backup') { |
||
48 | $handled = true; |
||
49 | $out['title'] = _L('Database Backup'); |
||
50 | $out['icon'] = file_exists(__DIR__.'/img/main_icon.png') ? OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/main_icon.png' : ''; |
||
51 | |||
52 | if (!OIDplus::authUtils()->isAdminLoggedIn()) { |
||
53 | throw new OIDplusHtmlException(_L('You need to <a %1>log in</a> as administrator.',OIDplus::gui()->link('oidplus:login$admin')), $out['title'], 401); |
||
54 | } |
||
55 | |||
56 | $out['text'] = '<noscript>'; |
||
57 | $out['text'] .= '<p><font color="red">'._L('You need to enable JavaScript to use this feature.').'</font></p>'; |
||
58 | $out['text'] .= '</noscript>'; |
||
59 | |||
60 | $out['text'] .= '<br><div id="databaseBackupArea" style="visibility: hidden"><div id="databaseBackupTab" class="container" style="width:100%;">'; |
||
61 | |||
62 | // ---------------- Tab control |
||
63 | $out['text'] .= OIDplus::gui()->tabBarStart(); |
||
64 | $out['text'] .= OIDplus::gui()->tabBarElement('export', _L('Backup'), $tab === 'export'); |
||
65 | $out['text'] .= OIDplus::gui()->tabBarElement('import', _L('Restore'), $tab === 'import'); |
||
66 | $out['text'] .= OIDplus::gui()->tabBarEnd(); |
||
67 | $out['text'] .= OIDplus::gui()->tabContentStart(); |
||
68 | // ---------------- "Backup" tab |
||
69 | $tabcont = ''; |
||
70 | $tabcont .= '<h2>'._L('Create database backup').'</h2>'; |
||
71 | $tabcont .= '<form action="'.OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'download_backup.php" method="POST" target="_blank">'; |
||
72 | |||
73 | $tabcont .= '<p>'._L('Download a database backup file with the following contents:').'</p>'; |
||
74 | $tabcont .= '<p>'; |
||
75 | $tabcont .= '<input type="checkbox" checked name="database_backup_export_objects" id="database_backup_export_objects"> <label for="database_backup_export_objects">'._L('Objects').'</label><br>'; |
||
76 | $tabcont .= '<input type="checkbox" checked name="database_backup_export_ra" id="database_backup_export_ra"> <label for="database_backup_export_ra">'._L('Registration Authorities').'</label><br>'; |
||
77 | $tabcont .= '<input type="checkbox" checked name="database_backup_export_config" id="database_backup_export_config"> <label for="database_backup_export_config">'._L('Configuration').'</label><br>'; |
||
78 | $tabcont .= '<input type="checkbox" checked name="database_backup_export_log" id="database_backup_export_log"> <label for="database_backup_export_log">'._L('Log messages').'</label><br>'; |
||
79 | $tabcont .= '<input type="checkbox" name="database_backup_export_pki" id="database_backup_export_pki"> <label for="database_backup_export_pki">'._L('Private key (Highly confidential!)').'</label><br>'; |
||
80 | $tabcont .= '</p>'; |
||
81 | |||
82 | $tabcont .= '<p>'; |
||
83 | $tabcont .= '<input type="checkbox" name="database_backup_export_encrypt" id="database_backup_export_encrypt"> <label for="database_backup_export_encrypt">'._L('Encrypt backup file with the following password (optional)').':</label><br>'; |
||
84 | $tabcont .= '<label style="margin-left:25px;width:160px" for="database_backup_export_password1">'._L('Password').':</label> <input type="password" name="database_backup_export_password1" id="database_backup_export_password1"><br>'; |
||
85 | $tabcont .= '<label style="margin-left:25px;width:160px" for="database_backup_export_password2">'._L('Password (repeat)').':</label> <input type="password" name="database_backup_export_password2" id="database_backup_export_password2"><br>'; |
||
86 | $tabcont .= '</p>'; |
||
87 | |||
88 | $tabcont .= '<input type="submit" value="'._L('Download backup').'">'; |
||
89 | |||
90 | $tabcont .= '<p><i>'._L('Attention: Some Database Management Systems (DBMS), OIDplus connectivity plugins, and OIDplus SQL Slang plugins might export and import data differently regarding NULL values, time zones, boolean values, Unicode characters, etc. Please use backup/restore with caution and consider testing the restore procedure on a staging environment first.').'</i></p>'; |
||
91 | |||
92 | $tabcont .= '</form>'; |
||
93 | $out['text'] .= OIDplus::gui()->tabContentPage('export', $tabcont, $tab === 'export'); |
||
94 | // ---------------- "Restore" tab |
||
95 | |||
96 | $tabcont = ''; |
||
97 | $tabcont .= '<h2>'._L('Restore database backup').'</h2>'; |
||
98 | $tabcont .= '<form action="'.OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'restore_backup.php" method="POST" target="_blank" enctype="multipart/form-data">'; |
||
99 | |||
100 | $tabcont .= '<p>'._L('Choose database backup file here').':<br><input type="file" name="userfile" value="" id="userfile"></p>'; |
||
101 | |||
102 | $tabcont .= '<p>'._L('Restore the following contents from the backup file (existing data will be deleted):').'</p>'; |
||
103 | $tabcont .= '<p>'; |
||
104 | $tabcont .= '<input type="checkbox" checked name="database_backup_import_objects" id="database_backup_import_objects"> <label for="database_backup_import_objects">'._L('Objects').'</label><br>'; |
||
105 | $tabcont .= '<input type="checkbox" checked name="database_backup_import_ra" id="database_backup_import_ra"> <label for="database_backup_import_ra">'._L('Registration Authorities').'</label><br>'; |
||
106 | $tabcont .= '<input type="checkbox" checked name="database_backup_import_config" id="database_backup_import_config"> <label for="database_backup_import_config">'._L('Configuration').'</label><br>'; |
||
107 | $tabcont .= '<input type="checkbox" checked name="database_backup_import_log" id="database_backup_import_log"> <label for="database_backup_import_log">'._L('Log messages').'</label><br>'; |
||
108 | $tabcont .= '<input type="checkbox" name="database_backup_import_pki" id="database_backup_import_pki"> <label for="database_backup_import_pki">'._L('Private key').' *</label><br>'; |
||
109 | $tabcont .= '</p>'; |
||
110 | |||
111 | $tabcont .= '<p>'; |
||
112 | $tabcont .= '<label for="database_backup_import_encrypt">'._L('In case the backup is encrypted, enter the decryption password here').':</label><br>'; |
||
113 | $tabcont .= '<label style="margin-left:25px;width:160px" for="database_backup_import_password">'._L('Password').':</label> <input type="password" name="database_backup_import_password" id="database_backup_import_password"><br>'; |
||
114 | $tabcont .= '</p>'; |
||
115 | |||
116 | $tabcont .= '<input type="submit" value="'._L('Restore backup').'">'; |
||
117 | |||
118 | $tabcont .= '<p>* <i>'._L('Attention: In case you are cloning a system, e.g. in order to create a staging environment, please DO NOT copy the private key, as this would cause two systems to have the same System ID.').'</i></p>'; |
||
119 | |||
120 | |||
121 | $tabcont .= '</form>'; |
||
122 | $out['text'] .= OIDplus::gui()->tabContentPage('import', $tabcont, $tab === 'import'); |
||
123 | $out['text'] .= OIDplus::gui()->tabContentEnd(); |
||
124 | // ---------------- Tab control END |
||
125 | |||
126 | $out['text'] .= '</div></div><script>$("#databaseBackupArea")[0].style.visibility = "visible";</script>'; |
||
127 | } |
||
128 | } |
||
129 | |||
130 | /** |
||
131 | * @param array $json |
||
132 | * @param string|null $ra_email |
||
133 | * @param bool $nonjs |
||
134 | * @param string $req_goto |
||
135 | * @return bool |
||
136 | * @throws OIDplusException |
||
137 | */ |
||
138 | public function tree(array &$json, string $ra_email=null, bool $nonjs=false, string $req_goto=''): bool { |
||
139 | if (!OIDplus::authUtils()->isAdminLoggedIn()) return false; |
||
140 | |||
141 | if (file_exists(__DIR__.'/img/main_icon16.png')) { |
||
142 | $tree_icon = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/main_icon16.png'; |
||
143 | } else { |
||
144 | $tree_icon = null; // default icon (folder) |
||
145 | } |
||
146 | |||
147 | $json[] = array( |
||
148 | 'id' => 'oidplus:database_backup', |
||
149 | 'icon' => $tree_icon, |
||
150 | 'text' => _L('Database Backup') |
||
151 | ); |
||
152 | |||
153 | return true; |
||
154 | } |
||
155 | |||
156 | /** |
||
157 | * @param string $request |
||
158 | * @return array|false |
||
159 | */ |
||
160 | public function tree_search(string $request) { |
||
161 | return false; |
||
162 | } |
||
163 | |||
164 | /** |
||
165 | * @param array $num_rows |
||
166 | * @return string |
||
167 | */ |
||
168 | private static function num_rows_list(array $num_rows): string { |
||
169 | $ary2 = []; |
||
170 | foreach ($num_rows as $table => $cnt) { |
||
171 | if ($cnt !== "n/a") $ary2[] = "$table=$cnt"; |
||
172 | } |
||
173 | $out = implode(", ", $ary2); |
||
174 | |||
175 | if ($out === '') $out = 'No tables selected'; |
||
176 | return $out; |
||
177 | } |
||
178 | |||
179 | /** |
||
1363 | daniel-mar | 180 | * @param mixed|string|null $datetime |
181 | * @return mixed|string|null |
||
182 | */ |
||
183 | private static function fix_datetime_for_output($datetime) { |
||
184 | if ($datetime === "0000-00-00 00:00:00") $datetime = null; // MySQL might use this as default instead of NULL... But SQL Server cannot read this. |
||
185 | |||
186 | if (is_string($datetime) && (substr($datetime,4,1) !== '-')) { |
||
187 | // Let's hope PHP can convert the database language specific string to ymd |
||
188 | $time = @strtotime($datetime); |
||
189 | if ($time) { |
||
190 | $date = date('Y-m-d H:i:s', $time); |
||
191 | if ($date) { |
||
192 | $datetime = $date; |
||
193 | } |
||
194 | } |
||
195 | } |
||
196 | return $datetime; |
||
197 | } |
||
198 | |||
199 | /** |
||
1359 | daniel-mar | 200 | * @param bool $showReport |
201 | * @param bool $export_objects |
||
202 | * @param bool $export_ra |
||
203 | * @param bool $export_config |
||
204 | * @param bool $export_log |
||
205 | * @param bool $export_pki |
||
206 | * @return string |
||
207 | * @throws OIDplusException |
||
208 | * @throws \ViaThinkSoft\OIDplus\OIDplusConfigInitializationException |
||
209 | */ |
||
210 | public static function createBackup(bool $showReport, bool $export_objects=true, bool $export_ra=true, bool $export_config=false, bool $export_log=false, bool $export_pki=false): string { |
||
211 | $num_rows = [ |
||
212 | "objects" => $export_objects ? 0 : "n/a", |
||
213 | "asn1id" => $export_objects ? 0 : "n/a", |
||
214 | "iri" => $export_objects ? 0 : "n/a", |
||
215 | "ra" => $export_ra ? 0 : "n/a", |
||
216 | "config" => $export_config ? 0 : "n/a", |
||
217 | "log" => $export_log ? 0 : "n/a", |
||
218 | "log_object" => $export_log ? 0 : "n/a", |
||
219 | "log_user" => $export_log ? 0 : "n/a", |
||
220 | "pki" => $export_pki ? 0 : "n/a" |
||
221 | ]; |
||
222 | |||
223 | if (BACKUP_RECOVERY_SPECIAL_TEST) { |
||
224 | if ($export_objects) { |
||
225 | OIDplus::db()->query("delete from ###objects where id like '%_CLONE'"); |
||
226 | OIDplus::db()->query("delete from ###asn1id where oid like '%_CLONE'"); |
||
227 | OIDplus::db()->query("delete from ###iri where oid like '%_CLONE'"); |
||
228 | } |
||
229 | if ($export_ra) { |
||
230 | OIDplus::db()->query("delete from ###ra where email like '%_CLONE'"); |
||
231 | } |
||
232 | if ($export_config) { |
||
233 | OIDplus::db()->query("delete from ###config where name <> 'oidplus_private_key' and name <> 'oidplus_public_key' and name like '%_CLONE'"); |
||
234 | } |
||
235 | if ($export_log) { |
||
236 | OIDplus::db()->query("delete from ###log where addr like '%_CLONE'"); |
||
237 | OIDplus::db()->query("delete from ###log_object where object like '%_CLONE'"); |
||
238 | OIDplus::db()->query("delete from ###log_user where username like '%_CLONE'"); |
||
239 | } |
||
240 | } |
||
241 | |||
242 | // Backup objects (Tables objects, asn1id, iri) |
||
243 | $objects = []; |
||
244 | if ($export_objects) { |
||
245 | $res = OIDplus::db()->query("select * from ###objects order by id"); |
||
246 | $rows = []; |
||
247 | while ($row = $res->fetch_array()) { |
||
248 | // Not all databases support multiple active rows, so we need to read it in a isolated loop |
||
249 | $rows[] = $row; |
||
250 | } |
||
251 | foreach ($rows as $row) { |
||
252 | $num_rows["objects"]++; |
||
253 | |||
254 | $asn1ids = []; |
||
255 | $res2 = OIDplus::db()->query("select * from ###asn1id where oid = ? order by name", array($row["id"])); |
||
256 | while ($row2 = $res2->fetch_array()) { |
||
257 | $num_rows["asn1id"]++; |
||
258 | $asn1ids[] = [ |
||
259 | "name" => $row2['name'], |
||
1363 | daniel-mar | 260 | "standardized" => $row2['standardized'] ?? false, |
261 | "well_known" => $row2['well_known'] ?? false, |
||
1359 | daniel-mar | 262 | ]; |
263 | } |
||
264 | |||
265 | $iris = []; |
||
266 | $res2 = OIDplus::db()->query("select * from ###iri where oid = ? order by name", array($row["id"])); |
||
267 | while ($row2 = $res2->fetch_array()) { |
||
268 | $num_rows["iri"]++; |
||
269 | $iris[] = [ |
||
270 | "name" => $row2['name'], |
||
1363 | daniel-mar | 271 | "longarc" => $row2['longarc'] ?? false, |
272 | "well_known" => $row2['well_known'] ?? false, |
||
1359 | daniel-mar | 273 | ]; |
274 | } |
||
275 | |||
276 | $objects[] = [ |
||
277 | "id" => $row["id"], |
||
278 | "parent" => $row["parent"], |
||
279 | "title" => $row["title"], |
||
280 | "description" => $row["description"], |
||
281 | "ra_email" => $row["ra_email"], |
||
1363 | daniel-mar | 282 | "confidential" => $row["confidential"] ?? false, |
283 | "created" => self::fix_datetime_for_output($row["created"]), |
||
284 | "updated" => self::fix_datetime_for_output($row["updated"]), |
||
1359 | daniel-mar | 285 | "comment" => $row["comment"], |
286 | "asn1ids" => $asn1ids, |
||
287 | "iris" => $iris |
||
288 | ]; |
||
289 | } |
||
290 | } |
||
291 | |||
292 | // Backup RAs (Table ra) |
||
293 | $ra = []; |
||
294 | if ($export_ra) { |
||
295 | $res = OIDplus::db()->query("select * from ###ra order by email"); |
||
296 | while ($row = $res->fetch_array()) { |
||
297 | $num_rows["ra"]++; |
||
298 | $ra[] = [ |
||
299 | "email" => $row["email"], |
||
300 | "ra_name" => $row["ra_name"], |
||
301 | "personal_name" => $row["personal_name"], |
||
302 | "organization" => $row["organization"], |
||
303 | "office" => $row["office"], |
||
304 | "street" => $row["street"], |
||
305 | "zip_town" => $row["zip_town"], |
||
306 | "country" => $row["country"], |
||
307 | "phone" => $row["phone"], |
||
308 | "mobile" => $row["mobile"], |
||
309 | "fax" => $row["fax"], |
||
1363 | daniel-mar | 310 | "privacy" => $row["privacy"] ?? false, |
1359 | daniel-mar | 311 | "authkey" => $row["authkey"], |
1363 | daniel-mar | 312 | "registered" => self::fix_datetime_for_output($row["registered"]), |
313 | "updated" => self::fix_datetime_for_output($row["updated"]), |
||
314 | "last_login" => self::fix_datetime_for_output($row["last_login"]) |
||
1359 | daniel-mar | 315 | ]; |
316 | } |
||
317 | } |
||
318 | |||
319 | // Backup configuration (Table config) |
||
320 | $config = []; |
||
321 | if ($export_config) { |
||
322 | $res = OIDplus::db()->query("select * from ###config where name <> 'oidplus_private_key' and name <> 'oidplus_public_key' order by name"); |
||
323 | while ($row = $res->fetch_array()) { |
||
324 | $num_rows["config"]++; |
||
325 | $config[] = [ |
||
326 | "name" => $row["name"], |
||
327 | "value" => $row["value"], |
||
328 | "description" => $row["description"], |
||
1363 | daniel-mar | 329 | "protected" => $row["protected"] ?? false, |
330 | "visible" => $row["visible"] ?? false |
||
1359 | daniel-mar | 331 | ]; |
332 | } |
||
333 | } |
||
334 | |||
335 | // Backup logs (Tables log, log_object, log_user) |
||
336 | $log = []; |
||
337 | if ($export_log) { |
||
338 | $res = OIDplus::db()->query("select * from ###log order by id"); |
||
339 | $rows = []; |
||
340 | while ($row = $res->fetch_array()) { |
||
341 | // Not all databases support multiple active rows, so we need to read it in a isolated loop |
||
342 | $rows[] = $row; |
||
343 | } |
||
344 | foreach ($rows as $row) { |
||
345 | $num_rows["log"]++; |
||
346 | |||
347 | $log_objects = []; |
||
348 | $res2 = OIDplus::db()->query("select * from ###log_object where log_id = ? order by id", array($row["id"])); |
||
349 | while ($row2 = $res2->fetch_array()) { |
||
350 | $num_rows["log_object"]++; |
||
351 | $log_objects[] = [ |
||
352 | "object" => $row2['object'], |
||
1363 | daniel-mar | 353 | "severity" => $row2['severity'] ?? 0 |
1359 | daniel-mar | 354 | ]; |
355 | } |
||
356 | |||
357 | $log_users = []; |
||
358 | $res2 = OIDplus::db()->query("select * from ###log_user where log_id = ? order by id", array($row["id"])); |
||
359 | while ($row2 = $res2->fetch_array()) { |
||
360 | $num_rows["log_user"]++; |
||
361 | $log_users[] = [ |
||
362 | "username" => $row2['username'], |
||
1363 | daniel-mar | 363 | "severity" => $row2['severity'] ?? 0 |
1359 | daniel-mar | 364 | ]; |
365 | } |
||
366 | |||
367 | $log[] = [ |
||
368 | "unix_ts" => $row["unix_ts"], |
||
369 | "addr" => $row["addr"], |
||
370 | "event" => $row["event"], |
||
371 | "objects" => $log_objects, |
||
372 | "users" => $log_users |
||
373 | ]; |
||
374 | } |
||
375 | } |
||
376 | |||
377 | // Backup public/private key |
||
378 | $pki = []; |
||
379 | if ($export_pki) { |
||
380 | $num_rows["pki"]++; |
||
381 | $pki[] = [ |
||
382 | "private_key" => OIDplus::getSystemPrivateKey(), |
||
383 | "public_key" => OIDplus::getSystemPublicKey() |
||
384 | ]; |
||
385 | } |
||
386 | |||
387 | // Put everything together |
||
388 | $json = [ |
||
389 | "\$schema" => "urn:oid:1.3.6.1.4.1.37476.2.5.2.8.1.1", |
||
390 | "oidplus_backup" => [ |
||
391 | "created" => date('Y-m-d H:i:s O'), |
||
392 | "origin_systemid" => ($sysid = OIDplus::getSystemId(false)) ? (int)$sysid : "unknown", |
||
393 | "dataset_count" => $num_rows |
||
394 | ], |
||
395 | "objects" => $objects, |
||
396 | "ra" => $ra, |
||
397 | "config" => $config, |
||
398 | "log" => $log, |
||
399 | "pki" => $pki |
||
400 | ]; |
||
401 | |||
402 | // Done! |
||
403 | |||
404 | $encoded_data = json_encode($json, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); |
||
1363 | daniel-mar | 405 | if ($encoded_data === false) { |
406 | // Some DBMS plugins might not output UTF-8 correctly. In my test case it was SQL Server on ADO/MSOLEDBSQL (where Unicode does not work in OIDplus for some unknown reason) |
||
407 | array_walk_recursive($json, function (&$value) |
||
408 | { |
||
409 | if (is_string($value)) $value = vts_utf8_encode($value); |
||
410 | }); |
||
411 | $encoded_data = json_encode($json, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); |
||
412 | if ($encoded_data === false) { |
||
413 | throw new OIDplusException(_L("%1 failed","json_encode")); |
||
414 | } |
||
415 | } |
||
1359 | daniel-mar | 416 | |
417 | OIDplus::logger()->log("V2:[INFO]A", "Created backup: ".self::num_rows_list($num_rows)); |
||
418 | |||
419 | if ($showReport) { |
||
420 | echo "<h1>"._L('Backup done')."</h1>"; |
||
421 | foreach ($num_rows as $table_name => $cnt) { |
||
422 | if ($cnt !== "n/a") echo "<p>... $table_name: "._L('%1 datasets', $cnt)."</p>"; |
||
423 | } |
||
424 | echo "<hr>"; |
||
425 | } |
||
426 | |||
427 | return $encoded_data; |
||
428 | } |
||
429 | |||
430 | /** |
||
431 | * @param bool $showReport |
||
432 | * @param string $cont |
||
433 | * @param bool $import_objects |
||
434 | * @param bool $import_ra |
||
435 | * @param bool $import_config |
||
436 | * @param bool $import_log |
||
437 | * @param bool $import_pki |
||
438 | * @return void |
||
439 | * @throws OIDplusException |
||
440 | * @throws \ViaThinkSoft\OIDplus\OIDplusConfigInitializationException |
||
441 | */ |
||
442 | public static function restoreBackup(bool $showReport, string $cont, bool $import_objects=true, bool $import_ra=true, bool $import_config=false, bool $import_log=false, bool $import_pki=false)/*: void*/ { |
||
443 | $num_rows = [ |
||
444 | "objects" => $import_objects ? 0 : "n/a", |
||
445 | "asn1id" => $import_objects ? 0 : "n/a", |
||
446 | "iri" => $import_objects ? 0 : "n/a", |
||
447 | "ra" => $import_ra ? 0 : "n/a", |
||
448 | "config" => $import_config ? 0 : "n/a", |
||
449 | "log" => $import_log ? 0 : "n/a", |
||
450 | "log_object" => $import_log ? 0 : "n/a", |
||
451 | "log_user" => $import_log ? 0 : "n/a", |
||
452 | "pki" => $import_pki ? 0 : "n/a" |
||
453 | ]; |
||
454 | |||
455 | //$cont = @file_get_contents($backup_file); |
||
456 | //if ($cont === false) throw new OIDplusException(_L("Could not read file from disk: %1", $backup_file)); |
||
457 | $json = @json_decode($cont,true); |
||
458 | if ($json === false) throw new OIDplusException(_L("Could not decode JSON structure of backup file")); |
||
459 | |||
460 | if (($json["\$schema"]??"") != "urn:oid:1.3.6.1.4.1.37476.2.5.2.8.1.1") { |
||
461 | throw new OIDplusException(_L("File cannot be restored, because it has a wrong file format (schema)")); |
||
462 | } |
||
463 | |||
464 | if ($import_objects) { |
||
465 | $tmp = $json["oidplus_backup"]["dataset_count"]["objects"] ?? "n/a"; |
||
466 | if ($tmp === "n/a") { |
||
467 | throw new OIDplusException(_L('Backup cannot be restored, because you want to import "%1", but the file was not created with this data.',"objects")); |
||
468 | } |
||
469 | |||
470 | $cnt = count($json["objects"]??[]); |
||
471 | if ($tmp != $cnt) { |
||
472 | throw new OIDplusException(_L('Backup cannot be restored, because the number of "%1" does not match',"objects")); |
||
473 | } |
||
474 | |||
475 | $tmp = $json["oidplus_backup"]["dataset_count"]["asn1id"] ?? "n/a"; |
||
476 | $cnt_asn1id = 0; |
||
477 | foreach (($json["objects"]??[]) as $row) { |
||
478 | $cnt_asn1id += count($row['asn1ids']??[]); |
||
479 | } |
||
480 | if ($tmp != $cnt_asn1id) { |
||
481 | throw new OIDplusException(_L('Backup cannot be restored, because the number of "%1" does not match',"asn1id")); |
||
482 | } |
||
483 | |||
484 | $tmp = $json["oidplus_backup"]["dataset_count"]["iri"] ?? "n/a"; |
||
485 | $cnt_iri = 0; |
||
486 | foreach (($json["objects"]??[]) as $row) { |
||
487 | $cnt_iri += count($row['iris']??[]); |
||
488 | } |
||
489 | if ($tmp != $cnt_iri) { |
||
490 | throw new OIDplusException(_L('Backup cannot be restored, because the number of "%1" does not match',"iri")); |
||
491 | } |
||
492 | } |
||
493 | |||
494 | if ($import_ra) { |
||
495 | $tmp = $json["oidplus_backup"]["dataset_count"]["ra"] ?? "n/a"; |
||
496 | if ($tmp === "n/a") { |
||
497 | throw new OIDplusException(_L('Backup cannot be restored, because you want to import "%1", but the file was not created with this data.',"ra")); |
||
498 | } |
||
499 | $cnt = count($json["ra"]??[]); |
||
500 | if ($tmp != $cnt) { |
||
501 | throw new OIDplusException(_L('Backup cannot be restored, because the number of "%1" does not match',"ra")); |
||
502 | } |
||
503 | } |
||
504 | |||
505 | if ($import_config) { |
||
506 | $tmp = $json["oidplus_backup"]["dataset_count"]["config"] ?? "n/a"; |
||
507 | if ($tmp === "n/a") { |
||
508 | throw new OIDplusException(_L('Backup cannot be restored, because you want to import "%1", but the file was not created with this data.',"config")); |
||
509 | } |
||
510 | $cnt = count($json["config"]??[]); |
||
511 | if ($tmp != $cnt) { |
||
512 | throw new OIDplusException(_L('Backup cannot be restored, because the number of "%1" does not match',"config")); |
||
513 | } |
||
514 | } |
||
515 | |||
516 | if ($import_log) { |
||
517 | $tmp = $json["oidplus_backup"]["dataset_count"]["log"] ?? "n/a"; |
||
518 | if ($tmp === "n/a") { |
||
519 | throw new OIDplusException(_L('Backup cannot be restored, because you want to import "%1", but the file was not created with this data.',"log")); |
||
520 | } |
||
521 | |||
522 | $cnt = count($json["log"]??[]); |
||
523 | if ($tmp != $cnt) { |
||
524 | throw new OIDplusException(_L('Backup cannot be restored, because the number of "%1" does not match',"log")); |
||
525 | } |
||
526 | |||
527 | $tmp = $json["oidplus_backup"]["dataset_count"]["log_object"] ?? "n/a"; |
||
528 | $cnt_objects = 0; |
||
529 | foreach (($json["log"]??[]) as $row) { |
||
530 | $cnt_objects += count($row['objects']??[]); |
||
531 | } |
||
532 | if ($tmp != $cnt_objects) { |
||
533 | throw new OIDplusException(_L('Backup cannot be restored, because the number of "%1" does not match',"log_object")); |
||
534 | } |
||
535 | |||
536 | $tmp = $json["oidplus_backup"]["dataset_count"]["log_user"] ?? "n/a"; |
||
537 | $cnt_users = 0; |
||
538 | foreach (($json["log"]??[]) as $row) { |
||
539 | $cnt_users += count($row['users']??[]); |
||
540 | } |
||
541 | if ($tmp != $cnt_users) { |
||
542 | throw new OIDplusException(_L('Backup cannot be restored, because the number of "%1" does not match',"log_user")); |
||
543 | } |
||
544 | } |
||
545 | |||
546 | if ($import_pki) { |
||
547 | $tmp = $json["oidplus_backup"]["dataset_count"]["pki"] ?? "n/a"; |
||
548 | if ($tmp === "n/a") { |
||
549 | throw new OIDplusException(_L('Backup cannot be restored, because you want to import "%1", but the file was not created with this data.',"pki")); |
||
550 | } |
||
551 | if (($tmp !== 0) && ($tmp !== 1)) { |
||
552 | throw new OIDplusException(_L('Backup cannot be restored, because the number of "%1" is invalid',"pki")); |
||
553 | } |
||
554 | $cnt = count($json["pki"]??[]); |
||
555 | if ($tmp != $cnt) { |
||
556 | throw new OIDplusException(_L('Backup cannot be restored, because the number of "%1" does not match',"pki")); |
||
557 | } |
||
558 | } |
||
559 | |||
1363 | daniel-mar | 560 | if (OIDplus::db()->getSlang()->id() == 'mssql') { |
561 | // MSSQL: Try to find out if the other system created in YMD format |
||
562 | $has_ymd_format = false; |
||
563 | foreach (($json["objects"]??[]) as $row) { |
||
564 | if (substr($row["created"]??'',4,1) === '-') $has_ymd_format = true; |
||
565 | if (substr($row["updated"]??'',4,1) === '-') $has_ymd_format = true; |
||
566 | |||
567 | } |
||
568 | foreach (($json["ra"]??[]) as $row) { |
||
569 | if (substr($row["registered"]??'',4,1) === '-') $has_ymd_format = true; |
||
570 | if (substr($row["updated"]??'',4,1) === '-') $has_ymd_format = true; |
||
571 | if (substr($row["last_login"]??'',4,1) === '-') $has_ymd_format = true; |
||
572 | } |
||
573 | if ($has_ymd_format) { |
||
574 | OIDplus::db()->query("SET DATEFORMAT ymd;"); |
||
575 | } |
||
576 | |||
577 | // Convert "0000-00-00 00:00:00" (MySQL) to NULL |
||
578 | if (isset($json["objects"])) { |
||
579 | foreach ($json["objects"] as &$row) { |
||
580 | if ($row["created"] === "0000-00-00 00:00:00") $row["created"] = null; |
||
581 | if ($row["updated"] === "0000-00-00 00:00:00") $row["updated"] = null; |
||
582 | } |
||
583 | unset($row); |
||
584 | } |
||
585 | if (isset($json["ra"])) { |
||
586 | foreach ($json["ra"] as &$row) { |
||
587 | if ($row["registered"] === "0000-00-00 00:00:00") $row["registered"] = null; |
||
588 | if ($row["updated"] === "0000-00-00 00:00:00") $row["updated"] = null; |
||
589 | if ($row["last_login"] === "0000-00-00 00:00:00") $row["last_login"] = null; |
||
590 | } |
||
591 | unset($row); |
||
592 | } |
||
593 | } |
||
594 | |||
1359 | daniel-mar | 595 | if (OIDplus::db()->transaction_supported()) OIDplus::db()->transaction_begin(); |
596 | try { |
||
597 | |||
598 | // Restore objects (Tables objects, asn1id, iri) |
||
599 | if ($import_objects) { |
||
600 | if (!BACKUP_RECOVERY_SPECIAL_TEST) { |
||
601 | OIDplus::db()->query("delete from ###objects"); |
||
602 | OIDplus::db()->query("delete from ###asn1id"); |
||
603 | OIDplus::db()->query("delete from ###iri"); |
||
604 | } |
||
605 | foreach (($json["objects"]??[]) as $row) { |
||
606 | if (BACKUP_RECOVERY_SPECIAL_TEST) { |
||
607 | $row['id'] .= '_CLONE'; |
||
608 | if (substr($row['parent'], -1) != ':') $row['parent'] .= '_CLONE'; |
||
609 | } |
||
610 | |||
611 | $num_rows["objects"]++; |
||
612 | OIDplus::db()->query("insert into ###objects (id, parent, title, description, ra_email, confidential, created, updated, comment) values (?, ?, ?, ?, ?, ?, ?, ?, ?)", |
||
613 | array($row["id"]??null, |
||
614 | $row["parent"]??null, |
||
615 | $row["title"]??null, |
||
616 | $row["description"]??null, |
||
617 | $row["ra_email"]??null, |
||
1452 | daniel-mar | 618 | (bool)($row["confidential"]??false), |
619 | $row["created"]??'1900-01-01 00:00:00', |
||
620 | $row["updated"]??'1900-01-01 00:00:00', |
||
1359 | daniel-mar | 621 | $row["comment"]??null) |
622 | ); |
||
623 | |||
624 | foreach (($row["asn1ids"]??[]) as $row2) { |
||
625 | $num_rows["asn1id"]++; |
||
626 | OIDplus::db()->query("insert into ###asn1id (oid, name, standardized, well_known) values (?, ?, ?, ?)", |
||
627 | array($row["id"]??null, // sic: $row, not $row2 |
||
628 | $row2["name"]??null, |
||
1452 | daniel-mar | 629 | (bool)($row2["standardized"]??false), |
630 | (bool)($row2["well_known"]??false)) |
||
1359 | daniel-mar | 631 | ); |
632 | } |
||
633 | |||
634 | foreach (($row["iris"]??[]) as $row2) { |
||
635 | $num_rows["iri"]++; |
||
636 | OIDplus::db()->query("insert into ###iri (oid, name, longarc, well_known) values (?, ?, ?, ?)", |
||
637 | array($row["id"]??null, // sic: $row, not $row2 |
||
638 | $row2["name"]??null, |
||
1452 | daniel-mar | 639 | (bool)($row2["longarc"]??false), |
640 | (bool)($row2["well_known"]??false)) |
||
1359 | daniel-mar | 641 | ); |
642 | } |
||
643 | } |
||
1452 | daniel-mar | 644 | OIDplus::db()->query("update ###objects set created = null where created = '1900-01-01 00:00:00';"); |
645 | OIDplus::db()->query("update ###objects set updated = null where updated = '1900-01-01 00:00:00';"); |
||
1359 | daniel-mar | 646 | } |
647 | |||
648 | // Restore RAs (Table ra) |
||
649 | if ($import_ra) { |
||
650 | if (!BACKUP_RECOVERY_SPECIAL_TEST) { |
||
651 | OIDplus::db()->query("delete from ###ra"); |
||
652 | } |
||
653 | foreach (($json["ra"]??[]) as $row) { |
||
654 | if (BACKUP_RECOVERY_SPECIAL_TEST) { |
||
655 | $row['email'] .= '_CLONE'; |
||
656 | } |
||
657 | |||
658 | $num_rows["ra"]++; |
||
659 | OIDplus::db()->query("insert into ###ra (email, ra_name, personal_name, organization, office, street, zip_town, country, phone, mobile, fax, privacy, authkey, registered, updated, last_login) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", |
||
660 | array($row["email"]??null, |
||
661 | $row["ra_name"]??null, |
||
662 | $row["personal_name"]??null, |
||
663 | $row["organization"]??null, |
||
664 | $row["office"]??null, |
||
665 | $row["street"]??null, |
||
666 | $row["zip_town"]??null, |
||
667 | $row["country"]??null, |
||
668 | $row["phone"]??null, |
||
669 | $row["mobile"]??null, |
||
670 | $row["fax"]??null, |
||
1452 | daniel-mar | 671 | (bool)($row["privacy"]??false), |
1359 | daniel-mar | 672 | $row["authkey"]??null, |
1452 | daniel-mar | 673 | $row["registered"]??'1900-01-01 00:00:00', |
674 | $row["updated"]??'1900-01-01 00:00:00', |
||
675 | $row["last_login"]??'1900-01-01 00:00:00') |
||
1359 | daniel-mar | 676 | ); |
677 | } |
||
1452 | daniel-mar | 678 | OIDplus::db()->query("update ###ra set registered = null where registered = '1900-01-01 00:00:00';"); |
679 | OIDplus::db()->query("update ###ra set updated = null where updated = '1900-01-01 00:00:00';"); |
||
680 | OIDplus::db()->query("update ###ra set last_login = null where last_login = '1900-01-01 00:00:00';"); |
||
1359 | daniel-mar | 681 | } |
682 | |||
683 | // Restore configuration (Table config) |
||
684 | if ($import_config) { |
||
685 | if (!BACKUP_RECOVERY_SPECIAL_TEST) { |
||
686 | OIDplus::db()->query("delete from ###config where name <> 'oidplus_private_key' and name <> 'oidplus_public_key'"); |
||
687 | } |
||
688 | |||
689 | foreach (($json["config"]??[]) as $row) { |
||
690 | if (BACKUP_RECOVERY_SPECIAL_TEST) { |
||
691 | $row['name'] .= '_CLONE'; |
||
692 | } |
||
693 | |||
694 | $num_rows["config"]++; |
||
695 | OIDplus::db()->query("insert into ###config (name, value, description, protected, visible) values (?, ?, ?, ?, ?)", |
||
696 | array($row["name"]??null, |
||
697 | $row["value"]??null, |
||
698 | $row["description"]??null, |
||
1452 | daniel-mar | 699 | (bool)($row["protected"]??false), |
700 | (bool)($row["visible"]??false)) |
||
1359 | daniel-mar | 701 | ); |
702 | } |
||
703 | |||
704 | } |
||
705 | |||
706 | // Restore logs (Tables log, log_object, log_user) |
||
707 | if ($import_log) { |
||
708 | if (!BACKUP_RECOVERY_SPECIAL_TEST) { |
||
709 | OIDplus::db()->query("delete from ###log"); |
||
710 | OIDplus::db()->query("delete from ###log_object"); |
||
711 | OIDplus::db()->query("delete from ###log_user"); |
||
712 | } |
||
713 | foreach (($json["log"]??[]) as $row) { |
||
714 | if (BACKUP_RECOVERY_SPECIAL_TEST) { |
||
715 | $row['addr'] .= '_CLONE'; |
||
716 | } |
||
717 | |||
718 | $num_rows["log"]++; |
||
719 | OIDplus::db()->query("insert into ###log (unix_ts, addr, event) values (?, ?, ?)", |
||
720 | array($row["unix_ts"]??null, |
||
721 | $row["addr"]??null, |
||
722 | $row["event"]??null) |
||
723 | ); |
||
724 | $row['id'] = OIDplus::db()->insert_id(); |
||
725 | if ($row['id'] <= 0) { |
||
726 | throw new OIDplusException(_L("Error during restore of backup: Cannot get insert_id of log entry!")); |
||
727 | } |
||
728 | |||
729 | foreach (($row["objects"]??[]) as $row2) { |
||
730 | $num_rows["log_object"]++; |
||
731 | OIDplus::db()->query("insert into ###log_object (log_id, object, severity) values (?, ?, ?)", |
||
732 | array($row["id"], // sic: $row, not $row2 |
||
733 | $row2["object"]??null, |
||
1363 | daniel-mar | 734 | $row2["severity"]??0) |
1359 | daniel-mar | 735 | ); |
736 | } |
||
737 | |||
738 | foreach (($row["users"]??[]) as $row2) { |
||
739 | $num_rows["log_user"]++; |
||
740 | OIDplus::db()->query("insert into ###log_user (log_id, username, severity) values (?, ?, ?)", |
||
741 | array($row["id"], // sic: $row, not $row2 |
||
742 | $row2["username"]??null, |
||
1363 | daniel-mar | 743 | $row2["severity"]??0) |
1359 | daniel-mar | 744 | ); |
745 | } |
||
746 | } |
||
747 | } |
||
748 | |||
749 | // Restore public/private key |
||
750 | if ($import_pki) { |
||
751 | $privkey = $json["pki"][0]["private_key"] ?? null; |
||
752 | $pubkey = $json["pki"][0]["public_key"] ?? null; |
||
753 | if ($privkey && $pubkey) { |
||
754 | $num_rows["pki"]++; |
||
755 | // Note: If the private key is not encrypted, then it will be re-encrypted during the next call of OIDplus::getPkiStatus() |
||
756 | OIDplus::db()->query("update ###config set value = ? where name = 'oidplus_private_key'", [$privkey]); |
||
757 | OIDplus::db()->query("update ###config set value = ? where name = 'oidplus_public_key'", [$pubkey]); |
||
758 | OIDplus::config()->clearCache(); |
||
759 | } |
||
760 | } |
||
761 | |||
762 | // Done! |
||
763 | |||
764 | OIDplus::logger()->log("V2:[WARN]A", "EXECUTED OBJECT AND RA DATABASE BACKUP RECOVERY: ".self::num_rows_list($num_rows)); |
||
765 | |||
766 | if (OIDplus::db()->transaction_supported()) OIDplus::db()->transaction_commit(); |
||
767 | |||
768 | if ($showReport) { |
||
769 | echo "<h1>"._L('Backup restore done')."</h1>"; |
||
770 | foreach ($num_rows as $table_name => $cnt) { |
||
771 | if ($cnt !== "n/a") echo "<p>... $table_name: "._L('%1 datasets', $cnt)."</p>"; |
||
772 | } |
||
773 | echo "<hr>"; |
||
774 | } |
||
775 | |||
776 | } catch (\Exception $e) { |
||
777 | if (OIDplus::db()->transaction_supported()) OIDplus::db()->transaction_rollback(); |
||
778 | throw $e; |
||
779 | } |
||
780 | } |
||
781 | |||
782 | } |