Subversion Repositories oidplus

Rev

Rev 1269 | Rev 1325 | 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. abstract class OIDplusObject extends OIDplusBaseClass {
  27.  
  28.         /**
  29.          *
  30.          */
  31.         const UUID_NAMEBASED_NS_OidPlusMisc = 'ad1654e6-7e15-11e4-9ef6-78e3b5fc7f22';
  32.  
  33.         /**
  34.          * Please overwrite this function!
  35.          * @param string $node_id
  36.          * @return OIDplusObject|null
  37.          */
  38.         public static function parse(string $node_id)/*: ?OIDplusObject*/ {
  39.                 foreach (OIDplus::getEnabledObjectTypes() as $ot) {
  40.                         try {
  41.                                 $good = false;
  42.                                 if (get_parent_class($ot) == OIDplusObject::class) {
  43.                                         $reflector = new \ReflectionMethod($ot, 'parse');
  44.                                         $isImplemented = ($reflector->getDeclaringClass()->getName() === $ot);
  45.                                         if ($isImplemented) { // avoid endless loop if parse is not overriden
  46.                                                 $good = true;
  47.                                         }
  48.                                 }
  49.                                 // We need to do the workaround with "$good", otherwise PHPstan shows
  50.                                 // "Call to an undefined static method object::parse()"
  51.                                 if ($good && $obj = $ot::parse($node_id)) return $obj;
  52.                         } catch (\Exception $e) {}
  53.                 }
  54.                 return null;
  55.         }
  56.  
  57.         /**
  58.          * @return OIDplusAltId[]
  59.          * @throws OIDplusException
  60.          */
  61.         public function getAltIds(): array {
  62.                 if ($this->isRoot()) return array();
  63.  
  64.                 $ids = array();
  65.  
  66.                 // Creates an OIDplus-Hash-OID
  67.                 if ($this->ns() != 'oid') {
  68.                         $sid = OIDplus::getSystemId(true);
  69.                         if (!empty($sid)) {
  70.                                 $ns_oid = $this->getPlugin()->getManifest()->getOid();
  71.                                 if (str_starts_with($ns_oid, '1.3.6.1.4.1.37476.2.5.2.')) {
  72.                                         // Official ViaThinkSoft object type plugins
  73.                                         // For backwards compatibility with existing IDs,
  74.                                         // set the hash_payload as '<namespace>:<id>'
  75.                                         $hash_payload = $this->nodeId(true);
  76.                                 } else {
  77.                                         // Third-party object type plugins
  78.                                         // Set the hash_payload as '<plugin oid>:<id>'
  79.                                         $hash_payload = $ns_oid.':'.$this->nodeId(false);
  80.                                 }
  81.                                 $oid = $sid . '.' . smallhash($hash_payload);
  82.                                 $ids[] = new OIDplusAltId('oid', $oid, _L('OIDplus Information Object OID'));
  83.                         }
  84.                 }
  85.  
  86.                 // Make a namebased UUID, but...
  87.                 // ... exclude GUID, because a GUID is already a GUID
  88.                 // ... exclude OID, because an OID already has a record UUID_NAMEBASED_NS_OID (defined by IETF) set by class OIDplusOid
  89.                 if (($this->ns() != 'guid') && ($this->ns() != 'oid')) {
  90.                         $ids[] = new OIDplusAltId('guid', gen_uuid_md5_namebased(self::UUID_NAMEBASED_NS_OidPlusMisc, $this->nodeId()), _L('Name based version 3 / MD5 UUID with namespace %1','UUID_NAMEBASED_NS_OidPlusMisc'));
  91.                         $ids[] = new OIDplusAltId('guid', gen_uuid_sha1_namebased(self::UUID_NAMEBASED_NS_OidPlusMisc, $this->nodeId()), _L('Name based version 5 / SHA1 UUID with namespace %1','UUID_NAMEBASED_NS_OidPlusMisc'));
  92.                 }
  93.  
  94.                 // Make a AID based on ViaThinkSoft schema
  95.                 // ... but not for OIDs below oid:1.3.6.1.4.1.37476.30.9, because these are the definition of these Information Object AIDs (which will be decoded in the OID object type plugin)
  96.                 if (($this->ns() != 'aid') && !str_starts_with($this->nodeId(true), 'oid:1.3.6.1.4.1.37476.30.9.')) {
  97.                         $sid = OIDplus::getSystemId(false);
  98.                         if ($sid !== false) {
  99.                                 $ns_oid = $this->getPlugin()->getManifest()->getOid();
  100.                                 if (str_starts_with($ns_oid, '1.3.6.1.4.1.37476.2.5.2.')) {
  101.                                         // Official ViaThinkSoft object type plugins
  102.                                         // For backwards compatibility with existing IDs,
  103.                                         // set the hash_payload as '<namespace>:<id>'
  104.                                         $hash_payload = $this->nodeId(true);
  105.                                 } else {
  106.                                         // Third-party object type plugins
  107.                                         // Set the hash_payload as '<plugin oid>:<id>'
  108.                                         $hash_payload = $ns_oid.':'.$this->nodeId(false);
  109.                                 }
  110.  
  111.                                 $sid_hex = strtoupper(str_pad(dechex((int)$sid),8,'0',STR_PAD_LEFT));
  112.                                 $obj_hex = strtoupper(str_pad(dechex(smallhash($hash_payload)),8,'0',STR_PAD_LEFT));
  113.                                 $aid = 'D276000186B20005'.$sid_hex.$obj_hex;
  114.                                 $ids[] = new OIDplusAltId('aid', $aid, _L('OIDplus Information Object Application Identifier (ISO/IEC 7816)'), ' ('._L('No PIX allowed').')');
  115.                         }
  116.                 }
  117.  
  118.                 return $ids;
  119.         }
  120.  
  121.         /**
  122.          * @return string
  123.          */
  124.         public abstract static function objectTypeTitle(): string;
  125.  
  126.         /**
  127.          * @return string
  128.          */
  129.         public abstract static function objectTypeTitleShort(): string;
  130.  
  131.         /**
  132.          * @return OIDplusObjectTypePlugin|null
  133.          */
  134.         public function getPlugin()/*: ?OIDplusObjectTypePlugin */ {
  135.                 $plugins = OIDplus::getObjectTypePlugins();
  136.                 foreach ($plugins as $plugin) {
  137.                         if (get_class($this) == $plugin::getObjectTypeClassName()) {
  138.                                 return $plugin;
  139.                         }
  140.                 }
  141.                 return null;
  142.         }
  143.  
  144.         /**
  145.          * @return string
  146.          */
  147.         public abstract static function ns(): string;
  148.  
  149.         /**
  150.          * @return string
  151.          */
  152.         public abstract static function root(): string;
  153.  
  154.         /**
  155.          * @return bool
  156.          */
  157.         public abstract function isRoot(): bool;
  158.  
  159.         /**
  160.          * @param bool $with_ns
  161.          * @return string
  162.          */
  163.         public abstract function nodeId(bool $with_ns=true): string;
  164.  
  165.         /**
  166.          * @param string $str
  167.          * @return string mixed
  168.          * @throws OIDplusException
  169.          */
  170.         public abstract function addString(string $str): string;
  171.  
  172.         /**
  173.          * @param OIDplusObject $parent
  174.          * @return string
  175.          */
  176.         public abstract function crudShowId(OIDplusObject $parent): string;
  177.  
  178.         /**
  179.          * @return string
  180.          */
  181.         public function crudInsertPrefix(): string {
  182.                 return '';
  183.         }
  184.  
  185.         /**
  186.          * @return string
  187.          */
  188.         public function crudInsertSuffix(): string {
  189.                 return '';
  190.         }
  191.  
  192.         /**
  193.          * @param OIDplusObject|null $parent
  194.          * @return string
  195.          */
  196.         public abstract function jsTreeNodeName(OIDplusObject $parent = null): string;
  197.  
  198.         /**
  199.          * @return string
  200.          */
  201.         public abstract function defaultTitle(): string;
  202.  
  203.         /**
  204.          * @return bool
  205.          */
  206.         public abstract function isLeafNode(): bool;
  207.  
  208.         /**
  209.          * @param string $title
  210.          * @param string $content
  211.          * @param string $icon
  212.          * @return void
  213.          */
  214.         public abstract function getContentPage(string &$title, string &$content, string &$icon);
  215.  
  216.         /**
  217.          * @param OIDplusRA|string|null $ra
  218.          * @return array
  219.          * @throws OIDplusConfigInitializationException
  220.          * @throws OIDplusException
  221.          */
  222.         public static function getRaRoots($ra=null) : array{
  223.                 if ($ra instanceof OIDplusRA) $ra = $ra->raEmail();
  224.  
  225.                 $out = array();
  226.  
  227.                 if (!OIDplus::baseConfig()->getValue('OBJECT_CACHING', true)) {
  228.                         if (!$ra) {
  229.                                 $res = OIDplus::db()->query("select oChild.id as child_id, oChild.ra_email as child_mail, oParent.ra_email as parent_mail from ###objects as oChild ".
  230.                                                             "left join ###objects as oParent on oChild.parent = oParent.id");
  231.                                 $res->naturalSortByField('oChild.id');
  232.                                 while ($row = $res->fetch_array()) {
  233.                                         if (!OIDplus::authUtils()->isRaLoggedIn($row['parent_mail']) && OIDplus::authUtils()->isRaLoggedIn($row['child_mail'])) {
  234.                                                 $x = self::parse($row['child_id']); // can be NULL if namespace was disabled
  235.                                                 if ($x) $out[] = $x;
  236.                                         }
  237.                                 }
  238.                         } else {
  239.                                 $res = OIDplus::db()->query("select oChild.id as child_id from ###objects as oChild ".
  240.                                                             "left join ###objects as oParent on oChild.parent = oParent.id ".
  241.                                                             "where (".OIDplus::db()->getSlang()->isNullFunction('oParent.ra_email',"''")." <> ? and ".
  242.                                                             OIDplus::db()->getSlang()->isNullFunction('oChild.ra_email',"''")." = ?) or ".
  243.                                                             "      (oParent.ra_email is null and ".OIDplus::db()->getSlang()->isNullFunction('oChild.ra_email',"''")." = ?) ",
  244.                                                             array($ra, $ra, $ra));
  245.                                 $res->naturalSortByField('oChild.id');
  246.                                 while ($row = $res->fetch_array()) {
  247.                                         $x = self::parse($row['child_id']); // can be NULL if namespace was disabled
  248.                                         if ($x) $out[] = $x;
  249.                                 }
  250.                         }
  251.                 } else {
  252.                         if (!$ra) {
  253.                                 $ra_mails_to_check = OIDplus::authUtils()->loggedInRaList();
  254.                                 if (count($ra_mails_to_check) == 0) return $out;
  255.                         } else {
  256.                                 $ra_mails_to_check = array($ra);
  257.                         }
  258.  
  259.                         self::buildObjectInformationCache();
  260.  
  261.                         foreach ($ra_mails_to_check as $check_ra_mail) {
  262.                                 $out_part = array();
  263.  
  264.                                 foreach (self::$object_info_cache as $id => $cacheitem) {
  265.                                         if ($cacheitem[self::CACHE_RA_EMAIL] == $check_ra_mail) {
  266.                                                 $parent = $cacheitem[self::CACHE_PARENT];
  267.                                                 if (!isset(self::$object_info_cache[$parent]) || (self::$object_info_cache[$parent][self::CACHE_RA_EMAIL] != $check_ra_mail)) {
  268.                                                         $out_part[] = $id;
  269.                                                 }
  270.                                         }
  271.                                 }
  272.  
  273.                                 natsort($out_part);
  274.  
  275.                                 foreach ($out_part as $id) {
  276.                                         $obj = self::parse($id);
  277.                                         if ($obj) $out[] = $obj;
  278.                                 }
  279.                         }
  280.                 }
  281.  
  282.                 return $out;
  283.         }
  284.  
  285.         /**
  286.          * @return array
  287.          * @throws OIDplusException
  288.          */
  289.         public static function getAllNonConfidential(): array {
  290.                 $out = array();
  291.  
  292.                 if (!OIDplus::baseConfig()->getValue('OBJECT_CACHING', true)) {
  293.                         $res = OIDplus::db()->query("select id from ###objects where confidential = ?", array(false));
  294.                         $res->naturalSortByField('id');
  295.                         while ($row = $res->fetch_array()) {
  296.                                 $obj = self::parse($row['id']); // will be NULL if the object type is not registered
  297.                                 if ($obj && (!$obj->isConfidential())) {
  298.                                         $out[] = $row['id'];
  299.                                 }
  300.                         }
  301.                 } else {
  302.                         self::buildObjectInformationCache();
  303.  
  304.                         foreach (self::$object_info_cache as $id => $cacheitem) {
  305.                                 $confidential = $cacheitem[self::CACHE_CONFIDENTIAL];
  306.                                 if (!$confidential) {
  307.                                         $obj = self::parse($id); // will be NULL if the object type is not registered
  308.                                         if ($obj && (!$obj->isConfidential())) {
  309.                                                 $out[] = $id;
  310.                                         }
  311.                                 }
  312.                         }
  313.                 }
  314.  
  315.                 return $out;
  316.         }
  317.  
  318.         /**
  319.          * @return bool
  320.          * @throws OIDplusException
  321.          */
  322.         public function isConfidential(): bool {
  323.                 if (!OIDplus::baseConfig()->getValue('OBJECT_CACHING', true)) {
  324.                         //static $confidential_cache = array();
  325.                         $curid = $this->nodeId();
  326.                         //$orig_curid = $curid;
  327.                         //if (isset($confidential_cache[$curid])) return $confidential_cache[$curid];
  328.                         // Recursively search for the confidential flag in the parents
  329.                         while (($res = OIDplus::db()->query("select parent, confidential from ###objects where id = ?", array($curid)))->any()) {
  330.                                 $row = $res->fetch_array();
  331.                                 if ($row['confidential']) {
  332.                                         //$confidential_cache[$curid] = true;
  333.                                         //$confidential_cache[$orig_curid] = true;
  334.                                         return true;
  335.                                 } else {
  336.                                         //$confidential_cache[$curid] = false;
  337.                                 }
  338.                                 $curid = $row['parent'];
  339.                                 //if (isset($confidential_cache[$curid])) {
  340.                                         //$confidential_cache[$orig_curid] = $confidential_cache[$curid];
  341.                                         //return $confidential_cache[$curid];
  342.                                 //}
  343.                         }
  344.  
  345.                         //$confidential_cache[$orig_curid] = false;
  346.                         return false;
  347.                 } else {
  348.                         self::buildObjectInformationCache();
  349.  
  350.                         $curid = $this->nodeId();
  351.                         // Recursively search for the confidential flag in the parents
  352.                         while (isset(self::$object_info_cache[$curid])) {
  353.                                 if (self::$object_info_cache[$curid][self::CACHE_CONFIDENTIAL]) return true;
  354.                                 $curid = self::$object_info_cache[$curid][self::CACHE_PARENT];
  355.                         }
  356.                         return false;
  357.                 }
  358.         }
  359.  
  360.         /**
  361.          * @param OIDplusObject $obj
  362.          * @return bool
  363.          * @throws OIDplusException
  364.          */
  365.         public function isChildOf(OIDplusObject $obj): bool {
  366.                 if (!OIDplus::baseConfig()->getValue('OBJECT_CACHING', true)) {
  367.                         $curid = $this->nodeId();
  368.                         while (($res = OIDplus::db()->query("select parent from ###objects where id = ?", array($curid)))->any()) {
  369.                                 $row = $res->fetch_array();
  370.                                 if ($curid == $obj->nodeId()) return true;
  371.                                 $curid = $row['parent'];
  372.                         }
  373.                         return false;
  374.                 } else {
  375.                         self::buildObjectInformationCache();
  376.  
  377.                         $curid = $this->nodeId();
  378.                         while (isset(self::$object_info_cache[$curid])) {
  379.                                 if ($curid == $obj->nodeId()) return true;
  380.                                 $curid = self::$object_info_cache[$curid][self::CACHE_PARENT];
  381.                         }
  382.                         return false;
  383.                 }
  384.         }
  385.  
  386.         /**
  387.          * @return array
  388.          * @throws OIDplusException
  389.          */
  390.         public function getChildren(): array {
  391.                 $out = array();
  392.                 if (!OIDplus::baseConfig()->getValue('OBJECT_CACHING', true)) {
  393.                         $res = OIDplus::db()->query("select id from ###objects where parent = ?", array($this->nodeId()));
  394.                         while ($row = $res->fetch_array()) {
  395.                                 $obj = self::parse($row['id']);
  396.                                 if (!$obj) continue;
  397.                                 $out[] = $obj;
  398.                         }
  399.                 } else {
  400.                         self::buildObjectInformationCache();
  401.  
  402.                         foreach (self::$object_info_cache as $id => $cacheitem) {
  403.                                 $parent = $cacheitem[self::CACHE_PARENT];
  404.                                 if ($parent == $this->nodeId()) {
  405.                                         $obj = self::parse($id);
  406.                                         if (!$obj) continue;
  407.                                         $out[] = $obj;
  408.                                 }
  409.                         }
  410.                 }
  411.                 return $out;
  412.         }
  413.  
  414.         /**
  415.          * @return OIDplusRA|null
  416.          * @throws OIDplusException
  417.          */
  418.         public function getRa()/*: ?OIDplusRA*/ {
  419.                 $ra = $this->getRaMail();
  420.                 return $ra ? new OIDplusRA($ra) : null;
  421.         }
  422.  
  423.         /**
  424.          * @param OIDplusRA|string|null $ra
  425.          * @return bool
  426.          * @throws OIDplusConfigInitializationException
  427.          * @throws OIDplusException
  428.          */
  429.         public function userHasReadRights($ra=null): bool {
  430.                 if ($ra instanceof OIDplusRA) $ra = $ra->raEmail();
  431.  
  432.                 // If it is not confidential, everybody can read/see it.
  433.                 // Note: This also checks if superior OIDs are confidential.
  434.                 if (!$this->isConfidential()) return true;
  435.  
  436.                 if (!$ra) {
  437.                         // Admin may do everything
  438.                         if (OIDplus::authUtils()->isAdminLoggedIn()) return true;
  439.  
  440.                         // If the RA is logged in, then they can see the OID.
  441.                         $ownRa = $this->getRaMail();
  442.                         if ($ownRa && OIDplus::authUtils()->isRaLoggedIn($ownRa)) return true;
  443.                 } else {
  444.                         // If this OID belongs to the requested RA, then they may see it.
  445.                         if ($this->getRaMail() == $ra) return true;
  446.                 }
  447.  
  448.                 // If someone has rights to an object below our confidential node,
  449.                 // we let him see the confidential node,
  450.                 // Otherwise he could not browse through to his own node.
  451.                 $roots = $this->getRaRoots($ra);
  452.                 foreach ($roots as $root) {
  453.                         if ($root->isChildOf($this)) return true;
  454.                 }
  455.  
  456.                 return false;
  457.         }
  458.  
  459.         /**
  460.          * @param array|null $row
  461.          * @return string|null
  462.          * @throws OIDplusException
  463.          */
  464.         public function getIcon(array $row=null) {
  465.                 $namespace = $this->ns(); // must use $this, not self::, otherwise the virtual method will not be called
  466.  
  467.                 if (is_null($row)) {
  468.                         $ra_email = $this->getRaMail();
  469.                 } else {
  470.                         $ra_email = $row['ra_email'];
  471.                 }
  472.  
  473.                 // $dirs = glob(OIDplus::localpath().'plugins/'.'*'.'/objectTypes/'.$namespace.'/');
  474.                 // if (count($dirs) == 0) return null; // default icon (folder)
  475.                 // $dir = substr($dirs[0], strlen(OIDplus::localpath()));
  476.                 $reflection = new \ReflectionClass($this);
  477.                 $dir = dirname($reflection->getFilename());
  478.                 $dir = substr($dir, strlen(OIDplus::localpath()));
  479.                 $dir = str_replace('\\', '/', $dir);
  480.  
  481.                 if ($this->isRoot()) {
  482.                         $icon = $dir . '/' . $this::treeIconFilename('root');
  483.                 } else {
  484.                         // We use $this:: instead of self:: , because we want to call the overridden methods
  485.                         if ($ra_email && OIDplus::authUtils()->isRaLoggedIn($ra_email)) {
  486.                                 if ($this->isLeafNode()) {
  487.                                         $icon = $dir . '/' . $this::treeIconFilename('own_leaf');
  488.                                         if (!file_exists($icon)) $icon = $dir . '/' . $this::treeIconFilename('own');
  489.                                 } else {
  490.                                         $icon = $dir . '/' . $this::treeIconFilename('own');
  491.                                 }
  492.                         } else {
  493.                                 if ($this->isLeafNode()) {
  494.                                         $icon = $dir . '/' . $this::treeIconFilename('general_leaf');
  495.                                         if (!file_exists($icon)) $icon = $dir . '/' . $this::treeIconFilename('general');
  496.                                 } else {
  497.                                         $icon = $dir . '/' . $this::treeIconFilename('general');
  498.                                 }
  499.                         }
  500.                 }
  501.  
  502.                 if (!file_exists($icon)) return null; // default icon (folder)
  503.  
  504.                 return $icon;
  505.         }
  506.  
  507.         /**
  508.          * @param string $id
  509.          * @return bool
  510.          * @throws OIDplusException
  511.          */
  512.         public static function exists(string $id): bool {
  513.                 if (!OIDplus::baseConfig()->getValue('OBJECT_CACHING', true)) {
  514.                         $res = OIDplus::db()->query("select id from ###objects where id = ?", array($id));
  515.                         return $res->any();
  516.                 } else {
  517.                         self::buildObjectInformationCache();
  518.                         return isset(self::$object_info_cache[$id]);
  519.                 }
  520.         }
  521.  
  522.         /**
  523.          * Get parent gives the next possible parent which is EXISTING in OIDplus
  524.          * It does not give the immediate parent
  525.          * @return OIDplusObject|null
  526.          * @throws OIDplusException
  527.          */
  528.         public function getParent()/*: ?OIDplusObject*/ {
  529.                 if (!OIDplus::baseConfig()->getValue('OBJECT_CACHING', true)) {
  530.                         $res = OIDplus::db()->query("select parent from ###objects where id = ?", array($this->nodeId()));
  531.                         if ($res->any()) {
  532.                                 $row = $res->fetch_array();
  533.                                 $parent = $row['parent'];
  534.                                 $obj = OIDplusObject::parse($parent);
  535.                                 if ($obj) return $obj;
  536.                         }
  537.                 } else {
  538.                         self::buildObjectInformationCache();
  539.                         if (isset(self::$object_info_cache[$this->nodeId()])) {
  540.                                 $parent = self::$object_info_cache[$this->nodeId()][self::CACHE_PARENT];
  541.                                 $obj = OIDplusObject::parse($parent);
  542.                                 if ($obj) return $obj;
  543.                         }
  544.                 }
  545.  
  546.                 // If this OID does not exist, the SQL query "select parent from ..." does not work. So we try to find the next possible parent using one_up()
  547.                 $cur = $this->one_up();
  548.                 if (!$cur) return null;
  549.                 do {
  550.                         // findFitting() checks if that OID exists
  551.                         if ($fitting = self::findFitting($cur->nodeId())) return $fitting;
  552.  
  553.                         $prev = $cur;
  554.                         $cur = $cur->one_up();
  555.                         if (!$cur) return null;
  556.                 } while ($prev->nodeId() !== $cur->nodeId());
  557.  
  558.                 return null;
  559.         }
  560.  
  561.         /**
  562.          * @return string|null
  563.          * @throws OIDplusException
  564.          */
  565.         public function getRaMail() {
  566.                 if (!OIDplus::baseConfig()->getValue('OBJECT_CACHING', true)) {
  567.                         $res = OIDplus::db()->query("select ra_email from ###objects where id = ?", array($this->nodeId()));
  568.                         if (!$res->any()) return null;
  569.                         $row = $res->fetch_array();
  570.                         return $row['ra_email'];
  571.                 } else {
  572.                         self::buildObjectInformationCache();
  573.                         if (isset(self::$object_info_cache[$this->nodeId()])) {
  574.                                 return self::$object_info_cache[$this->nodeId()][self::CACHE_RA_EMAIL];
  575.                         }
  576.                         return null;
  577.                 }
  578.         }
  579.  
  580.         /**
  581.          * @return string|null
  582.          * @throws OIDplusException
  583.          */
  584.         public function getTitle() {
  585.                 if (!OIDplus::baseConfig()->getValue('OBJECT_CACHING', true)) {
  586.                         $res = OIDplus::db()->query("select title from ###objects where id = ?", array($this->nodeId()));
  587.                         if (!$res->any()) return null;
  588.                         $row = $res->fetch_array();
  589.                         return $row['title'];
  590.                 } else {
  591.                         self::buildObjectInformationCache();
  592.                         if (isset(self::$object_info_cache[$this->nodeId()])) {
  593.                                 return self::$object_info_cache[$this->nodeId()][self::CACHE_TITLE];
  594.                         }
  595.                         return null;
  596.                 }
  597.         }
  598.  
  599.         /**
  600.          * @return string|null
  601.          * @throws OIDplusException
  602.          */
  603.         public function getDescription() {
  604.                 if (!OIDplus::baseConfig()->getValue('OBJECT_CACHING', true)) {
  605.                         $res = OIDplus::db()->query("select description from ###objects where id = ?", array($this->nodeId()));
  606.                         if (!$res->any()) return null;
  607.                         $row = $res->fetch_array();
  608.                         return $row['description'];
  609.                 } else {
  610.                         self::buildObjectInformationCache();
  611.                         if (isset(self::$object_info_cache[$this->nodeId()])) {
  612.                                 return self::$object_info_cache[$this->nodeId()][self::CACHE_DESCRIPTION];
  613.                         }
  614.                         return null;
  615.                 }
  616.         }
  617.  
  618.         /**
  619.          * @return string|null
  620.          * @throws OIDplusException
  621.          */
  622.         public function getComment() {
  623.                 if (!OIDplus::baseConfig()->getValue('OBJECT_CACHING', true)) {
  624.                         $res = OIDplus::db()->query("select comment from ###objects where id = ?", array($this->nodeId()));
  625.                         if (!$res->any()) return null;
  626.                         $row = $res->fetch_array();
  627.                         return $row['comment'];
  628.                 } else {
  629.                         self::buildObjectInformationCache();
  630.                         if (isset(self::$object_info_cache[$this->nodeId()])) {
  631.                                 return self::$object_info_cache[$this->nodeId()][self::CACHE_COMMENT];
  632.                         }
  633.                         return null;
  634.                 }
  635.         }
  636.  
  637.         /**
  638.          * @return string|null
  639.          * @throws OIDplusException
  640.          */
  641.         public function getCreatedTime() {
  642.                 if (!OIDplus::baseConfig()->getValue('OBJECT_CACHING', true)) {
  643.                         $res = OIDplus::db()->query("select created from ###objects where id = ?", array($this->nodeId()));
  644.                         if (!$res->any()) return null;
  645.                         $row = $res->fetch_array();
  646.                         return $row['created'];
  647.                 } else {
  648.                         self::buildObjectInformationCache();
  649.                         if (isset(self::$object_info_cache[$this->nodeId()])) {
  650.                                 return self::$object_info_cache[$this->nodeId()][self::CACHE_CREATED];
  651.                         }
  652.                         return null;
  653.                 }
  654.         }
  655.  
  656.         /**
  657.          * @return string|null
  658.          * @throws OIDplusException
  659.          */
  660.         public function getUpdatedTime() {
  661.                 if (!OIDplus::baseConfig()->getValue('OBJECT_CACHING', true)) {
  662.                         $res = OIDplus::db()->query("select updated from ###objects where id = ?", array($this->nodeId()));
  663.                         if (!$res->any()) return null;
  664.                         $row = $res->fetch_array();
  665.                         return $row['updated'];
  666.                 } else {
  667.                         self::buildObjectInformationCache();
  668.                         if (isset(self::$object_info_cache[$this->nodeId()])) {
  669.                                 return self::$object_info_cache[$this->nodeId()][self::CACHE_UPDATED];
  670.                         }
  671.                         return null;
  672.                 }
  673.         }
  674.  
  675.         /**
  676.          * @param OIDplusRA|string|null $ra
  677.          * @return bool
  678.          * @throws OIDplusException
  679.          */
  680.         public function userHasParentalWriteRights($ra=null): bool {
  681.                 if ($ra instanceof OIDplusRA) $ra = $ra->raEmail();
  682.  
  683.                 if (!$ra) {
  684.                         if (OIDplus::authUtils()->isAdminLoggedIn()) return true;
  685.                 }
  686.  
  687.                 $objParent = $this->getParent();
  688.                 if (!$objParent) return false;
  689.                 return $objParent->userHasWriteRights($ra);
  690.         }
  691.  
  692.         /**
  693.          * @param OIDplusRA|string|null $ra
  694.          * @return bool
  695.          * @throws OIDplusException
  696.          */
  697.         public function userHasWriteRights($ra=null): bool {
  698.                 if ($ra instanceof OIDplusRA) $ra = $ra->raEmail();
  699.  
  700.                 if (!$ra) {
  701.                         if (OIDplus::authUtils()->isAdminLoggedIn()) return true;
  702.                         // TODO: should we allow that the parent RA also may update title/description about this OID (since they delegated it?)
  703.                         $ownRa = $this->getRaMail();
  704.                         return $ownRa && OIDplus::authUtils()->isRaLoggedIn($ownRa);
  705.                 } else {
  706.                         return $this->getRaMail() == $ra;
  707.                 }
  708.         }
  709.  
  710.         /**
  711.          * @param string|OIDplusObject $to
  712.          * @return int|null
  713.          */
  714.         public function distance($to)/*: ?int*/ {
  715.                 return null; // not implemented
  716.         }
  717.  
  718.         /**
  719.          * @param OIDplusObject|string $obj
  720.          * @return bool
  721.          */
  722.         public function equals($obj): bool {
  723.                 if (!$obj) return false;
  724.                 if (!is_object($obj)) $obj = OIDplusObject::parse($obj);
  725.                 if (!$obj) return false;
  726.                 if (!($obj instanceof $this)) return false;
  727.  
  728.                 $distance = $this->distance($obj);
  729.                 if (is_numeric($distance)) return $distance === 0; // if the distance function is implemented, use it
  730.  
  731.                 return $this->nodeId() == $obj->nodeId(); // otherwise compare the node id case-sensitive
  732.         }
  733.  
  734.         /**
  735.          * @param string $id
  736.          * @return OIDplusObject|false
  737.          * @throws OIDplusException
  738.          */
  739.         public static function findFitting(string $id) {
  740.                 $obj = OIDplusObject::parse($id);
  741.                 if (!$obj) return false; // e.g. if ObjectType plugin is disabled
  742.  
  743.                 if (!OIDplus::baseConfig()->getValue('OBJECT_CACHING', true)) {
  744.                         $res = OIDplus::db()->query("select id from ###objects where id like ?", array($obj->ns().':%'));
  745.                         while ($row = $res->fetch_object()) {
  746.                                 $test = OIDplusObject::parse($row->id);
  747.                                 if ($obj->equals($test)) return $test;
  748.                         }
  749.                         return false;
  750.                 } else {
  751.                         self::buildObjectInformationCache();
  752.                         foreach (self::$object_info_cache as $id => $cacheitem) {
  753.                                 if (strpos($id, $obj->ns().':') === 0) {
  754.                                         $test = OIDplusObject::parse($id);
  755.                                         if ($obj->equals($test)) return $test;
  756.                                 }
  757.                         }
  758.                         return false;
  759.                 }
  760.         }
  761.  
  762.         /**
  763.          * @return OIDplusObject|null
  764.          */
  765.         public function one_up()/*: ?OIDplusObject*/ {
  766.                 return null; // not implemented
  767.         }
  768.  
  769.         // Caching stuff
  770.  
  771.         protected static $object_info_cache = null;
  772.  
  773.         /**
  774.          * @return void
  775.          */
  776.         public static function resetObjectInformationCache() {
  777.                 self::$object_info_cache = null;
  778.         }
  779.  
  780.         const CACHE_ID = 'id';
  781.         const CACHE_PARENT = 'parent';
  782.         const CACHE_TITLE = 'title';
  783.         const CACHE_DESCRIPTION = 'description';
  784.         const CACHE_RA_EMAIL = 'ra_email';
  785.         const CACHE_CONFIDENTIAL = 'confidential';
  786.         const CACHE_CREATED = 'created';
  787.         const CACHE_UPDATED = 'updated';
  788.         const CACHE_COMMENT = 'comment';
  789.  
  790.         /**
  791.          * @return void
  792.          * @throws OIDplusException
  793.          */
  794.         private static function buildObjectInformationCache() {
  795.                 if (is_null(self::$object_info_cache)) {
  796.                         self::$object_info_cache = array();
  797.                         $res = OIDplus::db()->query("select * from ###objects");
  798.                         while ($row = $res->fetch_array()) {
  799.                                 self::$object_info_cache[$row['id']] = $row;
  800.                         }
  801.                 }
  802.         }
  803.  
  804.         /**
  805.          * override this function if you want your object type to save
  806.          * attachments in directories with easy names.
  807.          * Take care that your custom directory name will not allow jailbreaks (../) !
  808.          * @return string
  809.          * @throws OIDplusException
  810.          */
  811.         public function getDirectoryName(): string {
  812.                 if ($this->isRoot()) return $this->ns();
  813.                 return $this->getLegacyDirectoryName();
  814.         }
  815.  
  816.         /**
  817.          * @return string
  818.          * @throws OIDplusException
  819.          */
  820.         public final function getLegacyDirectoryName(): string {
  821.                 if ($this::ns() == 'oid') {
  822.                         $oid = $this->nodeId(false);
  823.                 } else {
  824.                         $oid = null;
  825.                         $alt_ids = $this->getAltIds();
  826.                         foreach ($alt_ids as $alt_id) {
  827.                                 if ($alt_id->getNamespace() == 'oid') {
  828.                                         $oid = $alt_id->getId();
  829.                                         break; // we prefer the first OID (for GUIDs, the first OID is the OIDplus-OID, and the second OID is the UUID OID)
  830.                                 }
  831.                         }
  832.                 }
  833.  
  834.                 if (!is_null($oid) && ($oid != '')) {
  835.                         // For OIDs, it is the OID, for other identifiers
  836.                         // it it the OID alt ID (generated using the SystemID)
  837.                         return str_replace('.', '_', $oid);
  838.                 } else {
  839.                         // Can happen if you don't have a system ID (due to missing OpenSSL plugin)
  840.                         return md5($this->nodeId(true)); // we don't use $id, because $this->nodeId(true) is possibly more canonical than $id
  841.                 }
  842.         }
  843.  
  844.         /**
  845.          * @param string $mode
  846.          * @return string
  847.          */
  848.         public static function treeIconFilename(string $mode): string {
  849.                 // for backwards-compatibility with older plugins
  850.                 return 'img/treeicon_'.$mode.'.png';
  851.         }
  852.  
  853. }
  854.