Subversion Repositories oidplus

Rev

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

  1. <?php
  2.  
  3. /*
  4.  * OIDplus 2.0
  5.  * Copyright 2022 - 2024 Daniel Marschall, ViaThinkSoft / Till Wehowski, Frdlweb
  6.  *
  7.  * Licensed under the MIT License.
  8.  */
  9.  
  10. namespace Frdlweb\OIDplus;
  11.  
  12. use ViaThinkSoft\OIDplus\INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3;
  13. use ViaThinkSoft\OIDplus\INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_4;
  14. use ViaThinkSoft\OIDplus\INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_7;
  15. use ViaThinkSoft\OIDplus\INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_8;
  16. use ViaThinkSoft\OIDplus\OIDplus;
  17. use ViaThinkSoft\OIDplus\OIDplusObject;
  18. use ViaThinkSoft\OIDplus\OIDplusPagePluginPublic;
  19. use ViaThinkSoft\OIDplus\OIDplusNotification;
  20.  
  21. // phpcs:disable PSR1.Files.SideEffects
  22. \defined('INSIDE_OIDPLUS') or die;
  23. // phpcs:enable PSR1.Files.SideEffects
  24.  
  25. class OIDplusPagePublicAltIds extends OIDplusPagePluginPublic
  26.         implements INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_4,  /* whois*Attributes */
  27.                    INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_7,  /* getAlternativesForQuery */
  28.                    INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_8,  /* getNotifications */
  29.                    INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3   /* *objects* */
  30. {
  31.  
  32.         /**
  33.          * @var bool
  34.          */
  35.         private $db_table_exists;
  36.  
  37.  
  38.         /**
  39.          * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3
  40.          * @param string $id
  41.          * @return void
  42.          */
  43.         public function beforeObjectDelete(string $id){
  44.  
  45.         }
  46.  
  47.         /**
  48.          * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3
  49.          * @param string $id
  50.          * @return void
  51.          */
  52.         public function afterObjectDelete(string $id){
  53.                 if (!$this->db_table_exists) return;
  54.                 OIDplus::db()->query("DELETE FROM ###altids WHERE origin = ?", [$id]);
  55.         }
  56.  
  57.         /**
  58.          * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3
  59.          * @param string $id
  60.          * @param array $params
  61.          * @return void
  62.          */
  63.         public function beforeObjectUpdateSuperior(string $id, array &$params){
  64.  
  65.         }
  66.  
  67.         /**
  68.          * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3
  69.          * @param string $id
  70.          * @param array $params
  71.          * @return void
  72.          */
  73.         public function afterObjectUpdateSuperior(string $id, array &$params){
  74.                 $this->saveAltIdsForQuery($id);
  75.         }
  76.  
  77.         /**
  78.          * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3
  79.          * @param string $id
  80.          * @param array $params
  81.          * @return void
  82.          */
  83.         public function beforeObjectUpdateSelf(string $id, array &$params){
  84.  
  85.         }
  86.  
  87.         /**
  88.          * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3
  89.          * @param string $id
  90.          * @param array $params
  91.          * @return void
  92.          */
  93.         public function afterObjectUpdateSelf(string $id, array &$params){
  94.                 $this->saveAltIdsForQuery($id);
  95.         }
  96.  
  97.         /**
  98.          * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3
  99.          * @param string $id
  100.          * @param array $params
  101.          * @return void
  102.          */
  103.         public function beforeObjectInsert(string $id, array &$params){
  104.  
  105.         }
  106.  
  107.         /**
  108.          * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3
  109.          * @param string $id
  110.          * @param array $params
  111.          * @return void
  112.          */
  113.         public function afterObjectInsert(string $id, array &$params){
  114.                 $this->saveAltIdsForQuery($id);
  115.         }
  116.  
  117.  
  118.         /**
  119.          * Adds the required database table if DBMS is known
  120.          * @param bool $html
  121.          * @return void
  122.          * @throws \ViaThinkSoft\OIDplus\OIDplusConfigInitializationException
  123.          * @throws \ViaThinkSoft\OIDplus\OIDplusException
  124.          */
  125.         public function init(bool $html=true) {
  126.                 if (!OIDplus::db()->tableExists("###altids")) {
  127.                         if (OIDplus::db()->getSlang()->id() == 'mysql') {
  128.                                 OIDplus::db()->query("CREATE TABLE ###altids ( `origin` varchar(255) NOT NULL, `alternative` varchar(255) NOT NULL, UNIQUE KEY (`origin`, `alternative`)   )");
  129.                                 $this->db_table_exists = true;
  130.                         } else if (OIDplus::db()->getSlang()->id() == 'mssql') {
  131.                                 // We use nvarchar(225) instead of varchar(255), see https://github.com/frdl/oidplus-plugin-alternate-id-tracking/issues/18
  132.                                 // Unfortunately, we cannot use nvarchar(255), because we need two of them for the primary key, and an index must not be greater than 900 bytes in SQL Server.
  133.                                 // Therefore we can only use 225 Unicode characters instead of 255.
  134.                                 // It is very unlikely that someone has such giant identifiers. But if they do, then saveAltIdsForQuery() will reject the INSERT commands to avoid that an SQL Exception is thrown.
  135.                                 OIDplus::db()->query("CREATE TABLE ###altids ( [origin] nvarchar(225) NOT NULL, [alternative] nvarchar(225) NOT NULL, CONSTRAINT [PK_###altids] PRIMARY KEY CLUSTERED( [origin] ASC, [alternative] ASC ) )");
  136.                                 $this->db_table_exists = true;
  137.                         } else if (OIDplus::db()->getSlang()->id() == 'oracle') {
  138.                                 // TODO: Implement Table Creation for this DBMS (see CREATE TABLE syntax at plugins/viathinksoft/sqlSlang/oracle/sql/*.sql)
  139.                                 $this->db_table_exists = false;
  140.                         } else if (OIDplus::db()->getSlang()->id() == 'pgsql') {
  141.                                 // TODO: Implement Table Creation for this DBMS (see CREATE TABLE syntax at plugins/viathinksoft/sqlSlang/pgsql/sql/*.sql)
  142.                                 $this->db_table_exists = false;
  143.                         } else if (OIDplus::db()->getSlang()->id() == 'access') {
  144.                                 // TODO: Implement Table Creation for this DBMS (see CREATE TABLE syntax at plugins/viathinksoft/sqlSlang/access/sql/*.sql)
  145.                                 $this->db_table_exists = false;
  146.                         } else if (OIDplus::db()->getSlang()->id() == 'sqlite') {
  147.                                 // TODO: Implement Table Creation for this DBMS (see CREATE TABLE syntax at plugins/viathinksoft/sqlSlang/sqlite/sql/*.sql)
  148.                                 $this->db_table_exists = false;
  149.                         } else if (OIDplus::db()->getSlang()->id() == 'firebird') {
  150.                                 // TODO: Implement Table Creation for this DBMS (see CREATE TABLE syntax at plugins/viathinksoft/sqlSlang/firebird/sql/*.sql)
  151.                                 $this->db_table_exists = false;
  152.                         } else {
  153.                                 // DBMS not supported
  154.                                 $this->db_table_exists = false;
  155.                         }
  156.                 } else {
  157.                         $this->db_table_exists = true;
  158.                 }
  159.  
  160.                 // Whenever a user visits a page, we need to update our cache, so that reverse-lookups are possible later
  161.                 // TODO! Dirty hack. We need a cleaner solution...
  162.                 if (isset($_REQUEST['goto'])) $this->saveAltIdsForQuery($_REQUEST['goto']); // => solve using implementing gui()?
  163.                 if (isset($_REQUEST['query'])) $this->saveAltIdsForQuery($_REQUEST['query']); // for webwhois.php?query=... and rdap.php?query=...
  164.                 if (isset($_REQUEST['id'])) $this->saveAltIdsForQuery($_REQUEST['id']); // => solve using implementing action()?
  165.         }
  166.  
  167.         // TODO: call this via cronjob  https://github.com/frdl/oidplus-plugin-alternate-id-tracking/issues/20
  168.         public function renewAll() {
  169.                 if (!$this->db_table_exists) return;
  170.  
  171.                 OIDplus::db()->query("DELETE FROM ###altids");
  172.                 $resQ = OIDplus::db()->query("SELECT * FROM ###objects");
  173.                 while ($row = $resQ->fetch_array()) {
  174.                         $this->saveAltIdsForQuery($row['id']);
  175.                 }
  176.         }
  177.  
  178.         protected function saveAltIdsForQuery(string $id){
  179.                 if (!$this->db_table_exists) return;
  180.  
  181.                 $obj = OIDplusObject::parse($id);
  182.                 if (!$obj) return; // e.g. if plugin is disabled
  183.                 $ary = $obj->getAltIds();
  184.                 $origin = $obj->nodeId(true);
  185.  
  186.                 OIDplus::db()->query("DELETE FROM ###altids WHERE origin = ?", [$id]);
  187.  
  188.                 // Why prefiltering? Consider the following testcase:
  189.                 // "oid:1.3.6.1.4.1.37553.8.8.2" defines alt ID "mac:63-CF-E4-AE-C5-66" which is NOT canonized (otherwise it would not look good)!
  190.                 // You must be able to enter "mac:63-CF-E4-AE-C5-66" in the search box, which gets canonized
  191.                 // to mac:63CFE4AEC566 and must be resolved to "oid:1.3.6.1.4.1.37553.8.8.2" by this plugin.
  192.                 // Therefore we use self::special_in_array().
  193.                 // However, it is mandatory, that previously saveAltIdsForQuery("oid:1.3.6.1.4.1.37553.8.8.2") was called once!
  194.                 // Please also note that the "weid:" to "oid:" converting is handled by prefilterQuery(), but only if the OID plugin is installed.
  195.                 $origin_prefiltered = OIDplus::prefilterQuery($origin, false);
  196.                 if($origin_prefiltered !== $origin){
  197.                         $ok = true;
  198.                         if (OIDplus::db()->getSlang()->id() == 'mssql') {
  199.                                 // Explanation: See comment in the init() method.
  200.                                 if ((strlen($origin) > 225) || (strlen($origin_prefiltered) > 225)) $ok = false;
  201.                         }
  202.                         if ($ok) {
  203.                                 try {
  204.                                         OIDplus::db()->query("INSERT INTO ###altids (origin, alternative) VALUES (?,?);", [$origin, $origin_prefiltered]);
  205.                                 } catch (\Exception $e) {
  206.                                         // There could be a Primary Key collission if this method is called simultaneously at the same moment
  207.                                         // Ignore it. The last caller will eventually execute all INSERTs after its call to DELETE.
  208.                                 }
  209.                         }
  210.                 }
  211.  
  212.                 foreach ($ary as $a) {
  213.                         $alternative = $a->getNamespace() . ':' . $a->getId();
  214.                         $ok = true;
  215.                         if (OIDplus::db()->getSlang()->id() == 'mssql') {
  216.                                 // Explanation: See comment in the init() method.
  217.                                 if ((strlen($origin) > 225) || (strlen($alternative) > 225)) $ok = false;
  218.                         }
  219.                         if ($ok) {
  220.                                 try {
  221.                                         OIDplus::db()->query("INSERT INTO ###altids (origin, alternative) VALUES (?,?);", [$origin, $alternative]);
  222.                                 } catch (\Exception $e) {
  223.                                         // There could be a Primary Key collission if this method is called simultaneously at the same moment
  224.                                         // Ignore it. The last caller will eventually execute all INSERTs after its call to DELETE.
  225.                                 }
  226.                         }
  227.  
  228.                         $alternative_prefiltered = OIDplus::prefilterQuery($alternative, false);
  229.                         if($alternative_prefiltered !== $alternative){
  230.                                 $ok = true;
  231.                                 if (OIDplus::db()->getSlang()->id() == 'mssql') {
  232.                                         // Explanation: See comment in the init() method.
  233.                                         if ((strlen($origin) > 225) || (strlen($alternative_prefiltered) > 225)) $ok = false;
  234.                                 }
  235.                                 if ($ok) {
  236.                                         try {
  237.                                                 OIDplus::db()->query("INSERT INTO ###altids (origin, alternative) VALUES (?,?);", [$origin, $alternative_prefiltered]);
  238.                                         } catch (\Exception $e) {
  239.                                                 // There could be a Primary Key collission if this method is called simultaneously at the same moment
  240.                                                 // Ignore it. The last caller will eventually execute all INSERTs after its call to DELETE.
  241.                                         }
  242.                                 }
  243.                         }
  244.                 }
  245.         }
  246.  
  247.         /**
  248.          * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_7
  249.          * @param string $id
  250.          * @return array|string[]
  251.          * @throws \ReflectionException
  252.          * @throws \ViaThinkSoft\OIDplus\OIDplusConfigInitializationException
  253.          * @throws \ViaThinkSoft\OIDplus\OIDplusException
  254.          */
  255.         public function getAlternativesForQuery(string $id): array {
  256.                 if (!$this->db_table_exists) return [];
  257.  
  258.                 $id_prefiltered = OIDplus::prefilterQuery($id, false);
  259.  
  260.                 $res = [
  261.                         $id,
  262.                         $id_prefiltered
  263.                 ];
  264.  
  265.                 $resQ = OIDplus::db()->query("SELECT origin, alternative FROM ###altids WHERE origin = ? OR alternative = ? OR origin = ? OR alternative = ?", [$res[0],$res[0],$res[1],$res[1]]);
  266.                 while ($row = $resQ->fetch_array()) {
  267.                         if(!in_array($row['origin'], $res)){
  268.                                 $res[]=$row['origin'];
  269.                         }
  270.                         if(!in_array($row['alternative'], $res)){
  271.                                 $res[]=$row['alternative'];
  272.                         }
  273.                 }
  274.  
  275.                 return array_unique($res);
  276.         }
  277.  
  278.         /**
  279.          * @param string $id
  280.          * @param array $out
  281.          * @param bool $handled
  282.          * @return void
  283.          */
  284.         public function gui(string $id, array &$out, bool &$handled) {
  285.                 // $this->saveAltIdsForQuery($id);
  286.         }
  287.  
  288.         /**
  289.          * @param array $out
  290.          * @return void
  291.          */
  292.         public function publicSitemap(array &$out) {
  293.  
  294.         }
  295.  
  296.         /**
  297.          * @param array $json
  298.          * @param string|null $ra_email
  299.          * @param bool $nonjs
  300.          * @param string $req_goto
  301.          * @return bool
  302.          */
  303.         public function tree(array &$json, string $ra_email=null, bool $nonjs=false, string $req_goto=''): bool {
  304.                 return false;
  305.         }
  306.  
  307.         /**
  308.          * @param string $request
  309.          * @return array|false
  310.          */
  311.         public function tree_search(string $request) {
  312.                 return false;
  313.         }
  314.  
  315.         /**
  316.          * @param string $id
  317.          * @return false|mixed|string
  318.          * @throws \ViaThinkSoft\OIDplus\OIDplusException
  319.          */
  320.         public function getCanonical(string $id){
  321.                 foreach($this->getAlternativesForQuery($id) as $alt){
  322.                         if (strpos($alt,':') !== false) {
  323.                                 list($ns, $altIdRaw) = explode(':', $alt, 2);
  324.                                 if($ns === 'oid'){
  325.                                         return $alt;
  326.                                 }
  327.                         }
  328.                 }
  329.                 return false;
  330.         }
  331.  
  332.         /**
  333.          * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_4
  334.          * @param string $id
  335.          * @param array $out
  336.          * @return void
  337.          * @throws \ViaThinkSoft\OIDplus\OIDplusException
  338.          */
  339.         public function whoisObjectAttributes(string $id, array &$out) {
  340.                 $xmlns = 'oidplus-frdlweb-altids-plugin';
  341.                 $xmlschema = 'urn:oid:1.3.6.1.4.1.37553.8.1.8.8.53354196964.641310544.1714020422';
  342.                 $xmlschemauri = OIDplus::webpath(__DIR__.'/altids.xsd',OIDplus::PATH_ABSOLUTE_CANONICAL);
  343.  
  344.                 $handleShown = false;
  345.                 $canonicalShown = false;
  346.  
  347.                 $out1 = array();
  348.                 $out2 = array();
  349.  
  350.                 //$tmp = $this->getAlternativesForQuery($id);
  351.                 $obj = OIDplusObject::parse($id);
  352.                 $tmp = [
  353.                         $this->getCanonical($id),
  354.                 ];
  355.                 foreach ($obj->getAltIds() as $altId) {
  356.                         $tmp[] = $altId->getNamespace().':'.$altId->getId();
  357.                 }
  358.  
  359.                 sort($tmp); // DM 26.03.2023 : Added sorting (intended to sort "alternate-identifier")
  360.                 foreach($tmp as $alt) {
  361.                         if (strpos($alt,':') === false) continue;
  362.  
  363.                         list($ns, $altIdRaw) = explode(':', $alt, 2);
  364.  
  365.                         if (($canonicalShown === false) && ($ns === 'oid')) {
  366.                                 $canonicalShown=true;
  367.  
  368.                                 $out1[] = [
  369.                                         'xmlns' => $xmlns,
  370.                                         'xmlschema' => $xmlschema,
  371.                                         'xmlschemauri' => $xmlschemauri,
  372.                                         'name' => 'canonical-identifier',
  373.                                         'value' => $ns.':'.$altIdRaw,
  374.                                 ];
  375.  
  376.                         }
  377.  
  378.                         if (($handleShown === false) && ($alt === $id)) {
  379.                                 $handleShown=true;
  380.  
  381.                                 $out1[] = [
  382.                                         'xmlns' => $xmlns,
  383.                                         'xmlschema' => $xmlschema,
  384.                                         'xmlschemauri' => $xmlschemauri,
  385.                                         'name' => 'handle-identifier',
  386.                                         'value' => $alt,
  387.                                 ];
  388.  
  389.                         }
  390.  
  391.                         if ($alt !== $id) { // DM 26.03.2023 : Added condition that alternate must not be the id itself
  392.                                 $out2[] = [
  393.                                         'xmlns' => $xmlns,
  394.                                         'xmlschema' => $xmlschema,
  395.                                         'xmlschemauri' => $xmlschemauri,
  396.                                         'name' => 'alternate-identifier',
  397.                                         'value' => $ns.':'.$altIdRaw,
  398.                                 ];
  399.                         }
  400.  
  401.                 }
  402.  
  403.                 // DM 26.03.2023 : Added this
  404.                 $out = array_merge($out, $out1); // handle-identifier and canonical-identifier
  405.                 $out = array_merge($out, $out2); // alternate-identifier
  406.  
  407.         }
  408.  
  409.         /**
  410.          * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_4
  411.          * @param string $email
  412.          * @param array $out
  413.          * @return void
  414.          */
  415.         public function whoisRaAttributes(string $email, array &$out) {
  416.  
  417.         }
  418.  
  419.         /**
  420.          * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_8
  421.          * @param string|null $user
  422.          * @return array
  423.          * @throws \ViaThinkSoft\OIDplus\OIDplusException
  424.          */
  425.         public function getNotifications(string $user=null): array {
  426.                 $notifications = array();
  427.                 if ((!$user || ($user == 'admin')) && OIDplus::authUtils()->isAdminLoggedIn()) {
  428.                         if (!$this->db_table_exists) {
  429.                                 $title = _L('Alt ID Plugin');
  430.                                 $notifications[] = new OIDplusNotification('ERR', _L('OIDplus plugin "%1" is enabled, but it does not know how to create its database tables to this DBMS. Therefore the plugin does not work.', htmlentities($title)));
  431.                         }
  432.                 }
  433.                 return $notifications;
  434.         }
  435.  
  436. }
  437.