2,7 → 2,7 |
|
/* |
* OIDplus 2.0 |
* Copyright 2022 - 2023 Daniel Marschall, ViaThinkSoft / Till Wehowski, Frdlweb |
* Copyright 2022 - 2024 Daniel Marschall, ViaThinkSoft / Till Wehowski, Frdlweb |
* |
* Licensed under the MIT License. |
*/ |
9,11 → 9,14 |
|
namespace Frdlweb\OIDplus; |
|
use ViaThinkSoft\OIDplus\INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3; |
use ViaThinkSoft\OIDplus\INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_4; |
use ViaThinkSoft\OIDplus\INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_7; |
use ViaThinkSoft\OIDplus\INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_8; |
use ViaThinkSoft\OIDplus\OIDplus; |
use ViaThinkSoft\OIDplus\OIDplusObject; |
use ViaThinkSoft\OIDplus\OIDplusPagePluginPublic; |
use ViaThinkSoft\OIDplus\OIDplusNotification; |
|
// phpcs:disable PSR1.Files.SideEffects |
\defined('INSIDE_OIDPLUS') or die; |
20,206 → 23,288 |
// phpcs:enable PSR1.Files.SideEffects |
|
class OIDplusPagePublicAltIds extends OIDplusPagePluginPublic |
implements INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_4, /* whois*Attributes */ |
INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_7 /* getAlternativesForQuery */ |
implements INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_4, /* whois*Attributes */ |
INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_7, /* getAlternativesForQuery */ |
INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_8, /* getNotifications */ |
INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3 /* *objects* */ |
{ |
|
/** |
* @param string $actionID |
* @param array $params |
* @return array |
* @throws \ViaThinkSoft\OIDplus\OIDplusException |
* @var bool |
*/ |
public function action(string $actionID, array $params): array { |
return parent::action($actionID, $params); |
private $db_table_exists; |
|
|
/** |
* Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3 |
* @param string $id |
* @return void |
*/ |
public function beforeObjectDelete(string $id){ |
|
} |
|
/** |
* Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3 |
* @param string $id |
* @param array $out |
* @param bool $handled |
* @return void |
*/ |
public function gui(string $id, array &$out, bool &$handled) { |
public function afterObjectDelete(string $id){ |
if (!$this->db_table_exists) return; |
OIDplus::db()->query("DELETE FROM ###altids WHERE origin = ?", [$id]); |
} |
|
/** |
* Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3 |
* @param string $id |
* @param array $params |
* @return void |
*/ |
public function beforeObjectUpdateSuperior(string $id, array &$params){ |
|
} |
|
/** |
* @param array $out |
* Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3 |
* @param string $id |
* @param array $params |
* @return void |
*/ |
public function publicSitemap(array &$out) { |
public function afterObjectUpdateSuperior(string $id, array &$params){ |
$this->saveAltIdsForQuery($id); |
} |
|
/** |
* Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3 |
* @param string $id |
* @param array $params |
* @return void |
*/ |
public function beforeObjectUpdateSelf(string $id, array &$params){ |
|
} |
|
/** |
* @param array $json |
* @param string|null $ra_email |
* @param bool $nonjs |
* @param string $req_goto |
* @return bool |
* Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3 |
* @param string $id |
* @param array $params |
* @return void |
*/ |
public function tree(array &$json, string $ra_email=null, bool $nonjs=false, string $req_goto=''): bool { |
return false; |
public function afterObjectUpdateSelf(string $id, array &$params){ |
$this->saveAltIdsForQuery($id); |
} |
|
/** |
* @return string|null |
* @throws \ViaThinkSoft\OIDplus\OIDplusException |
* Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3 |
* @param string $id |
* @param array $params |
* @return void |
*/ |
private function cache_id() { |
static $cache_id = null; |
if (!is_null($cache_id)) return $cache_id; |
$cache_id = 'Create='.OIDplus::db()->getScalar("select max(created) as ts from ###objects where created is not null;"); |
$cache_id .= '/Update='.OIDplus::db()->getScalar("select max(updated) as ts from ###objects where updated is not null;"); |
$cache_id .= '/Count='.OIDplus::db()->getScalar("select count(id) as cnt from ###objects;"); |
$plugin_versions = array(); |
foreach (OIDplus::getObjectTypePluginsEnabled() as $otp) { |
$plugin_versions[] = '/'.$otp->getManifest()->getOid().'='.$otp->getManifest()->getVersion(); |
} |
sort($plugin_versions); |
$cache_id .= implode('',$plugin_versions); |
return $cache_id; |
public function beforeObjectInsert(string $id, array &$params){ |
|
} |
|
/** |
* @param bool $noCache |
* @return array[]|mixed|null |
* @throws \ViaThinkSoft\OIDplus\OIDplusException |
* Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3 |
* @param string $id |
* @param array $params |
* @return void |
*/ |
public function readAll(bool $noCache = false) { |
static $local_cache = null; |
public function afterObjectInsert(string $id, array &$params){ |
$this->saveAltIdsForQuery($id); |
} |
|
$cache_file = OIDplus::localpath().'/userdata/cache/frdl_alt_id.ser'; |
if ($noCache === false) { |
// Local cache (to save time for multiple calls during the same HTTP request) |
if (!is_null($local_cache)) return $local_cache; |
|
// File cache (to save time between HTTP requests) |
if (file_exists($cache_file)) { |
$cache_data = unserialize(file_get_contents($cache_file)); |
$cache_id = $cache_data[0]; |
if ($cache_id == $this->cache_id()) { |
return $cache_data[1]; |
} |
/** |
* Adds the required database table if DBMS is known |
* @param bool $html |
* @return void |
* @throws \ViaThinkSoft\OIDplus\OIDplusConfigInitializationException |
* @throws \ViaThinkSoft\OIDplus\OIDplusException |
*/ |
public function init(bool $html=true) { |
if (!OIDplus::db()->tableExists("###altids")) { |
if (OIDplus::db()->getSlang()->id() == 'mysql') { |
OIDplus::db()->query("CREATE TABLE ###altids ( `origin` varchar(255) NOT NULL, `alternative` varchar(255) NOT NULL, UNIQUE KEY (`origin`, `alternative`) )"); |
$this->db_table_exists = true; |
} else if (OIDplus::db()->getSlang()->id() == 'mssql') { |
// We use nvarchar(225) instead of varchar(255), see https://github.com/frdl/oidplus-plugin-alternate-id-tracking/issues/18 |
// 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. |
// Therefore we can only use 225 Unicode characters instead of 255. |
// 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. |
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 ) )"); |
$this->db_table_exists = true; |
} else if (OIDplus::db()->getSlang()->id() == 'oracle') { |
// TODO: Implement Table Creation for this DBMS (see CREATE TABLE syntax at plugins/viathinksoft/sqlSlang/oracle/sql/*.sql) |
$this->db_table_exists = false; |
} else if (OIDplus::db()->getSlang()->id() == 'pgsql') { |
// TODO: Implement Table Creation for this DBMS (see CREATE TABLE syntax at plugins/viathinksoft/sqlSlang/pgsql/sql/*.sql) |
$this->db_table_exists = false; |
} else if (OIDplus::db()->getSlang()->id() == 'access') { |
// TODO: Implement Table Creation for this DBMS (see CREATE TABLE syntax at plugins/viathinksoft/sqlSlang/access/sql/*.sql) |
$this->db_table_exists = false; |
} else if (OIDplus::db()->getSlang()->id() == 'sqlite') { |
// TODO: Implement Table Creation for this DBMS (see CREATE TABLE syntax at plugins/viathinksoft/sqlSlang/sqlite/sql/*.sql) |
$this->db_table_exists = false; |
} else if (OIDplus::db()->getSlang()->id() == 'firebird') { |
// TODO: Implement Table Creation for this DBMS (see CREATE TABLE syntax at plugins/viathinksoft/sqlSlang/firebird/sql/*.sql) |
$this->db_table_exists = false; |
} else { |
// DBMS not supported |
$this->db_table_exists = false; |
} |
} else { |
$this->db_table_exists = true; |
} |
|
$alt_ids = array(); |
$rev_lookup = array(); |
// Whenever a user visits a page, we need to update our cache, so that reverse-lookups are possible later |
// TODO! Dirty hack. We need a cleaner solution... |
if (isset($_REQUEST['goto'])) $this->saveAltIdsForQuery($_REQUEST['goto']); // => solve using implementing gui()? |
if (isset($_REQUEST['query'])) $this->saveAltIdsForQuery($_REQUEST['query']); // for webwhois.php?query=... and rdap.php?query=... |
if (isset($_REQUEST['id'])) $this->saveAltIdsForQuery($_REQUEST['id']); // => solve using implementing action()? |
} |
|
$res = OIDplus::db()->query("select id from ###objects ". |
"where parent <> 'oid:1.3.6.1.4.1.37476.1.2.3.1'"); // TODO FIXME! readAll() is TOOOOO slow if a system has more than 50.000 OIDs!!! DEADLOCK!!! |
while ($row = $res->fetch_array()) { |
$obj = OIDplusObject::parse($row['id']); |
if (!$obj) continue; // e.g. if plugin is disabled |
$ary = $obj->getAltIds(); |
foreach ($ary as $a) { |
$origin = $obj->nodeId(true); |
$alternative = $a->getNamespace() . ':' . $a->getId(); |
// TODO: call this via cronjob https://github.com/frdl/oidplus-plugin-alternate-id-tracking/issues/20 |
public function renewAll() { |
if (!$this->db_table_exists) return; |
|
if (!isset($alt_ids[$origin])) $alt_ids[$origin] = array(); |
$alt_ids[$origin][] = $alternative; |
|
if (!isset($rev_lookup[$alternative])) $rev_lookup[$alternative] = array(); |
$rev_lookup[$alternative][] = $origin; |
} |
OIDplus::db()->query("DELETE FROM ###altids"); |
$resQ = OIDplus::db()->query("SELECT * FROM ###objects"); |
while ($row = $resQ->fetch_array()) { |
$this->saveAltIdsForQuery($row['id']); |
} |
} |
|
$data = array($alt_ids, $rev_lookup); |
protected function saveAltIdsForQuery(string $id){ |
if (!$this->db_table_exists) return; |
|
// File cache (to save time between HTTP requests) |
$cache_data = array($this->cache_id(), $data); |
@file_put_contents($cache_file, serialize($cache_data)); |
$obj = OIDplusObject::parse($id); |
if (!$obj) return; // e.g. if plugin is disabled |
$ary = $obj->getAltIds(); |
$origin = $obj->nodeId(true); |
|
// Local cache (to save time for multiple calls during the same HTTP request) |
$local_cache = $data; |
OIDplus::db()->query("DELETE FROM ###altids WHERE origin = ?", [$id]); |
|
return $data; |
} |
// Why prefiltering? Consider the following testcase: |
// "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)! |
// You must be able to enter "mac:63-CF-E4-AE-C5-66" in the search box, which gets canonized |
// to mac:63CFE4AEC566 and must be resolved to "oid:1.3.6.1.4.1.37553.8.8.2" by this plugin. |
// Therefore we use self::special_in_array(). |
// However, it is mandatory, that previously saveAltIdsForQuery("oid:1.3.6.1.4.1.37553.8.8.2") was called once! |
// Please also note that the "weid:" to "oid:" converting is handled by prefilterQuery(), but only if the OID plugin is installed. |
$origin_prefiltered = OIDplus::prefilterQuery($origin, false); |
if($origin_prefiltered !== $origin){ |
$ok = true; |
if (OIDplus::db()->getSlang()->id() == 'mssql') { |
// Explanation: See comment in the init() method. |
if ((strlen($origin) > 225) || (strlen($origin_prefiltered) > 225)) $ok = false; |
} |
if ($ok) { |
try { |
OIDplus::db()->query("INSERT INTO ###altids (origin, alternative) VALUES (?,?);", [$origin, $origin_prefiltered]); |
} catch (\Exception $e) { |
// There could be a Primary Key collission if this method is called simultaneously at the same moment |
// Ignore it. The last caller will eventually execute all INSERTs after its call to DELETE. |
} |
} |
} |
|
/** |
* Acts like in_array(), but allows includes prefilterQuery, e.g. `mac:AA-BB-CC-DD-EE-FF` can be found in an array containing `mac:AABBCCDDEEFF`. |
* @param string $needle |
* @param array $haystack |
* @return bool |
*/ |
private static function special_in_array(string $needle, array $haystack) { |
$needle_prefiltered = OIDplus::prefilterQuery($needle,false); |
foreach ($haystack as $straw) { |
$straw_prefiltered = OIDplus::prefilterQuery($straw, false); |
if ($needle == $straw) return true; |
else if ($needle == $straw_prefiltered) return true; |
else if ($needle_prefiltered == $straw) return true; |
else if ($needle_prefiltered == $straw_prefiltered) return true; |
foreach ($ary as $a) { |
$alternative = $a->getNamespace() . ':' . $a->getId(); |
$ok = true; |
if (OIDplus::db()->getSlang()->id() == 'mssql') { |
// Explanation: See comment in the init() method. |
if ((strlen($origin) > 225) || (strlen($alternative) > 225)) $ok = false; |
} |
if ($ok) { |
try { |
OIDplus::db()->query("INSERT INTO ###altids (origin, alternative) VALUES (?,?);", [$origin, $alternative]); |
} catch (\Exception $e) { |
// There could be a Primary Key collission if this method is called simultaneously at the same moment |
// Ignore it. The last caller will eventually execute all INSERTs after its call to DELETE. |
} |
} |
|
$alternative_prefiltered = OIDplus::prefilterQuery($alternative, false); |
if($alternative_prefiltered !== $alternative){ |
$ok = true; |
if (OIDplus::db()->getSlang()->id() == 'mssql') { |
// Explanation: See comment in the init() method. |
if ((strlen($origin) > 225) || (strlen($alternative_prefiltered) > 225)) $ok = false; |
} |
if ($ok) { |
try { |
OIDplus::db()->query("INSERT INTO ###altids (origin, alternative) VALUES (?,?);", [$origin, $alternative_prefiltered]); |
} catch (\Exception $e) { |
// There could be a Primary Key collission if this method is called simultaneously at the same moment |
// Ignore it. The last caller will eventually execute all INSERTs after its call to DELETE. |
} |
} |
} |
} |
return false; |
} |
|
/** |
* Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_7 |
* @param string $id |
* @return string[] |
* @return array|string[] |
* @throws \ReflectionException |
* @throws \ViaThinkSoft\OIDplus\OIDplusConfigInitializationException |
* @throws \ViaThinkSoft\OIDplus\OIDplusException |
*/ |
public function getAlternativesForQuery(string $id/* INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_7 signature takes just 1 param!? , $noCache = false*/): array { |
public function getAlternativesForQuery(string $id): array { |
if (!$this->db_table_exists) return []; |
|
static $caches = array(); |
$id_prefiltered = OIDplus::prefilterQuery($id, false); |
|
if(/*$noCache === false && */isset($caches[$id]) ){ |
return $caches[$id]; |
} |
|
if (strpos($id,':') !== false) { |
list($ns, $altIdRaw) = explode(':', $id, 2); |
if($ns === 'weid'){ |
$id='oid:'.\Frdl\Weid\WeidOidConverter::weid2oid($id); |
} |
} |
|
list($alt_ids, $rev_lookup) = $this->readAll(false); |
|
$res = [ |
$id, |
$id_prefiltered |
]; |
if(isset($rev_lookup[$id])){ |
$res = array_merge($res, $rev_lookup[$id]); |
} |
foreach($alt_ids as $original => $altIds){ |
// Why self::special_in_array() instead of in_array()? Consider the following testcase: |
// "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! |
// You must be able to enter "mac:63-CF-E4-AE-C5-66" in the search box, which gets canonized |
// to mac:63CFE4AEC566 and must be solved to "oid:1.3.6.1.4.1.37553.8.8.2" by this plugin. |
// Therefore we use self::special_in_array(). |
// However, it is mandatory, that previously saveAltIdsForQuery("oid:1.3.6.1.4.1.37553.8.8.2") was called once! |
// Please also note that the "weid:" to "oid:" converting is handled by prefilterQuery(), but only if the OID plugin is installed. |
if($id === $original || self::special_in_array($id, $altIds) ){ |
$res = array_merge($res, $altIds); |
$res = array_merge($res, [$original]); |
} |
} |
|
$weid = false; |
foreach($res as $alt){ |
if (strpos($alt,':') !== false) { |
list($ns, $altIdRaw) = explode(':', $alt, 2); |
if($ns === 'oid'){ |
$weid=\Frdl\Weid\WeidOidConverter::oid2weid($altIdRaw); |
break; |
} |
$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]]); |
while ($row = $resQ->fetch_array()) { |
if(!in_array($row['origin'], $res)){ |
$res[]=$row['origin']; |
} |
if(!in_array($row['alternative'], $res)){ |
$res[]=$row['alternative']; |
} |
} |
|
if ($weid !== false) { |
$res[]=$weid; |
} |
$res = array_unique($res); |
return array_unique($res); |
} |
|
$caches[$id] = $res; |
/** |
* @param string $id |
* @param array $out |
* @param bool $handled |
* @return void |
*/ |
public function gui(string $id, array &$out, bool &$handled) { |
// $this->saveAltIdsForQuery($id); |
} |
|
return $res; |
/** |
* @param array $out |
* @return void |
*/ |
public function publicSitemap(array &$out) { |
|
} |
|
/** |
* @param array $json |
* @param string|null $ra_email |
* @param bool $nonjs |
* @param string $req_goto |
* @return bool |
*/ |
public function tree(array &$json, string $ra_email=null, bool $nonjs=false, string $req_goto=''): bool { |
return false; |
} |
|
/** |
* @param string $request |
* @return array|false |
*/ |
241,7 → 326,6 |
} |
} |
} |
|
return false; |
} |
|
263,9 → 347,16 |
$out1 = array(); |
$out2 = array(); |
|
$tmp = $this->getAlternativesForQuery($id); |
//$tmp = $this->getAlternativesForQuery($id); |
$obj = OIDplusObject::parse($id); |
$tmp = [ |
$this->getCanonical($id), |
]; |
foreach ($obj->getAltIds() as $altId) { |
$tmp[] = $altId->getNamespace().':'.$altId->getId(); |
} |
|
sort($tmp); // DM 26.03.2023 : Added sorting (intended to sort "alternate-identifier") |
|
foreach($tmp as $alt) { |
if (strpos($alt,':') === false) continue; |
|
325,4 → 416,21 |
|
} |
|
} |
/** |
* Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_8 |
* @param string|null $user |
* @return array |
* @throws \ViaThinkSoft\OIDplus\OIDplusException |
*/ |
public function getNotifications(string $user=null): array { |
$notifications = array(); |
if ((!$user || ($user == 'admin')) && OIDplus::authUtils()->isAdminLoggedIn()) { |
if (!$this->db_table_exists) { |
$title = _L('Alt ID Plugin'); |
$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))); |
} |
} |
return $notifications; |
} |
|
} |