Subversion Repositories oidplus

Rev

Rev 1363 | Blame | Compare with Previous | Last modification | View Log | RSS feed

  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.         /**
  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.         /**
  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'],
  260.                                                 "standardized" => $row2['standardized'] ?? false,
  261.                                                 "well_known" => $row2['well_known'] ?? false,
  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'],
  271.                                                 "longarc" => $row2['longarc'] ?? false,
  272.                                                 "well_known" => $row2['well_known'] ?? false,
  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"],
  282.                                         "confidential" => $row["confidential"] ?? false,
  283.                                         "created" => self::fix_datetime_for_output($row["created"]),
  284.                                         "updated" => self::fix_datetime_for_output($row["updated"]),
  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"],
  310.                                         "privacy" => $row["privacy"] ?? false,
  311.                                         "authkey" => $row["authkey"],
  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"])
  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"],
  329.                                         "protected" => $row["protected"] ?? false,
  330.                                         "visible" => $row["visible"] ?? false
  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'],
  353.                                                 "severity" => $row2['severity'] ?? 0
  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'],
  363.                                                 "severity" => $row2['severity'] ?? 0
  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);
  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.                 }
  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.  
  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.  
  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,
  618.                                                         (bool)($row["confidential"]??false),
  619.                                                         $row["created"]??'1900-01-01 00:00:00',
  620.                                                         $row["updated"]??'1900-01-01 00:00:00',
  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,
  629.                                                                 (bool)($row2["standardized"]??false),
  630.                                                                 (bool)($row2["well_known"]??false))
  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,
  639.                                                                 (bool)($row2["longarc"]??false),
  640.                                                                 (bool)($row2["well_known"]??false))
  641.                                                 );
  642.                                         }
  643.                                 }
  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';");
  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,
  671.                                                         (bool)($row["privacy"]??false),
  672.                                                         $row["authkey"]??null,
  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')
  676.                                         );
  677.                                 }
  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';");
  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,
  699.                                                         (bool)($row["protected"]??false),
  700.                                                         (bool)($row["visible"]??false))
  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,
  734.                                                                 $row2["severity"]??0)
  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,
  743.                                                                 $row2["severity"]??0)
  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. }
  783.