Subversion Repositories oidplus

Rev

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