Subversion Repositories oidplus

Rev

Rev 1381 | Rev 1433 | Go to most recent revision | 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. class OIDplusPageAdminOIDInfoExport extends OIDplusPagePluginAdmin
  27.         implements INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_8 /* getNotifications */
  28. {
  29.  
  30.         /**
  31.          *
  32.          */
  33.         /*private*/ const QUERY_LIST_OIDINFO_OIDS_V1 = '1.3.6.1.4.1.37476.2.5.2.1.5.1';
  34.  
  35.         /**
  36.          *
  37.          */
  38.         /*private*/ const QUERY_GET_OIDINFO_DATA_V1  = '1.3.6.1.4.1.37476.2.5.2.1.6.1';
  39.  
  40.         /**
  41.          * @param array $params
  42.          * @return array
  43.          * @throws OIDplusException
  44.          */
  45.         private function action_ImportXml(array $params): array {
  46.                 if (!OIDplus::authUtils()->isAdminLoggedIn()) {
  47.                         throw new OIDplusHtmlException(_L('You need to <a %1>log in</a> as administrator.',OIDplus::gui()->link('oidplus:login$admin')), null,401);
  48.                 }
  49.  
  50.                 if (!isset($_FILES['userfile'])) {
  51.                         throw new OIDplusException(_L('Please choose a file.'));
  52.                 }
  53.  
  54.                 if ($_FILES['userfile']['error']) {
  55.                         throw new OIDplusException(_L('Could not receive file (probably it is too large?)'));
  56.                 }
  57.  
  58.                 $xml_contents = file_get_contents($_FILES['userfile']['tmp_name']);
  59.  
  60.                 $errors = array();
  61.                 list($count_imported_oids, $count_already_existing, $count_errors, $count_warnings) = $this->oidinfoImportXML($xml_contents, $errors, $replaceExistingOIDs=false, $orphan_mode=self::ORPHAN_AUTO_DEORPHAN);
  62.                 if (count($errors) > 0) {
  63.                         // Note: These "errors" can also be warnings (partial success)
  64.                         // TODO: since the output can be very long, should we really show it in a JavaScript alert() ?!
  65.                         return array(
  66.                                 "status" => -1,
  67.                                 "count_imported_oids" => $count_imported_oids,
  68.                                 "count_already_existing" => $count_already_existing,
  69.                                 "count_errors" => $count_errors,
  70.                                 "count_warnings" => $count_warnings,
  71.                                 "error" => implode("\n",$errors)
  72.                         );
  73.                 } else {
  74.                         return array(
  75.                                 "status" => 0,
  76.                                 "count_imported_oids" => $count_imported_oids,
  77.                                 "count_already_existing" => $count_already_existing,
  78.                                 "count_errors" => $count_errors,
  79.                                 "count_warnings" => $count_warnings
  80.                         );
  81.                 }
  82.         }
  83.  
  84.         /**
  85.          * @param array $params
  86.          * @return array
  87.          * @throws OIDplusException
  88.          */
  89.         private function action_ImportOidInfo(array $params): array {
  90.                 if (!OIDplus::authUtils()->isAdminLoggedIn()) {
  91.                         throw new OIDplusHtmlException(_L('You need to <a %1>log in</a> as administrator.',OIDplus::gui()->link('oidplus:login$admin')), null, 401);
  92.                 }
  93.  
  94.                 _CheckParamExists($params, 'oid');
  95.  
  96.                 $oid = $params['oid'];
  97.  
  98.                 $query = self::QUERY_GET_OIDINFO_DATA_V1;
  99.  
  100.                 $payload = array(
  101.                         "query" => $query, // we must repeat the query because we want to sign it
  102.                         "system_id" => OIDplus::getSystemId(false),
  103.                         "oid" => $oid
  104.                 );
  105.  
  106.                 $signature = '';
  107.                 if (!OIDplus::getPkiStatus() || !@openssl_sign(json_encode($payload), $signature, OIDplus::getSystemPrivateKey())) {
  108.                         if (!OIDplus::getPkiStatus()) {
  109.                                 throw new OIDplusException(_L('Error: Your system could not generate a private/public key pair. (OpenSSL is probably missing on your system). Therefore, you cannot register/unregister your OIDplus instance.'));
  110.                         } else {
  111.                                 throw new OIDplusException(_L('Signature failed'));
  112.                         }
  113.                 }
  114.  
  115.                 $data = array(
  116.                         "payload" => $payload,
  117.                         "signature" => base64_encode($signature)
  118.                 );
  119.  
  120.                 if (OIDplus::getEditionInfo()['vendor'] != 'ViaThinkSoft') {
  121.                         // The oid-info.com import functionality is a confidential API between ViaThinkSoft and oid-info.com and cannot be used in forks of OIDplus
  122.                         throw new OIDplusException(_L('This feature is only available in the ViaThinkSoft edition of OIDplus'));
  123.                 }
  124.  
  125.                 if (function_exists('gzdeflate')) {
  126.                         $compressed = "1";
  127.                         $data2 = gzdeflate(json_encode($data));
  128.                 } else {
  129.                         $compressed = "0";
  130.                         $data2 = json_encode($data);
  131.                 }
  132.  
  133.                 $res_curl = url_post_contents(
  134.                         'https://oidplus.viathinksoft.com/reg2/query.php',
  135.                         array(
  136.                                 "query"      => $query,
  137.                                 "compressed" => $compressed,
  138.                                 "data"       => base64_encode($data2)
  139.                         )
  140.                 );
  141.  
  142.                 if ($res_curl === false) {
  143.                         throw new OIDplusException(_L('Communication with %1 server failed', 'ViaThinkSoft'));
  144.                 }
  145.  
  146.                 $json = @json_decode($res_curl, true);
  147.  
  148.                 if (!$json) {
  149.                         return array(
  150.                                 "status" => -1,
  151.                                 "error" => _L('JSON reply from ViaThinkSoft decoding error: %1',$res_curl)
  152.                         );
  153.                 }
  154.  
  155.                 if (isset($json['error']) || ($json['status'] < 0)) {
  156.                         return array(
  157.                                 "status" => -1,
  158.                                 "error" => $json['error'] ?? _L('Received error status code: %1', $json['status'])
  159.                         );
  160.                 }
  161.  
  162.                 $errors = array();
  163.                 list($count_imported_oids, $count_already_existing, $count_errors, $count_warnings) = $this->oidinfoImportXML('<oid-database>'.$json['xml'].'</oid-database>', $errors, $replaceExistingOIDs=false, $orphan_mode=self::ORPHAN_DISALLOW_ORPHANS);
  164.                 if (count($errors) > 0) {
  165.                         return array("status" => -1, "error" => implode("\n",$errors));
  166.                 } else if ($count_imported_oids <> 1) {
  167.                         return array("status" => -1, "error" => _L('Imported %1, but expected to import 1',$count_imported_oids));
  168.                 } else {
  169.                         return array("status" => 0);
  170.                 }
  171.         }
  172.  
  173.         /**
  174.          * @param string $actionID
  175.          * @param array $params
  176.          * @return array
  177.          * @throws OIDplusException
  178.          */
  179.         public function action(string $actionID, array $params): array {
  180.                 if ($actionID == 'import_xml_file') {
  181.                         return $this->action_ImportXml($params);
  182.                 } else if ($actionID == 'import_oidinfo_oid') {
  183.                         return $this->action_ImportOidInfo($params);
  184.                 } else {
  185.                         return parent::action($actionID, $params);
  186.                 }
  187.         }
  188.  
  189.         /**
  190.          * @param bool $html
  191.          * @return void
  192.          */
  193.         public function init(bool $html=true) {
  194.                 // Nothing
  195.         }
  196.  
  197.         /**
  198.          * @param string $id
  199.          * @param array $out
  200.          * @param bool $handled
  201.          * @return void
  202.          * @throws OIDplusException
  203.          */
  204.         public function gui(string $id, array &$out, bool &$handled) {
  205.                 $ary = explode('$', $id);
  206.                 if (isset($ary[1])) {
  207.                         $id = $ary[0];
  208.                         $tab = $ary[1];
  209.                 } else {
  210.                         $tab = 'export';
  211.                 }
  212.                 if ($id === 'oidplus:oidinfo_compare_export') {
  213.                         $handled = true;
  214.                         $out['title'] = _L('List OIDs in your system which are missing at oid-info.com');
  215.                         $out['icon'] = file_exists(__DIR__.'/img/main_icon.png') ? OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/main_icon.png' : '';
  216.  
  217.                         if (!OIDplus::authUtils()->isAdminLoggedIn()) {
  218.                                 throw new OIDplusHtmlException(_L('You need to <a %1>log in</a> as administrator.',OIDplus::gui()->link('oidplus:login$admin')), $out['title'], 401);
  219.                         }
  220.  
  221.                         $query = self::QUERY_LIST_OIDINFO_OIDS_V1;
  222.  
  223.                         $payload = array(
  224.                                 "query" => $query, // we must repeat the query because we want to sign it
  225.                                 "system_id" => OIDplus::getSystemId(false),
  226.                                 "show_all" => 1 // this is required so that the VTS OIDRA gets no false notifications for adding the systems in the directory 1.3.6.1.4.1.37476.30.9
  227.                         );
  228.  
  229.                         $signature = '';
  230.                         if (!OIDplus::getPkiStatus() || !@openssl_sign(json_encode($payload), $signature, OIDplus::getSystemPrivateKey())) {
  231.                                 if (!OIDplus::getPkiStatus()) {
  232.                                         throw new OIDplusException(_L('Error: Your system could not generate a private/public key pair. (OpenSSL is probably missing on your system). Therefore, you cannot register/unregister your OIDplus instance.'));
  233.                                 } else {
  234.                                         throw new OIDplusException(_L('Signature failed'));
  235.                                 }
  236.                         }
  237.  
  238.                         $data = array(
  239.                                 "payload" => $payload,
  240.                                 "signature" => base64_encode($signature)
  241.                         );
  242.  
  243.                         if (OIDplus::getEditionInfo()['vendor'] != 'ViaThinkSoft') {
  244.                                 // The oid-info.com import functionality is a confidential API between ViaThinkSoft and oid-info.com and cannot be used in forks of OIDplus
  245.                                 throw new OIDplusException(_L('This feature is only available in the ViaThinkSoft edition of OIDplus'));
  246.                         }
  247.  
  248.                         if (function_exists('gzdeflate')) {
  249.                                 $compressed = "1";
  250.                                 $data2 = gzdeflate(json_encode($data));
  251.                         } else {
  252.                                 $compressed = "0";
  253.                                 $data2 = json_encode($data);
  254.                         }
  255.  
  256.                         $res_curl = url_post_contents(
  257.                                 'https://oidplus.viathinksoft.com/reg2/query.php',
  258.                                 array(
  259.                                         "query"      => $query,
  260.                                         "compressed" => $compressed,
  261.                                         "data"       => base64_encode($data2)
  262.                                 )
  263.                         );
  264.  
  265.                         if ($res_curl === false) {
  266.                                 throw new OIDplusException(_L('Communication with %1 server failed', 'ViaThinkSoft'));
  267.                         }
  268.  
  269.                         $out['text'] = '<p><a '.OIDplus::gui()->link('oidplus:datatransfer$export').'><img src="img/arrow_back.png" width="16" alt="'._L('Go back').'"> '._L('Go back to data transfer main page').'</a></p>';
  270.  
  271.                         $json = @json_decode($res_curl, true);
  272.  
  273.                         if (!$json) {
  274.                                 throw new OIDplusException($out['text']._L('JSON reply from ViaThinkSoft decoding error: %1',$res_curl), $out['title']);
  275.                         }
  276.  
  277.                         if (isset($json['error']) || ($json['status'] < 0)) {
  278.                                 if (isset($json['error'])) {
  279.                                         throw new OIDplusException($out['text']._L('Received error: %1',$json['error']), $out['title']);
  280.                                 } else {
  281.                                         throw new OIDplusException($out['text']._L('Received error status code: %1',$json['status']), $out['title']);
  282.                                 }
  283.                         }
  284.  
  285.                         if (isset($json['error']) || ($json['status'] < 0)) {
  286.                                 $out['text'] .= '<p>'._L('Error: %1',htmlentities($json['error'])).'</p>';
  287.                         } else {
  288.                                 // TODO: If roots were created or deleted recently, we must do a re-query of the registration, so that the "roots" information at the directory service gets refreshed
  289.                                 if (count($json['roots']) == 0) $out['text'] .= '<p>'._L('In order to use this feature, you need to have at least one (root) OID added in your system, and the system needs to report the newly added root to the directory service (the reporting interval is 1 hour).').'</p>';
  290.                                 foreach ($json['roots'] as $root) {
  291.                                         $oid = $root['oid'];
  292.                                         $out['text'] .= '<h2>'._L('Root OID %1',$oid).'</h2>';
  293.                                         if ($root['verified']) {
  294.                                                 $count = 0;
  295.                                                 $out['text'] .= '<div class="container box"><div id="suboid_table" class="table-responsive">';
  296.                                                 $out['text'] .= '<table class="table table-bordered table-striped">';
  297.                                                 $out['text'] .= '<thead>';
  298.                                                 $out['text'] .= '<tr><th colspan="3">'._L('Actions').'</th><th>'._L('OID').'</th></tr>';
  299.                                                 $out['text'] .= '</thead>';
  300.                                                 $out['text'] .= '<tbody>';
  301.  
  302.                                                 $lookup_nonoid = array();
  303.                                                 $row_lookup = array();
  304.  
  305.                                                 $all_local_oids_of_root = array();
  306.                                                 $res = OIDplus::db()->query("select * from ###objects where confidential <> 1");
  307.                                                 while ($row = $res->fetch_object()) {
  308.                                                         $obj = OIDplusObject::parse($row->id);
  309.                                                         if (!$obj) continue; // can happen when object type is not enabled
  310.                                                         if ($obj->isConfidential()) continue; // This will also exclude OIDs which are descendants of confidential OIDs
  311.                                                         if (strpos($row->id, 'oid:') === 0) {
  312.                                                                 $oid = substr($row->id,strlen('oid:'));
  313.                                                                 if (strpos($oid.'.', $root['oid']) === 0) {
  314.                                                                         $row_lookup[$oid] = $row;
  315.                                                                         $all_local_oids_of_root[] = $oid;
  316.                                                                 }
  317.                                                         } else {
  318.                                                                 $aids = $obj->getAltIds();
  319.                                                                 foreach ($aids as $aid) {
  320.                                                                         // TODO: Let Object Type plugins decide if they want that their OID representations get published or not (via a Feature OID implementation)
  321.                                                                         if ($aid->getNamespace() == 'oid') {
  322.                                                                                 $oid = $aid->getId();
  323.                                                                                 if (strpos($oid.'.', $root['oid']) === 0) {
  324.                                                                                         $row_lookup[$oid] = $row;
  325.                                                                                         $all_local_oids_of_root[] = $oid;
  326.                                                                                         $lookup_nonoid[$oid] = $row->id;
  327.                                                                                 }
  328.                                                                         }
  329.                                                                 }
  330.                                                         }
  331.                                                 }
  332.  
  333.                                                 natsort($all_local_oids_of_root);
  334.                                                 foreach ($all_local_oids_of_root as $local_oid) {
  335.                                                         if (!in_array($local_oid, $root['children'])) {
  336.                                                                 $count++;
  337.  
  338.                                                                 // Start: Build oid-info.com create URL
  339.  
  340.                                                                 $row = $row_lookup[$local_oid];
  341.  
  342.                                                                 $url = "http://oid-info.com/cgi-bin/manage?f=".oid_up($local_oid)."&a=create";
  343.  
  344.                                                                 $tmp = explode('.',$local_oid);
  345.                                                                 $url .= "&nb=".urlencode(array_pop($tmp));
  346.  
  347.                                                                 $asn1_ids = array();
  348.                                                                 $res_asn = OIDplus::db()->query("select * from ###asn1id where oid = ?", array($row->id));
  349.                                                                 while ($row_asn = $res_asn->fetch_object()) {
  350.                                                                         $asn1_ids[] = $row_asn->name; // 'unicode-label' is currently not in the standard format (oid.xsd)
  351.                                                                 }
  352.                                                                 $url .= "&id=".array_shift($asn1_ids); // urlencode() is already done (see above)
  353.                                                                 $url .= "&syn_id=".implode('%0A', $asn1_ids); // urlencode() is already done (see above)
  354.  
  355.                                                                 $iri_ids = array();
  356.                                                                 $res_iri = OIDplus::db()->query("select * from ###iri where oid = ?", array($row->id));
  357.                                                                 while ($row_iri = $res_iri->fetch_object()) {
  358.                                                                         $iri_ids[] = $row_iri->name;
  359.                                                                 }
  360.                                                                 $url .= "&unicode_label_list=".implode('%0A', $iri_ids); // urlencode() is already done (see above)
  361.  
  362.                                                                 if (!empty($row->title)) {
  363.                                                                         $tmp_description = $row->title;
  364.                                                                         $tmp_information = $row->description;/** @phpstan-ignore-line */
  365.                                                                         if (trim($row->title) == trim(strip_tags($row->description))) {/** @phpstan-ignore-line */
  366.                                                                                 $tmp_information = '';
  367.                                                                         }
  368.                                                                 } else if (isset($asn1_ids[0])) {
  369.                                                                         $tmp_description = '"'.$asn1_ids[0].'"';
  370.                                                                         $tmp_information = $row->description;
  371.                                                                 } else if (isset($iri_ids[0])) {
  372.                                                                         $tmp_description = '"'.$iri_ids[0].'"';
  373.                                                                         $tmp_information = $row->description;
  374.                                                                 } else if (!empty($row->description)) {
  375.                                                                         $tmp_description = $row->description;
  376.                                                                         $tmp_information = '';
  377.                                                                 } else if (!empty($row->comment)) {
  378.                                                                         $tmp_description = $row->comment;
  379.                                                                         $tmp_information = '';
  380.                                                                 } else {
  381.                                                                         $tmp_description = '<i>No description available</i>'; // do not translate
  382.                                                                         $tmp_information = '';
  383.                                                                 }
  384.  
  385.                                                                 if ($tmp_information != '') {
  386.                                                                         $tmp_information .= '<br/><br/>';
  387.                                                                 }
  388.  
  389.                                                                 $tmp_information .= 'See <a href="'.OIDplus::webpath(null,OIDplus::PATH_ABSOLUTE_CANONICAL).'?goto='.urlencode($id).'">more information</a>.'; // do not translate
  390.  
  391.                                                                 if (explode(':',$id,2)[0] != 'oid') {
  392.                                                                         $tmp_information = "Object: $id\n\n" . $tmp_information; // do not translate
  393.                                                                 }
  394.  
  395.                                                                 $url .= "&description=".urlencode(self::repair_relative_links($tmp_description));
  396.                                                                 $url .= "&info=".urlencode(self::repair_relative_links($tmp_information));
  397.  
  398.                                                                 $url .= "&current_registrant_email=".urlencode($row->ra_email);
  399.  
  400.                                                                 $res_ra = OIDplus::db()->query("select * from ###ra where email = ?", array($row->ra_email));
  401.                                                                 if ($res_ra->any()) {
  402.                                                                         $row_ra = $res_ra->fetch_object();
  403.  
  404.                                                                         $tmp = array();
  405.                                                                         if (!empty($row_ra->personal_name)) {
  406.                                                                                 $name_ary = split_firstname_lastname($row_ra->personal_name);
  407.                                                                                 $tmp_first_name = $name_ary[0];
  408.                                                                                 $tmp_last_name  = $name_ary[1];
  409.                                                                                 if (!empty($row_ra->ra_name)       ) $tmp[] = $row_ra->ra_name;
  410.                                                                                 if (!empty($row_ra->office)        ) $tmp[] = $row_ra->office;
  411.                                                                                 if (!empty($row_ra->organization)  ) $tmp[] = $row_ra->organization;
  412.                                                                         } else {
  413.                                                                                 $tmp_first_name = $row_ra->ra_name;
  414.                                                                                 $tmp_last_name  = '';
  415.                                                                                 if (!empty($row_ra->personal_name) ) $tmp[] = $row_ra->personal_name;
  416.                                                                                 if (!empty($row_ra->office)        ) $tmp[] = $row_ra->office;
  417.                                                                                 if (!empty($row_ra->organization)  ) $tmp[] = $row_ra->organization;
  418.                                                                         }
  419.  
  420.                                                                         if (empty($tmp_first_name) || empty($tmp_last_name)) {
  421.                                                                                 $name = self::split_name($tmp_first_name.' '.$tmp_last_name);
  422.                                                                                 $tmp_first_name = $name[0];
  423.                                                                                 $tmp_last_name = $name[1];
  424.                                                                         }
  425.                                                                         $url .= "&current_registrant_first_name=".urlencode($tmp_first_name);
  426.                                                                         $url .= "&current_registrant_last_name=".urlencode($tmp_last_name);
  427.  
  428.                                                                         if ((count($tmp) > 0) && ($tmp[0] == $row_ra->ra_name)) array_shift($tmp);
  429.                                                                         $tmp = array_unique($tmp);
  430.  
  431.                                                                         if (!$row_ra->privacy) {
  432.                                                                                 if (!empty($row_ra->street))   $tmp[] = $row_ra->street;
  433.                                                                                 if (!empty($row_ra->zip_town)) $tmp[] = $row_ra->zip_town;
  434.                                                                                 if (!empty($row_ra->country))  $tmp[] = $row_ra->country;
  435.                                                                                 $url .= "&current_registrant_tel=".urlencode(!empty($row_ra->phone) ? $row_ra->phone : $row_ra->mobile);
  436.                                                                                 $url .= "&current_registrant_fax=".urlencode($row_ra->fax);
  437.                                                                         }
  438.                                                                         if (empty($row_ra->zip_town) && empty($row_ra->country)) {
  439.                                                                                 // The address is useless if we do neither know city nor country
  440.                                                                                 // Ignore it
  441.                                                                         } else {
  442.                                                                                 $tmp = self::split_address_country(implode("<br/>", $tmp));
  443.                                                                                 $url .= "&current_registrant_address=".urlencode($tmp[0]);
  444.                                                                                 $url .= "&current_registrant_country=".urlencode($tmp[1]);
  445.                                                                         }
  446.                                                                 }
  447.                                                                 if (!empty($row->updated)) {
  448.                                                                         $tmp = explode('-', self::_formatdate($row->updated));
  449.                                                                         $url .= "&modification_year=".urlencode($tmp[0]);
  450.                                                                         $url .= "&modification_month=".urlencode($tmp[1]);
  451.                                                                         $url .= "&modification_day=".urlencode($tmp[2]);
  452.                                                                 }
  453.  
  454.                                                                 //$url .= "&submitter_last_name=".urlencode($xml->{'submitter'}->{'last-name'});
  455.                                                                 //$url .= "&submitter_first_name=".urlencode($xml->{'submitter'}->{'first-name'});
  456.                                                                 //$url .= "&submitter_email=".urlencode($xml->{'submitter'}->{'email'});
  457.  
  458.                                                                 // End: Build oid-info.com create URL
  459.  
  460.                                                                 // Note: "Actions" is at the left, because it has a fixed width, so the user can continue clicking without the links moving if the OID length changes between lines
  461.                                                                 $out['text'] .= '<tr id="missing_oid_'.str_replace('.','_',$local_oid).'">'.
  462.                                                                 '<td><a '.OIDplus::gui()->link($lookup_nonoid[$local_oid] ?? 'oid:' . $local_oid, true).'>'._L('View local OID').'</a></td>'.
  463.                                                                 '<td><a href="javascript:OIDplusPageAdminOIDInfoExport.removeMissingOid(\''.$local_oid.'\');">'._L('Ignore for now').'</a></td>'.
  464.                                                                 '<td><a target="_blank" href="'.$url.'">'._L('Add to oid-info.com manually').'</a></td>'.
  465.                                                                 '<td>'.$local_oid.'</td>'.
  466.                                                                 '</tr>';
  467.                                                         }
  468.                                                 }
  469.                                                 $out['text'] .= '</tbody>';
  470.                                                 if ($count == 0) {
  471.                                                         $out['text'] .= '<tfoot>';
  472.                                                         $out['text'] .= '<tr><td colspan="4">'._L('No missing OIDs found').'</td></tr>';
  473.                                                         $out['text'] .= '</tfoot>';
  474.                                                 }
  475.                                                 $out['text'] .= '</table></div></div>';
  476.                                         } else {
  477.                                                 $out['text'] .= '<p>'._L('This root is not validated. Please send an email to %1 in order to request ownership verification of this root OID.',$json['vts_verification_email']).'</p>';
  478.                                         }
  479.                                 }
  480.                         }
  481.                 }
  482.  
  483.                 if ($id === 'oidplus:oidinfo_compare_import') {
  484.                         $handled = true;
  485.                         $out['title'] = _L('List OIDs at oid-info.com which are missing in your system');
  486.                         $out['icon'] = file_exists(__DIR__.'/img/main_icon.png') ? OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/main_icon.png' : '';
  487.  
  488.                         if (!OIDplus::authUtils()->isAdminLoggedIn()) {
  489.                                 throw new OIDplusHtmlException(_L('You need to <a %1>log in</a> as administrator.',OIDplus::gui()->link('oidplus:login$admin')), $out['title'], 401);
  490.                         }
  491.  
  492.                         $query = self::QUERY_LIST_OIDINFO_OIDS_V1;
  493.  
  494.                         $payload = array(
  495.                                 "query" => $query, // we must repeat the query because we want to sign it
  496.                                 "system_id" => OIDplus::getSystemId(false),
  497.                                 "show_all" => 0
  498.                         );
  499.  
  500.                         $signature = '';
  501.                         if (!OIDplus::getPkiStatus() || !@openssl_sign(json_encode($payload), $signature, OIDplus::getSystemPrivateKey())) {
  502.                                 if (!OIDplus::getPkiStatus()) {
  503.                                         throw new OIDplusException(_L('Error: Your system could not generate a private/public key pair. (OpenSSL is probably missing on your system). Therefore, you cannot register/unregister your OIDplus instance.'));
  504.                                 } else {
  505.                                         throw new OIDplusException(_L('Signature failed'));
  506.                                 }
  507.                         }
  508.  
  509.                         $data = array(
  510.                                 "payload" => $payload,
  511.                                 "signature" => base64_encode($signature)
  512.                         );
  513.  
  514.                         if (OIDplus::getEditionInfo()['vendor'] != 'ViaThinkSoft') {
  515.                                 // The oid-info.com import functionality is a confidential API between ViaThinkSoft and oid-info.com and cannot be used in forks of OIDplus
  516.                                 throw new OIDplusException(_L('This feature is only available in the ViaThinkSoft edition of OIDplus'));
  517.                         }
  518.  
  519.                         if (function_exists('gzdeflate')) {
  520.                                 $compressed = "1";
  521.                                 $data2 = gzdeflate(json_encode($data));
  522.                         } else {
  523.                                 $compressed = "0";
  524.                                 $data2 = json_encode($data);
  525.                         }
  526.  
  527.                         $res = url_post_contents(
  528.                                 'https://oidplus.viathinksoft.com/reg2/query.php',
  529.                                 array(
  530.                                         "query"      => $query,
  531.                                         "compressed" => $compressed,
  532.                                         "data"       => base64_encode($data2)
  533.                                 )
  534.                         );
  535.  
  536.                         if ($res === false) {
  537.                                 throw new OIDplusException(_L('Communication with %1 server failed', 'ViaThinkSoft'));
  538.                         }
  539.  
  540.                         $out['text'] = '<p><a '.OIDplus::gui()->link('oidplus:datatransfer$import').'><img src="img/arrow_back.png" width="16" alt="'._L('Go back').'"> '._L('Go back to data transfer main page').'</a></p>';
  541.  
  542.                         $json = @json_decode($res, true);
  543.  
  544.                         if (!$json) {
  545.                                 throw new OIDplusException($out['text']._L('JSON reply from ViaThinkSoft decoding error: %1',$res), $out['title']);
  546.                         }
  547.  
  548.                         if (isset($json['error']) || ($json['status'] < 0)) {
  549.                                 if (isset($json['error'])) {
  550.                                         throw new OIDplusException($out['text']._L('Received error: %1',$json['error']), $out['title']);
  551.                                 } else {
  552.                                         throw new OIDplusException($out['text']._L('Received error status code: %1',$json['status']), $out['title']);
  553.                                 }
  554.                         }
  555.  
  556.                         $all_local_oids = array();
  557.                         $res = OIDplus::db()->query("select id from ###objects");
  558.                         while ($row = $res->fetch_array()) {
  559.                                 if (strpos($row['id'], 'oid:') === 0) {
  560.                                         $all_local_oids[] = substr($row['id'],strlen('oid:'));
  561.                                 } else {
  562.                                         $obj = OIDplusObject::parse($row['id']);
  563.                                         if (!$obj) continue; // can happen when object type is not enabled
  564.                                         $aids = $obj->getAltIds();
  565.                                         foreach ($aids as $aid) {
  566.                                                 // TODO: Let Object Type plugins decide if they want that their OID representations get published or not (via a Feature OID implementation)
  567.                                                 if ($aid->getNamespace() == 'oid') {
  568.                                                         $all_local_oids[] = $aid->getId();
  569.                                                 }
  570.                                         }
  571.                                 }
  572.                         }
  573.  
  574.                         if (isset($json['error']) || ($json['status'] < 0)) {
  575.                                 $out['text'] .= '<p>'._L('Error: %1',htmlentities($json['error'])).'</p>';
  576.                         } else {
  577.                                 // TODO: If roots were created or deleted recently, we must do a re-query of the registration, so that the "roots" information at the directory service gets refreshed
  578.                                 if (count($json['roots']) == 0) $out['text'] .= '<p>'._L('In order to use this feature, you need to have at least one (root) OID added in your system, and the system needs to report the newly added root to the directory service (the reporting interval is 1 hour).').'</p>';
  579.                                 foreach ($json['roots'] as $root) {
  580.                                         $oid = $root['oid'];
  581.                                         $out['text'] .= '<h2>'._L('Root OID %1',$oid).'</h2>';
  582.                                         // TODO: "Import all" button
  583.                                         if ($root['verified']) {
  584.                                                 $count = 0;
  585.                                                 $out['text'] .= '<div class="container box"><div id="suboid_table" class="table-responsive">';
  586.                                                 $out['text'] .= '<table class="table table-bordered table-striped">';
  587.                                                 $out['text'] .= '<thead>';
  588.                                                 $out['text'] .= '<tr><th colspan="4">'._L('Actions').'</th><th>'._L('OID').'</th></tr>';
  589.                                                 $out['text'] .= '</thead>';
  590.                                                 $out['text'] .= '<tbody>';
  591.                                                 natsort($root['children']);
  592.                                                 foreach ($root['children'] as $child_oid) {
  593.                                                         if (!in_array($child_oid, $all_local_oids)) {
  594.                                                                 $count++;
  595.                                                                 // Note: "Actions" is at the left, because it has a fixed width, so the user can continue clicking without the links moving if the OID length changes between lines
  596.                                                                 $out['text'] .= '<tr id="missing_oid_'.str_replace('.','_',$child_oid).'">'.
  597.                                                                 '<td><a target="_blank" href="http://oid-info.com/get/'.$child_oid.'">'._L('View OID at oid-info.com').'</a></td>'.
  598.                                                                 '<td><a href="javascript:OIDplusPageAdminOIDInfoExport.removeMissingOid(\''.$child_oid.'\');">'._L('Ignore for now').'</a></td>'.
  599.                                                                 '<td><a href="mailto:admin@oid-info.com">'._L('Report illegal OID').'</a></td>'.
  600.                                                                 (strpos($child_oid,'1.3.6.1.4.1.37476.30.9.') === 0 ? '<td>&nbsp;</td>' : '<td><a href="javascript:OIDplusPageAdminOIDInfoExport.importMissingOid(\''.$child_oid.'\');">'._L('Import OID').'</a></td>').
  601.                                                                 '<td>'.$child_oid.'</td>'.
  602.                                                                 '</tr>';
  603.                                                         }
  604.                                                 }
  605.                                                 $out['text'] .= '</tbody>';
  606.                                                 if ($count == 0) {
  607.                                                         $out['text'] .= '<tfoot>';
  608.                                                         $out['text'] .= '<tr><td colspan="5">'._L('No extra OIDs found').'</td></tr>';
  609.                                                         $out['text'] .= '</tfoot>';
  610.                                                 }
  611.                                                 $out['text'] .= '</table></div></div>';
  612.                                         } else {
  613.                                                 $out['text'] .= '<p>'._L('This root is not validated. Please send an email to %1 in order to request ownership verification of this root OID.',$json['vts_verification_email']).'</p>';
  614.                                         }
  615.                                 }
  616.                         }
  617.                 }
  618.  
  619.                 if ($id === 'oidplus:datatransfer') {
  620.                         $handled = true;
  621.                         $out['title'] = _L('Data Exchange (oid-info.com)');
  622.                         $out['icon'] = file_exists(__DIR__.'/img/main_icon.png') ? OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/main_icon.png' : '';
  623.  
  624.                         if (!OIDplus::authUtils()->isAdminLoggedIn()) {
  625.                                 throw new OIDplusHtmlException(_L('You need to <a %1>log in</a> as administrator.',OIDplus::gui()->link('oidplus:login$admin')), $out['title'], 401);
  626.                         }
  627.  
  628.                         $out['text'] = '<noscript>';
  629.                         $out['text'] .= '<p><font color="red">'._L('You need to enable JavaScript to use this feature.').'</font></p>';
  630.                         $out['text'] .= '</noscript>';
  631.  
  632.                         $out['text'] .= '<br><div id="dataTransferArea" style="visibility: hidden"><div id="dataTransferTab" class="container" style="width:100%;">';
  633.  
  634.                         // ---------------- Tab control
  635.                         $out['text'] .= OIDplus::gui()->tabBarStart();
  636.                         $out['text'] .= OIDplus::gui()->tabBarElement('export', _L('Export'), $tab === 'export');
  637.                         $out['text'] .= OIDplus::gui()->tabBarElement('import', _L('Import'), $tab === 'import');
  638.                         $out['text'] .= OIDplus::gui()->tabBarEnd();
  639.                         $out['text'] .= OIDplus::gui()->tabContentStart();
  640.                         // ---------------- "Export" tab
  641.                         $tabcont  = '<h2>'._L('Generate XML file containing all OIDs').'</h2>';
  642.                         $tabcont .= '<p>'._L('These XML files are following the <a %1>XML schema</a> of <b>oid-info.com</b>. They can be used for various purposes though.','href="http://oid-info.com/oid.xsd" target="_blank"').'</p>';
  643.                         $tabcont .= '<p><input type="button" onclick="window.open(\''.OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'oidinfo_export.php\',\'_blank\')" value="'._L('Generate XML (all OIDs)').'"></p>';
  644.                         $tabcont .= '<p><input type="button" onclick="window.open(\''.OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'oidinfo_export.php?online=1\',\'_blank\')" value="'._L('Generate XML (only OIDs which do not exist at oid-info.com)').'"></p>';
  645.                         $tabcont .= '<p><a href="http://oid-info.com/submit.htm" target="_blank">'._L('Upload XML files manually to oid-info.com').'</a></p>';
  646.                         $tabcont .= '<br><p>'._L('Attention: Do not use this XML Export/Import to exchange, backup or restore data between OIDplus systems!<br>It will cause various loss of information, e.g. because Non-OIDs like GUIDs are converted in OIDs and can\'t be converted back.').'</p>';
  647.                         $tabcont .= '<h2>'._L('Automatic export to oid-info.com').'</h2>';
  648.                         $privacy_level = OIDplus::config()->getValue('reg_privacy');
  649.                         if ($privacy_level == 0) {
  650.                                 $tabcont .= '<p>'._L('All your OIDs will automatically submitted to oid-info.com through the remote directory service in regular intervals.').' (<a '.OIDplus::gui()->link('oidplus:srv_registration').'>'._L('Change preference').'</a>)</p>';
  651.                         } else {
  652.                                 $tabcont .= '<p>'._L('If you set the privacy option to "0" (your system is registered), then all your OIDs will be automatically exported to oid-info.com.').' (<a '.OIDplus::gui()->link('oidplus:srv_registration').'>'._L('Change preference').'</a>)</p>';
  653.                         }
  654.                         $tabcont .= '<h2>'._L('Comparison with oid-info.com').'</h2>';
  655.                         $tabcont .= '<p><a '.OIDplus::gui()->link('oidplus:oidinfo_compare_export').'>'._L('List OIDs in your system which are missing at oid-info.com').'</a></p>';
  656.                         $out['text'] .= OIDplus::gui()->tabContentPage('export', $tabcont, $tab === 'export');
  657.                         // ---------------- "Import" tab
  658.                         $tabcont  = '<h2>'._L('Import XML file').'</h2>';
  659.                         $tabcont .= '<p>'._L('These XML files are following the <a %1>XML schema</a> of <b>oid-info.com</b>.','href="http://oid-info.com/oid.xsd" target="_blank"').'</p>';
  660.                         // TODO: we need a waiting animation!
  661.                         $tabcont .= '<form action="javascript:void(0);" onsubmit="return OIDplusPageAdminOIDInfoExport.uploadXmlFileOnSubmit(this);" enctype="multipart/form-data" id="uploadXmlFileForm">';
  662.                         $tabcont .= '<div>'._L('Choose XML file here').':<br><input type="file" name="userfile" value="" id="userfile">';
  663.                         $tabcont .= '<br><input type="submit" value="'._L('Import XML').'"></div>';
  664.                         $tabcont .= '</form>';
  665.                         $tabcont .= '<br><p>'._L('Attention: Do not use this XML Export/Import to exchange, backup or restore data between OIDplus systems!<br>It will cause various loss of information, e.g. because Non-OIDs like GUIDs are converted in OIDs and can\'t be converted back.').'</p>';
  666.                         $tabcont .= '<h2>'._L('Comparison with oid-info.com').'</h2>';
  667.                         $tabcont .= '<p><a '.OIDplus::gui()->link('oidplus:oidinfo_compare_import').'>'._L('List OIDs at oid-info.com which are missing in your system').'</a></p>';
  668.                         $out['text'] .= OIDplus::gui()->tabContentPage('import', $tabcont, $tab === 'import');
  669.                         $out['text'] .= OIDplus::gui()->tabContentEnd();
  670.                         // ---------------- Tab control END
  671.  
  672.                         $out['text'] .= '</div></div><script>$("#dataTransferArea")[0].style.visibility = "visible";</script>';
  673.                 }
  674.         }
  675.  
  676.         /**
  677.          * @param array $json
  678.          * @param string|null $ra_email
  679.          * @param bool $nonjs
  680.          * @param string $req_goto
  681.          * @return bool
  682.          * @throws OIDplusException
  683.          */
  684.         public function tree(array &$json, string $ra_email=null, bool $nonjs=false, string $req_goto=''): bool {
  685.                 if (!OIDplus::authUtils()->isAdminLoggedIn()) return false;
  686.  
  687.                 if (file_exists(__DIR__.'/img/main_icon16.png')) {
  688.                         $tree_icon = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/main_icon16.png';
  689.                 } else {
  690.                         $tree_icon = null; // default icon (folder)
  691.                 }
  692.  
  693.                 $json[] = array(
  694.                         'id' => 'oidplus:datatransfer',
  695.                         'icon' => $tree_icon,
  696.                         'text' => _L('Data Exchange (oid-info.com)')
  697.                 );
  698.  
  699.                 return true;
  700.         }
  701.  
  702.         /**
  703.          * @param string $request
  704.          * @return array|false
  705.          */
  706.         public function tree_search(string $request) {
  707.                 return false;
  708.         }
  709.  
  710.         /**
  711.          * @param bool $only_non_existing
  712.          * @return string[]
  713.          * @throws OIDplusException
  714.          * @throws \OIDInfoException
  715.          */
  716.         public static function outputXML(bool $only_non_existing): array {
  717.                 $out_type = null;
  718.                 $out_content = '';
  719.  
  720.                 // This file contains class \OIDInfoAPI.
  721.                 // We cannot include this in init(), because the init
  722.                 // of the registration plugin (OIDplusPageAdminRegistration) uses
  723.                 // OIDplusPageAdminOIDInfoExport::outputXML() before
  724.                 // OIDplusPageAdminOIDInfoExport::init() ,
  725.                 // because OIDplusPageAdminRegistration::init() comes first sometimes.
  726.                 require_once __DIR__ . '/oidinfo_api.inc.php';
  727.  
  728.                 $oa = new \OIDInfoAPI();
  729.                 if ($only_non_existing) {
  730.                         if (!function_exists('socket_create')) {
  731.                                 throw new OIDplusException(_L('You must install the PHP "sockets" in order to check for non-existing OIDs.'));
  732.                         }
  733.                         $oa->addSimplePingProvider('viathinksoft.de:49500');
  734.                 }
  735.  
  736.                 $email = OIDplus::config()->getValue('admin_email');
  737.                 if (empty($email)) $email = 'unknown@example.com';
  738.  
  739.                 $sys_title = OIDplus::config()->getValue('system_title');
  740.                 $name1 = !empty($sys_title) ? $sys_title : 'OIDplus 2.0';
  741.                 $name2 = $_SERVER['SERVER_NAME'] ?? 'Export interface';
  742.                 $out_content .= $oa->xmlAddHeader($name1, $name2, $email); // do not translate
  743.  
  744.                 $params['allow_html'] = true;
  745.                 $params['allow_illegal_email'] = true; // It should be enabled, because the creator could have used some kind of human-readable anti-spam technique
  746.                 $params['soft_correct_behavior'] = \OIDInfoAPI::SOFT_CORRECT_BEHAVIOR_NONE;
  747.                 $params['do_online_check'] = false; // Flag to disable this online check, because it generates a lot of traffic and runtime.
  748.                 $params['do_illegality_check'] = true;
  749.                 $params['do_simpleping_check'] = $only_non_existing;
  750.                 $params['auto_extract_name'] = '';
  751.                 $params['auto_extract_url'] = '';
  752.                 $params['always_output_comment'] = false;
  753.                 $params['creation_allowed_check'] = $only_non_existing;
  754.                 $params['tolerant_htmlentities'] = true;
  755.                 $params['ignore_xhtml_light'] = false;
  756.  
  757.                 $nonConfidential = OIDplusObject::getAllNonConfidential();
  758.                 natsort($nonConfidential);
  759.  
  760.                 foreach ($nonConfidential as $id) {
  761.                         $obj = OIDplusObject::parse($id);
  762.                         if ($obj) {
  763.                                 $elements['identifier'] = array();
  764.                                 $res_asn = OIDplus::db()->query("select * from ###asn1id where oid = ?", array($id));
  765.                                 while ($row_asn = $res_asn->fetch_object()) {
  766.                                         $elements['identifier'][] = $row_asn->name; // 'unicode-label' is currently not in the standard format (oid.xsd)
  767.                                 }
  768.  
  769.                                 $elements['unicode-label'] = array();
  770.                                 $res_iri = OIDplus::db()->query("select * from ###iri where oid = ?", array($id));
  771.                                 while ($row_iri = $res_iri->fetch_object()) {
  772.                                         $elements['unicode-label'][] = $row_iri->name;
  773.                                 }
  774.  
  775.                                 $title = $obj->getTitle();
  776.                                 $description = $obj->getDescription();
  777.                                 $comment = $obj->getComment();
  778.                                 if (!empty($title)) {
  779.                                         $elements['description'] = $title;
  780.                                         $elements['information'] = $description;
  781.                                         if (trim($title) == trim(strip_tags($description))) {
  782.                                                 $elements['information'] = '';
  783.                                         }
  784.                                 } else if (isset($elements['identifier'][0])) {
  785.                                         $elements['description'] = '"'.$elements['identifier'][0].'"';
  786.                                         $elements['information'] = $description;
  787.                                 } else if (isset($elements['unicode-label'][0])) {
  788.                                         $elements['description'] = '"'.$elements['unicode-label'][0].'"';
  789.                                         $elements['information'] = $description;
  790.                                 } else if (!empty($description)) {
  791.                                         $elements['description'] = $description;
  792.                                         $elements['information'] = '';
  793.                                 } else if (!empty($comment)) {
  794.                                         $elements['description'] = $comment;
  795.                                         $elements['information'] = '';
  796.                                 } else {
  797.                                         $elements['description'] = '<i>No description available</i>'; // do not translate
  798.                                         $elements['information'] = '';
  799.                                 }
  800.  
  801.                                 if ($elements['information'] != '') {
  802.                                         $elements['information'] .= '<br/><br/>';
  803.                                 }
  804.  
  805.                                 $elements['information'] .= 'See <a href="'.OIDplus::webpath(null,OIDplus::PATH_ABSOLUTE_CANONICAL).'?goto='.urlencode($id).'">more information</a>.'; // do not translate
  806.  
  807.                                 if (explode(':',$id,2)[0] != 'oid') {
  808.                                         $elements['information'] = "Object: $id\n\n" . $elements['information']; // do not translate
  809.                                 }
  810.  
  811.                                 $elements['description'] = self::repair_relative_links($elements['description']);
  812.                                 $elements['information'] = self::repair_relative_links($elements['information']);
  813.  
  814.                                 $elements['first-registrant']['first-name'] = '';
  815.                                 $elements['first-registrant']['last-name'] = '';
  816.                                 $elements['first-registrant']['address'] = '';
  817.                                 $elements['first-registrant']['email'] = '';
  818.                                 $elements['first-registrant']['phone'] = '';
  819.                                 $elements['first-registrant']['fax'] = '';
  820.                                 $create_ts = $obj->getCreatedTime();
  821.                                 $elements['first-registrant']['creation-date'] = $create_ts ? self::_formatdate($create_ts) : '';
  822.  
  823.                                 $elements['current-registrant']['first-name'] = '';
  824.                                 $elements['current-registrant']['last-name'] = '';
  825.                                 $elements['current-registrant']['email'] = $obj->getRaMail();
  826.                                 $elements['current-registrant']['phone'] = '';
  827.                                 $elements['current-registrant']['fax'] = '';
  828.                                 $elements['current-registrant']['address'] = '';
  829.  
  830.                                 $res_ra = OIDplus::db()->query("select * from ###ra where email = ?", array($obj->getRaMail()));
  831.                                 if ($res_ra->any()) {
  832.                                         $row_ra = $res_ra->fetch_object();
  833.  
  834.                                         $tmp = array();
  835.                                         if (!empty($row_ra->personal_name)) {
  836.                                                 $name_ary = split_firstname_lastname($row_ra->personal_name);
  837.                                                 $elements['current-registrant']['first-name'] = $name_ary[0];
  838.                                                 $elements['current-registrant']['last-name']  = $name_ary[1];
  839.                                                 if (!empty($row_ra->ra_name)       ) $tmp[] = $row_ra->ra_name;
  840.                                                 if (!empty($row_ra->office)        ) $tmp[] = $row_ra->office;
  841.                                                 if (!empty($row_ra->organization)  ) $tmp[] = $row_ra->organization;
  842.                                         } else {
  843.                                                 $elements['current-registrant']['first-name'] = $row_ra->ra_name;
  844.                                                 $elements['current-registrant']['last-name']  = '';
  845.                                                 if (!empty($row_ra->personal_name) ) $tmp[] = $row_ra->personal_name;
  846.                                                 if (!empty($row_ra->office)        ) $tmp[] = $row_ra->office;
  847.                                                 if (!empty($row_ra->organization)  ) $tmp[] = $row_ra->organization;
  848.                                         }
  849.  
  850.                                         if ((count($tmp) > 0) && ($tmp[0] == $row_ra->ra_name)) array_shift($tmp);
  851.                                         $tmp = array_unique($tmp);
  852.  
  853.                                         if (!$row_ra->privacy) {
  854.                                                 if (!empty($row_ra->street))   $tmp[] = $row_ra->street;
  855.                                                 if (!empty($row_ra->zip_town)) $tmp[] = $row_ra->zip_town;
  856.                                                 if (!empty($row_ra->country))  $tmp[] = $row_ra->country;
  857.                                                 $elements['current-registrant']['phone'] = !empty($row_ra->phone) ? $row_ra->phone : $row_ra->mobile;
  858.                                                 $elements['current-registrant']['fax'] = $row_ra->fax;
  859.                                         }
  860.                                         if (empty($row_ra->zip_town) && empty($row_ra->country)) {
  861.                                                 // The address is useless if we do neither know city nor country
  862.                                                 // Ignore it
  863.                                                 $elements['current-registrant']['address'] = '';
  864.                                         } else {
  865.                                                 $elements['current-registrant']['address'] = implode("<br/>", $tmp);
  866.                                         }
  867.                                 }
  868.                                 $update_ts = $obj->getUpdatedTime();
  869.                                 $elements['current-registrant']['modification-date'] = $update_ts ? self::_formatdate($update_ts) : '';
  870.  
  871.                                 // Request from O.D. 20 May 2019: First registrant should not be empty (especially for cases where Creation and Modify Dates are the same)
  872.                                 // Actually, this is a problem because we don't know the first registrant.
  873.                                 // However, since oidinfo gets their XML very fast (if using registration), it is likely that the reported RA is still the same...
  874.                                 // ... and changes at the RA are not reported to oid-info.com anyways - the XML is only for creation
  875.  
  876.                                 $elements['first-registrant']['first-name'] = $elements['current-registrant']['first-name'];
  877.                                 $elements['first-registrant']['last-name']  = $elements['current-registrant']['last-name'];
  878.                                 $elements['first-registrant']['address']    = $elements['current-registrant']['address'];
  879.                                 $elements['first-registrant']['email']      = $elements['current-registrant']['email'];
  880.                                 $elements['first-registrant']['phone']      = $elements['current-registrant']['phone'];
  881.                                 $elements['first-registrant']['fax']        = $elements['current-registrant']['fax'];
  882.  
  883.                                 $elements['current-registrant']['first-name'] = '';
  884.                                 $elements['current-registrant']['last-name'] = '';
  885.                                 $elements['current-registrant']['address'] = '';
  886.                                 $elements['current-registrant']['email'] = '';
  887.                                 $elements['current-registrant']['phone'] = '';
  888.                                 $elements['current-registrant']['fax'] = '';
  889.                                 $elements['current-registrant']['modification-date'] = '';
  890.  
  891.                                 // End request O.D. 20 May 2019
  892.  
  893.                                 list($ns,$id) = explode(':',$obj->nodeId());
  894.                                 if ($ns == 'oid') {
  895.                                         $out_content .= $oa->createXMLEntry($id, $elements, $params, $comment=$obj->nodeId());
  896.                                 } else {
  897.                                         $alt_ids = $obj->getAltIds(); // TODO: slow!
  898.                                         foreach ($alt_ids as $alt_id) {
  899.                                                 $ns = $alt_id->getNamespace();
  900.                                                 $id = $alt_id->getId();
  901.                                                 $desc = $alt_id->getDescription();
  902.                                                 if ($ns == 'oid') {
  903.                                                         // TODO: Let Object Type plugins decide if they want that their OID representations get published or not (via a Feature OID implementation)
  904.                                                         if (strpos($id, '2.25.') === 0) continue; // don't spam the uuid arc with GUID objects
  905.                                                         if (strpos($id, '1.2.840.113556.1.8000.2554.') === 0) continue; // don't spam the uuid arc with GUID objects
  906.                                                         if (strpos($id, '1.3.6.1.4.1.54392.1.') === 0) continue; // don't spam the uuid arc with GUID objects
  907.                                                         if (strpos($id, '1.3.6.1.4.1.54392.2.') === 0) continue; // don't spam the uuid arc with GUID objects
  908.                                                         if (strpos($id, '1.3.6.1.4.1.54392.3.') === 0) continue; // don't spam the uuid arc with GUID objects
  909.                                                         $out_content .= $oa->createXMLEntry($id, $elements, $params, $comment=$obj->nodeId());
  910.                                                 }
  911.                                         }
  912.                                 }
  913.                         }
  914.                 }
  915.  
  916.                 $out_content .= $oa->xmlAddFooter();
  917.  
  918.                 $out_type = 'text/xml';
  919.                 return array($out_content, $out_type);
  920.         }
  921.  
  922.         /**
  923.          * @param string $str
  924.          * @return string
  925.          */
  926.         private static function _formatdate(string $str): string {
  927.                 $str = explode(' ',$str)[0];
  928.                 if ($str == '0000-00-00') $str = '';
  929.                 return $str;
  930.         }
  931.  
  932.         /**
  933.          * @param string $str
  934.          * @return string
  935.          * @throws OIDplusException
  936.          */
  937.         private static function repair_relative_links(string $str): string {
  938.                 return preg_replace_callback('@(href\s*=\s*([\'"]))(.+)(\\2)@ismU', function($treffer) {
  939.                         $url = $treffer[3];
  940.                         if ((stripos($url,'http:') !== 0) && (stripos($url,'https:') !== 0) && (stripos($url,'ftp:') !== 0)) {
  941.                                 if (stripos($url,'www.') === 0) {
  942.                                         $url .= 'http://' . $url;
  943.                                 } else {
  944.                                         $url = OIDplus::webpath(null,OIDplus::PATH_ABSOLUTE_CANONICAL) . $url;
  945.                                 }
  946.                         }
  947.                         return $treffer[1].$url.$treffer[4];
  948.                 }, $str);
  949.         }
  950.  
  951.         /**
  952.          * @param string $address
  953.          * @return string[]
  954.          */
  955.         private static function split_address_country(string $address): array {
  956.                 global $oidinfo_countries;
  957.                 $ary = explode("\n", $address);
  958.                 $last_line = array_pop($ary);
  959.                 $rest = implode("\n", $ary);
  960.                 if (isset($oidinfo_countries[$last_line])) {
  961.                         return array($rest, $oidinfo_countries[$last_line]);
  962.                 } else {
  963.                         return array($rest."\n".$last_line, '');
  964.                 }
  965.         }
  966.  
  967.         /**
  968.          * @param string $name
  969.          * @return array
  970.          */
  971.         private static function split_name(string $name): array {
  972.                 // uses regex that accepts any word character or hyphen in last name
  973.                 // https://stackoverflow.com/questions/13637145/split-text-string-into-first-and-last-name-in-php
  974.                 $name = trim($name);
  975.                 $last_name = (strpos($name, ' ') === false) ? '' : preg_replace('#.*\s([\w-]*)$#', '$1', $name);
  976.                 $first_name = trim( preg_replace('#'.preg_quote($last_name,'#').'#', '', $name ) );
  977.                 return array($first_name, $last_name);
  978.         }
  979.  
  980.         /*protected*/ const ORPHAN_IGNORE = 0;
  981.         /*protected*/ const ORPHAN_AUTO_DEORPHAN = 1;
  982.         /*protected*/ const ORPHAN_DISALLOW_ORPHANS = 2;
  983.  
  984.         /**
  985.          * @param string $xml_contents
  986.          * @param array $errors
  987.          * @param bool $replaceExistingOIDs
  988.          * @param int $orphan_mode
  989.          * @return int[]
  990.          * @throws OIDplusException
  991.          */
  992.         protected function oidinfoImportXML(string $xml_contents, array &$errors, bool $replaceExistingOIDs=false, int $orphan_mode=self::ORPHAN_AUTO_DEORPHAN): array {
  993.                 // TODO: Implement RA import (let the user decide)
  994.                 // TODO: Let the user decide about $replaceExistingOIDs
  995.                 // TODO: Let the user decide if "created=now" should be set (this is good when the XML files is created by the user itself to do bulk-inserts)
  996.  
  997.                 $xml_contents = str_replace('<description>', '<description><![CDATA[', $xml_contents);
  998.                 $xml_contents = str_replace('</description>', ']]></description>', $xml_contents);
  999.  
  1000.                 $xml_contents = str_replace('<information>', '<information><![CDATA[', $xml_contents);
  1001.                 $xml_contents = str_replace('</information>', ']]></information>', $xml_contents);
  1002.  
  1003.                 $xml = @simplexml_load_string($xml_contents);
  1004.  
  1005.                 $count_already_existing = 0;
  1006.                 $count_errors = 0;
  1007.                 $count_warnings = 0;
  1008.  
  1009.                 if (!$xml) {
  1010.                         $errors[] = _L('Cannot read XML data. The XML file is probably invalid.');
  1011.                         $count_errors++;
  1012.                         return array(0, 0, 1, 0);
  1013.                 }
  1014.  
  1015.                 $ok_oids = array();
  1016.  
  1017.                 foreach ($xml->oid as $xoid) {
  1018.  
  1019.                         if (isset($xoid->{'dot-notation'})) {
  1020.                                 $dot_notation = $xoid->{'dot-notation'}->__toString();
  1021.                         } else if (isset($xoid->{'asn1-notation'})) {
  1022.                                 $dot_notation = asn1_to_dot($xoid->{'asn1-notation'}->__toString());
  1023.                         } else {
  1024.                                 $errors[] = _L('Cannot find dot notation because fields asn1-notation and dot-notation are both not existing');
  1025.                                 $count_errors++;
  1026.                                 continue;
  1027.                         }
  1028.  
  1029.                         $id = "oid:$dot_notation";
  1030.                         $title = isset($xoid->{'description'}) ? $xoid->{'description'}->__toString() : '';
  1031.                         $info = isset($xoid->{'description'}) ? $xoid->{'information'}->__toString() : '';
  1032.  
  1033.                         // For ASN.1 definitions, "Description" is filled with the definition and "Information" is usually empty
  1034.                         if (strpos($title,'<br') !== false) {
  1035.                                 $info = $title . $info;
  1036.                                 $title = explode(' ',$title)[0];
  1037.                         }
  1038.  
  1039.                         if (isset($xoid->{'current-registrant'}->email)) {
  1040.                                 $ra = $xoid->{'current-registrant'}->email->__toString();
  1041.                         } else if (isset($xoid->{'first-registrant'}->email)) {
  1042.                                 $ra = $xoid->{'first-registrant'}->email->__toString();
  1043.                         } else {
  1044.                                 $ra = '';
  1045.                         }
  1046.  
  1047.                         if (!oid_valid_dotnotation($dot_notation, false, false)) {
  1048.                                 $errors[] = _L('Ignored OID %1 because its dot notation is illegal or was not found',$dot_notation);
  1049.                                 $count_errors++;
  1050.                                 continue;
  1051.                         }
  1052.  
  1053.                         $parent = 'oid:'.oid_up($dot_notation);
  1054.  
  1055.                         if ($orphan_mode === self::ORPHAN_DISALLOW_ORPHANS) {
  1056.                                 if (!OIDplusObject::exists($parent)) {
  1057.                                         $errors[] = _L('Cannot import %1, because its parent is not in the database.',$dot_notation);
  1058.                                         $count_errors++;
  1059.                                         continue;
  1060.                                 }
  1061.                         }
  1062.  
  1063.                         if (OIDplus::db()->transaction_supported()) OIDplus::db()->transaction_begin();
  1064.                         try {
  1065.                                 $obj_test = OIDplusObject::findFitting($id);
  1066.                                 if ($obj_test) {
  1067.                                         if ($replaceExistingOIDs) {
  1068.                                                 OIDplus::db()->query("delete from ###objects where id = ?", array($id));
  1069.                                                 OIDplus::db()->query("delete from ###asn1id where oid = ?", array($id));
  1070.                                                 OIDplus::db()->query("delete from ###iri where oid = ?", array($id));
  1071.                                         } else {
  1072.                                                 //$errors[] = "Ignore OID '$dot_notation' because it already exists";
  1073.                                                 //$count_errors++;
  1074.                                                 $count_already_existing++;
  1075.                                                 continue;
  1076.                                         }
  1077.                                 }
  1078.  
  1079.                                 // TODO: we can probably get the created and modified timestamp from oid-info.com XML
  1080.                                 OIDplus::db()->query("insert into ###objects (id, parent, title, description, confidential, ra_email) values (?, ?, ?, ?, ?, ?)", array($id, $parent, $title, $info, false, $ra));
  1081.  
  1082.                                 if (OIDplus::db()->transaction_supported()) OIDplus::db()->transaction_commit();
  1083.                         } catch (\Exception $e) {
  1084.                                 if (OIDplus::db()->transaction_supported()) OIDplus::db()->transaction_rollback();
  1085.                                 throw new $e;
  1086.                         }
  1087.  
  1088.                         OIDplusObject::resetObjectInformationCache();
  1089.  
  1090.                         $this_oid_has_warnings = false;
  1091.  
  1092.                         // ---------------------------------------------------------------------
  1093.  
  1094.                         $asn1ids = array();
  1095.                         if (isset($xoid->{'identifier'})) {
  1096.                                 $asn1ids[] = $xoid->{'identifier'}->__toString();
  1097.                         }
  1098.                         if (isset($xoid->{'asn1-notation'})) {
  1099.                                 $last_id = asn1_last_identifier($xoid->{'asn1-notation'}->__toString());
  1100.                                 if ($last_id) {
  1101.                                         $asn1ids[] = $last_id;
  1102.                                 }
  1103.                         }
  1104.                         if (isset($xoid->{'synonymous-identifier'})) {
  1105.                                 foreach ($xoid->{'synonymous-identifier'} as $synid) {
  1106.                                         $asn1ids[] = $synid->__toString();
  1107.                                 }
  1108.                         }
  1109.                         $asn1ids = array_unique($asn1ids);
  1110.                         foreach ($asn1ids as $asn1id) {
  1111.                                 if (!oid_id_is_valid($asn1id)) {
  1112.                                         $errors[] = _L('Warning').' ['._L('OID %1',$dot_notation).']: '._L('Ignored alphanumeric identifier %1, because it is invalid',$asn1id);
  1113.                                         $this_oid_has_warnings = true;
  1114.                                 } else {
  1115.                                         OIDplus::db()->query("delete from ###asn1id where oid = ? and name = ?", array($id, $asn1id)); // Attention: Requires case-SENSITIVE database collation!!
  1116.                                         OIDplus::db()->query("insert into ###asn1id (oid, name) values (?, ?)", array($id, $asn1id));
  1117.                                 }
  1118.                         }
  1119.  
  1120.                         // ---------------------------------------------------------------------
  1121.  
  1122.                         if (isset($xoid->{'unicode-label'})) {
  1123.                                 $iris = array();
  1124.                                 foreach ($xoid->{'unicode-label'} as $iri) {
  1125.                                         $iris[] = $iri->__toString();
  1126.                                 }
  1127.                                 $iris = array_unique($iris);
  1128.                                 foreach ($iris as $iri) {
  1129.                                         if (!iri_arc_valid($iri, false)) {
  1130.                                                 $errors[] = _L('Warning').' ['._L('OID %1',$dot_notation).']: '._L('Ignored Unicode label %1, because it is invalid',$iri);
  1131.                                                 $this_oid_has_warnings = true;
  1132.                                         } else {
  1133.                                                 OIDplus::db()->query("delete from ###iri where oid = ? and name = ?", array($id, $iri)); // Attention: Requires case-SENSITIVE database collation!!
  1134.                                                 OIDplus::db()->query("insert into ###iri (oid, name) values (?, ?)", array($id, $iri));
  1135.                                         }
  1136.                                 }
  1137.                         }
  1138.  
  1139.                         if ($this_oid_has_warnings) $count_warnings++;
  1140.                         $ok_oids[] = $id;
  1141.                 }
  1142.  
  1143.                 // De-orphanize
  1144.                 //if ($orphan_mode === self::ORPHAN_AUTO_DEORPHAN) {
  1145.                 //      OIDplus::db()->query("update ###objects set parent = 'oid:' where parent like 'oid:%' and parent not in (select id from ###objects)");
  1146.                 //      OIDplusObject::resetObjectInformationCache();
  1147.                 //}
  1148.                 foreach ($ok_oids as $id) {
  1149.                         // De-orphanize if neccessary
  1150.                         if ($orphan_mode === self::ORPHAN_AUTO_DEORPHAN) {
  1151.                                 $res = OIDplus::db()->query("select * from ###objects where id = ? and parent not in (select id from ###objects)", array($id));
  1152.                                 if ($res->any()) {
  1153.                                         $errors[] = _L("%1 was de-orphaned (placed as root OID) because its parent is not existing.",$id);
  1154.                                         $count_warnings++;
  1155.                                         OIDplus::db()->query("update ###objects set parent = 'oid:' where id = ? and parent not in (select id from ###objects)", array($id));
  1156.                                         OIDplusObject::resetObjectInformationCache();
  1157.                                 }
  1158.                         }
  1159.  
  1160.                         // We do the logging at the end, otherwise SUPOIDRA() might not work correctly if the OIDs were not imported in order or if there were orphans
  1161.                         OIDplus::logger()->log("V2:[INFO]OID(%1)+[INFO]SUPOID(%1)+[INFO]SUPOIDRA(%1)+[OK/INFO]A", "Object '%1' was automatically created by the XML import tool", $id);
  1162.                 }
  1163.  
  1164.                 $count_imported_oids = count($ok_oids);
  1165.  
  1166.                 return array($count_imported_oids, $count_already_existing, $count_errors, $count_warnings);
  1167.  
  1168.         }
  1169.  
  1170.         /**
  1171.          * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_8
  1172.          * @param string|null $user
  1173.          * @return array
  1174.          * @throws OIDplusException
  1175.          */
  1176.         public function getNotifications(string $user=null): array {
  1177.                 $notifications = array();
  1178.                 if ((!$user || ($user == 'admin')) && OIDplus::authUtils()->isAdminLoggedIn()) {
  1179.                         if (!url_post_contents_available(true, $reason)) {
  1180.                                 $title = _L('OID-Info.com import/export');
  1181.                                 $notifications[] = new OIDplusNotification('ERR', _L('OIDplus plugin "%1" is enabled, but OIDplus cannot connect to the Internet.', '<a '.OIDplus::gui()->link('oidplus:datatransfer').'>'.htmlentities($title).'</a>').' '.$reason);
  1182.                         }
  1183.                 }
  1184.                 return $notifications;
  1185.         }
  1186.  
  1187. }
  1188.