Subversion Repositories oidplus

Rev

Rev 929 | Rev 956 | 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 - 2022 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. if (!defined('INSIDE_OIDPLUS')) die();
  21.  
  22. abstract class OIDplusObject extends OIDplusBaseClass {
  23.         const UUID_NAMEBASED_NS_OidPlusMisc = 'ad1654e6-7e15-11e4-9ef6-78e3b5fc7f22';
  24.  
  25.         public static function parse($node_id) { // please overwrite this function!
  26.                 // TODO: in case we are not calling this class directly, check if function is overwritten and throw exception otherwise
  27.                 foreach (OIDplus::getEnabledObjectTypes() as $ot) {
  28.                         try {
  29.                                 if ($obj = $ot::parse($node_id)) return $obj;
  30.                         } catch (Exception $e) {}
  31.                 }
  32.                 return null;
  33.         }
  34.  
  35.         public function /*OIDplusAltId[]*/ getAltIds() {
  36.                 if ($this->isRoot()) return array();
  37.  
  38.                 $ids = array();
  39.                 if ($this->ns() != 'oid') {
  40.                         // Creates an OIDplus-Hash-OID
  41.                         $sid = OIDplus::getSystemId(true);
  42.                         if (!empty($sid)) {
  43.                                 $ns_oid = $this->getPlugin()->getManifest()->getOid();
  44.                                 if (str_starts_with($ns_oid, '1.3.6.1.4.1.37476.2.5.2.')) {
  45.                                         // Official ViaThinkSoft object type plugins
  46.                                         // For backwards compatibility with existing IDs,
  47.                                         // set the hash_payload as '<namespace>:<id>'
  48.                                         $hash_payload = $this->nodeId(true);
  49.                                 } else {
  50.                                         // Third-party object type plugins
  51.                                         // Set the hash_payload as '<plugin oid>:<id>'
  52.                                         $hash_payload = $ns_oid.':'.$this->nodeId(false);
  53.                                 }
  54.                                 $oid = $sid . '.' . smallhash($hash_payload);
  55.                                 $ids[] = new OIDplusAltId('oid', $oid, _L('OIDplus Information Object ID'));
  56.                         }
  57.  
  58.                         // Make a namebased UUID, but...
  59.                         // ... exclude GUID, because a GUID is already a GUID
  60.                         // ... exclude OID, because an OID already has a record UUID_NAMEBASED_NS_OID set  by class OIDplusOid
  61.                         if ($this->ns() != 'guid') {
  62.                                 $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'));
  63.                                 $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'));
  64.                         }
  65.                 }
  66.                 return $ids;
  67.         }
  68.  
  69.         public abstract static function objectTypeTitle();
  70.  
  71.         public abstract static function objectTypeTitleShort();
  72.  
  73.         public function getPlugin()/*: ?OIDplusObjectTypePlugin */ {
  74.                 $res = null;
  75.                 $plugins = OIDplus::getObjectTypePlugins();
  76.                 foreach ($plugins as $plugin) {
  77.                         if (get_class($this) == $plugin::getObjectTypeClassName($this)) {
  78.                                 return $plugin;
  79.                         }
  80.                 }
  81.                 return $res;
  82.         }
  83.  
  84.         public abstract static function ns();
  85.  
  86.         public abstract static function root();
  87.  
  88.         public abstract function isRoot();
  89.  
  90.         public abstract function nodeId($with_ns=true);
  91.  
  92.         public abstract function addString($str);
  93.  
  94.         public abstract function crudShowId(OIDplusObject $parent);
  95.  
  96.         public function crudInsertPrefix() {
  97.                 return '';
  98.         }
  99.  
  100.         public function crudInsertSuffix() {
  101.                 return '';
  102.         }
  103.  
  104.         public abstract function jsTreeNodeName(OIDplusObject $parent = null);
  105.  
  106.         public abstract function defaultTitle();
  107.  
  108.         public abstract function isLeafNode();
  109.  
  110.         public abstract function getContentPage(&$title, &$content, &$icon);
  111.  
  112.         public static function getRaRoots($ra_email=null) {
  113.                 if ($ra_email instanceof OIDplusRA) $ra_email = $ra_email->raEmail();
  114.  
  115.                 $out = array();
  116.  
  117.                 if (!OIDplus::baseConfig()->getValue('OBJECT_CACHING', true)) {
  118.                         if (is_null($ra_email)) {
  119.                                 $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 ".
  120.                                                             "left join ###objects as oParent on oChild.parent = oParent.id ".
  121.                                                             "order by ".OIDplus::db()->natOrder('oChild.id'));
  122.                                 while ($row = $res->fetch_array()) {
  123.                                         if (!OIDplus::authUtils()->isRaLoggedIn($row['parent_mail']) && OIDplus::authUtils()->isRaLoggedIn($row['child_mail'])) {
  124.                                                 $x = self::parse($row['id']); // can be FALSE if namespace was disabled
  125.                                                 if ($x) $out[] = $x;
  126.                                         }
  127.                                 }
  128.                         } else {
  129.                                 $res = OIDplus::db()->query("select oChild.id as id from ###objects as oChild ".
  130.                                                             "left join ###objects as oParent on oChild.parent = oParent.id ".
  131.                                                             "where (".OIDplus::db()->getSlang()->isNullFunction('oParent.ra_email',"''")." <> ? and ".
  132.                                                             OIDplus::db()->getSlang()->isNullFunction('oChild.ra_email',"''")." = ?) or ".
  133.                                                             "      (oParent.ra_email is null and ".OIDplus::db()->getSlang()->isNullFunction('oChild.ra_email',"''")." = ?) ".
  134.                                                             "order by ".OIDplus::db()->natOrder('oChild.id'), array($ra_email, $ra_email, $ra_email));
  135.                                 while ($row = $res->fetch_array()) {
  136.                                         $x = self::parse($row['id']); // can be FALSE if namespace was disabled
  137.                                         if ($x) $out[] = self::parse($row['id']);
  138.                                 }
  139.                         }
  140.                 } else {
  141.                         if (is_null($ra_email)) {
  142.                                 $ra_mails_to_check = OIDplus::authUtils()->loggedInRaList();
  143.                                 if (count($ra_mails_to_check) == 0) return $out;
  144.                         } else {
  145.                                 $ra_mails_to_check = array($ra_email);
  146.                         }
  147.  
  148.                         self::buildObjectInformationCache();
  149.  
  150.                         foreach ($ra_mails_to_check as $check_ra_mail) {
  151.                                 $out_part = array();
  152.  
  153.                                 foreach (self::$object_info_cache as $id => list($confidential, $parent, $ra_email, $title)) {
  154.                                         // If the OID RA is the RA we are searching, then add the object to the choice list
  155.                                         if ($ra_email == $check_ra_mail) $out_part[] = $id;
  156.                                 }
  157.  
  158.                                 foreach (self::$object_info_cache as $id => list($confidential, $parent, $ra_email, $title)) {
  159.                                         if (isset(self::$object_info_cache[$parent])) {
  160.                                                 if (self::$object_info_cache[$parent][self::CACHE_RA_EMAIL] == $ra_email) {
  161.                                                         // if the parent has the same RA, then this OID cannot be a root => remove the element from the choice list
  162.                                                         foreach (array_keys($out_part, $id) as $key) unset($out_part[$key]);
  163.                                                 }
  164.                                         }
  165.                                 }
  166.  
  167.                                 natsort($out_part);
  168.  
  169.                                 foreach ($out_part as $id) {
  170.                                         $obj = self::parse($id);
  171.                                         if ($obj) $out[] = $obj;
  172.                                 }
  173.                         }
  174.                 }
  175.  
  176.                 return $out;
  177.         }
  178.  
  179.         public static function getAllNonConfidential() {
  180.                 $out = array();
  181.  
  182.                 if (!OIDplus::baseConfig()->getValue('OBJECT_CACHING', true)) {
  183.                         $res = OIDplus::db()->query("select id from ###objects where confidential = ? order by ".OIDplus::db()->natOrder('id'), array(false));
  184.  
  185.                         while ($row = $res->fetch_array()) {
  186.                                 $obj = self::parse($row['id']); // will be NULL if the object type is not registered
  187.                                 if ($obj && (!$obj->isConfidential())) {
  188.                                         $out[] = $row['id'];
  189.                                 }
  190.                         }
  191.                 } else {
  192.                         self::buildObjectInformationCache();
  193.  
  194.                         foreach (self::$object_info_cache as $id => list($confidential, $parent, $ra_email, $title)) {
  195.                                 if (!$confidential) {
  196.                                         $obj = self::parse($id); // will be NULL if the object type is not registered
  197.                                         if ($obj && (!$obj->isConfidential())) {
  198.                                                 $out[] = $id;
  199.                                         }
  200.                                 }
  201.                         }
  202.                 }
  203.  
  204.                 return $out;
  205.         }
  206.  
  207.         public function isConfidential() {
  208.                 if (!OIDplus::baseConfig()->getValue('OBJECT_CACHING', true)) {
  209.                         //static $confidential_cache = array();
  210.                         $curid = $this->nodeId();
  211.                         //$orig_curid = $curid;
  212.                         //if (isset($confidential_cache[$curid])) return $confidential_cache[$curid];
  213.                         // Recursively search for the confidential flag in the parents
  214.                         while (($res = OIDplus::db()->query("select parent, confidential from ###objects where id = ?", array($curid)))->any()) {
  215.                                 $row = $res->fetch_array();
  216.                                 if ($row['confidential']) {
  217.                                         //$confidential_cache[$curid] = true;
  218.                                         //$confidential_cache[$orig_curid] = true;
  219.                                         return true;
  220.                                 } else {
  221.                                         //$confidential_cache[$curid] = false;
  222.                                 }
  223.                                 $curid = $row['parent'];
  224.                                 //if (isset($confidential_cache[$curid])) {
  225.                                         //$confidential_cache[$orig_curid] = $confidential_cache[$curid];
  226.                                         //return $confidential_cache[$curid];
  227.                                 //}
  228.                         }
  229.  
  230.                         //$confidential_cache[$orig_curid] = false;
  231.                         return false;
  232.                 } else {
  233.                         self::buildObjectInformationCache();
  234.  
  235.                         $curid = $this->nodeId();
  236.                         // Recursively search for the confidential flag in the parents
  237.                         while (isset(self::$object_info_cache[$curid])) {
  238.                                 if (self::$object_info_cache[$curid][self::CACHE_CONFIDENTIAL]) return true;
  239.                                 $curid = self::$object_info_cache[$curid][self::CACHE_PARENT];
  240.                         }
  241.                         return false;
  242.                 }
  243.         }
  244.  
  245.         public function isChildOf(OIDplusObject $obj) {
  246.                 if (!OIDplus::baseConfig()->getValue('OBJECT_CACHING', true)) {
  247.                         $curid = $this->nodeId();
  248.                         while (($res = OIDplus::db()->query("select parent from ###objects where id = ?", array($curid)))->any()) {
  249.                                 $row = $res->fetch_array();
  250.                                 if ($curid == $obj->nodeId()) return true;
  251.                                 $curid = $row['parent'];
  252.                         }
  253.                         return false;
  254.                 } else {
  255.                         self::buildObjectInformationCache();
  256.  
  257.                         $curid = $this->nodeId();
  258.                         while (isset(self::$object_info_cache[$curid])) {
  259.                                 if ($curid == $obj->nodeId()) return true;
  260.                                 $curid = self::$object_info_cache[$curid][self::CACHE_PARENT];
  261.                         }
  262.                         return false;
  263.                 }
  264.         }
  265.  
  266.         public function getChildren() {
  267.                 $out = array();
  268.                 if (!OIDplus::baseConfig()->getValue('OBJECT_CACHING', true)) {
  269.                         $res = OIDplus::db()->query("select id from ###objects where parent = ?", array($this->nodeId()));
  270.                         while ($row = $res->fetch_array()) {
  271.                                 $obj = self::parse($row['id']);
  272.                                 if (!$obj) continue;
  273.                                 $out[] = $obj;
  274.                         }
  275.                 } else {
  276.                         self::buildObjectInformationCache();
  277.  
  278.                         foreach (self::$object_info_cache as $id => list($confidential, $parent, $ra_email, $title)) {
  279.                                 if ($parent == $this->nodeId()) {
  280.                                         $obj = self::parse($id);
  281.                                         if (!$obj) continue;
  282.                                         $out[] = $obj;
  283.                                 }
  284.                         }
  285.                 }
  286.                 return $out;
  287.         }
  288.  
  289.         public function getRa() {
  290.                 return new OIDplusRA($this->getRaMail());
  291.         }
  292.  
  293.         public function userHasReadRights($ra_email=null) {
  294.                 if ($ra_email instanceof OIDplusRA) $ra_email = $ra_email->raEmail();
  295.  
  296.                 // If it is not confidential, everybody can read/see it.
  297.                 // Note: This also checks if superior OIDs are confidential.
  298.                 if (!$this->isConfidential()) return true;
  299.  
  300.                 if (is_null($ra_email)) {
  301.                         // Admin may do everything
  302.                         if (OIDplus::authUtils()->isAdminLoggedIn()) return true;
  303.  
  304.                         // If the RA is logged in, then they can see the OID.
  305.                         if (OIDplus::authUtils()->isRaLoggedIn($this->getRaMail())) return true;
  306.                 } else {
  307.                         // If this OID belongs to the requested RA, then they may see it.
  308.                         if ($this->getRaMail() == $ra_email) return true;
  309.                 }
  310.  
  311.                 // If someone has rights to an object below our confidential node,
  312.                 // we let him see the confidential node,
  313.                 // Otherwise he could not browse through to his own node.
  314.                 $roots = $this->getRaRoots($ra_email);
  315.                 foreach ($roots as $root) {
  316.                         if ($root->isChildOf($this)) return true;
  317.                 }
  318.  
  319.                 return false;
  320.         }
  321.  
  322.         public function getIcon($row=null) {
  323.                 $namespace = $this->ns(); // must use $this, not self::, otherwise the virtual method will not be called
  324.  
  325.                 if (is_null($row)) {
  326.                         $ra_email = $this->getRaMail();
  327.                 } else {
  328.                         $ra_email = $row['ra_email'];
  329.                 }
  330.                 // TODO: have different icons for Leaf-Nodes
  331.  
  332.                 $dirs = glob(OIDplus::localpath().'plugins/'.'*'.'/objectTypes/'.$namespace.'/');
  333.  
  334.                 if (count($dirs) == 0) return null; // default icon (folder)
  335.  
  336.                 $dir = substr($dirs[0], strlen(OIDplus::localpath()));
  337.  
  338.                 // We use $this:: instead of self:: , because we want to call the overridden methods
  339.                 if (OIDplus::authUtils()->isRaLoggedIn($ra_email)) {
  340.                         $icon = $dir.'/'.$this::treeIconFilename('own');
  341.                 } else {
  342.                         $icon = $dir.'/'.$this::treeIconFilename('general');
  343.                 }
  344.  
  345.                 if (!file_exists($icon)) return null; // default icon (folder)
  346.  
  347.                 return $icon;
  348.         }
  349.  
  350.         public static function exists($id) {
  351.                 if (!OIDplus::baseConfig()->getValue('OBJECT_CACHING', true)) {
  352.                         $res = OIDplus::db()->query("select id from ###objects where id = ?", array($id));
  353.                         return $res->any();
  354.                 } else {
  355.                         self::buildObjectInformationCache();
  356.                         return isset(self::$object_info_cache[$id]);
  357.                 }
  358.         }
  359.  
  360.         // Get parent gives the next possible parent which is EXISTING in OIDplus
  361.         // It does not give the immediate parent
  362.         public function getParent() {
  363.                 if (!OIDplus::baseConfig()->getValue('OBJECT_CACHING', true)) {
  364.                         $res = OIDplus::db()->query("select parent from ###objects where id = ?", array($this->nodeId()));
  365.                         if (!$res->any()) return null;
  366.                         $row = $res->fetch_array();
  367.                         $parent = $row['parent'];
  368.                         $obj = OIDplusObject::parse($parent);
  369.                         if ($obj) return $obj;
  370.                         // TODO: Also implement one_up() like below
  371.                 } else {
  372.                         self::buildObjectInformationCache();
  373.                         if (isset(self::$object_info_cache[$this->nodeId()])) {
  374.                                 $parent = self::$object_info_cache[$this->nodeId()][self::CACHE_PARENT];
  375.                                 $obj = OIDplusObject::parse($parent);
  376.                                 if ($obj) return $obj;
  377.                         }
  378.  
  379.                         // 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()
  380.                         $cur = $this->one_up();
  381.                         if (!$cur) return null;
  382.                         do {
  383.                                 // findFitting() checks if that OID exists
  384.                                 if ($fitting = self::findFitting($cur->nodeId())) return $fitting;
  385.  
  386.                                 $prev = $cur;
  387.                                 $cur = $cur->one_up();
  388.                                 if (!$cur) return null;
  389.                         } while ($prev != $cur);
  390.  
  391.                         return null;
  392.                 }
  393.         }
  394.  
  395.         public function getRaMail() {
  396.                 if (!OIDplus::baseConfig()->getValue('OBJECT_CACHING', true)) {
  397.                         $res = OIDplus::db()->query("select ra_email from ###objects where id = ?", array($this->nodeId()));
  398.                         if (!$res->any()) return null;
  399.                         $row = $res->fetch_array();
  400.                         return $row['ra_email'];
  401.                 } else {
  402.                         self::buildObjectInformationCache();
  403.                         if (isset(self::$object_info_cache[$this->nodeId()])) {
  404.                                 return self::$object_info_cache[$this->nodeId()][self::CACHE_RA_EMAIL];
  405.                         }
  406.                         return false;
  407.                 }
  408.         }
  409.  
  410.         public function getTitle() {
  411.                 if (!OIDplus::baseConfig()->getValue('OBJECT_CACHING', true)) {
  412.                         $res = OIDplus::db()->query("select title from ###objects where id = ?", array($this->nodeId()));
  413.                         if (!$res->any()) return null;
  414.                         $row = $res->fetch_array();
  415.                         return $row['title'];
  416.                 } else {
  417.                         self::buildObjectInformationCache();
  418.                         if (isset(self::$object_info_cache[$this->nodeId()])) {
  419.                                 return self::$object_info_cache[$this->nodeId()][self::CACHE_TITLE];
  420.                         }
  421.                         return false;
  422.                 }
  423.         }
  424.  
  425.         public function userHasParentalWriteRights($ra_email=null) {
  426.                 if ($ra_email instanceof OIDplusRA) $ra_email = $ra_email->raEmail();
  427.  
  428.                 if (is_null($ra_email)) {
  429.                         if (OIDplus::authUtils()->isAdminLoggedIn()) return true;
  430.                 }
  431.  
  432.                 $objParent = $this->getParent();
  433.                 if (!$objParent) return false;
  434.                 return $objParent->userHasWriteRights($ra_email);
  435.         }
  436.  
  437.         public function userHasWriteRights($ra_email=null) {
  438.                 if ($ra_email instanceof OIDplusRA) $ra_email = $ra_email->raEmail();
  439.  
  440.                 if (is_null($ra_email)) {
  441.                         if (OIDplus::authUtils()->isAdminLoggedIn()) return true;
  442.                         return OIDplus::authUtils()->isRaLoggedIn($this->getRaMail());
  443.                 } else {
  444.                         return $this->getRaMail() == $ra_email;
  445.                 }
  446.         }
  447.  
  448.         public function distance($to) {
  449.                 return null; // not implemented
  450.         }
  451.  
  452.         public function equals($obj) {
  453.                 if (!is_object($obj)) $obj = OIDplusObject::parse($obj);
  454.                 if (!($obj instanceof $this)) return false;
  455.  
  456.                 $distance = $this->distance($obj);
  457.                 if (is_numeric($distance)) return $distance === 0; // if the distance function is implemented, use it
  458.  
  459.                 return $this->nodeId() == $obj->nodeId(); // otherwise compare the node id case-sensitive
  460.         }
  461.  
  462.         public static function findFitting($id) {
  463.                 $obj = OIDplusObject::parse($id);
  464.                 if (!$obj) throw new OIDplusException(_L('findFitting: Parse failed'));
  465.  
  466.                 if (!OIDplus::baseConfig()->getValue('OBJECT_CACHING', true)) {
  467.                         $res = OIDplus::db()->query("select id from ###objects where id like ?", array($obj->ns().':%'));
  468.                         while ($row = $res->fetch_object()) {
  469.                                 $test = OIDplusObject::parse($row->id);
  470.                                 if ($obj->equals($test)) return $test;
  471.                         }
  472.                         return false;
  473.                 } else {
  474.                         self::buildObjectInformationCache();
  475.                         foreach (self::$object_info_cache as $id => list($confidential, $parent, $ra_email, $title)) {
  476.                                 if (strpos($id, $obj->ns().':') === 0) {
  477.                                         $test = OIDplusObject::parse($id);
  478.                                         if ($obj->equals($test)) return $test;
  479.                                 }
  480.                         }
  481.                         return false;
  482.                 }
  483.         }
  484.  
  485.         public function one_up() {
  486.                 return null; // not implemented
  487.         }
  488.  
  489.         // Caching stuff
  490.  
  491.         protected static $object_info_cache = null;
  492.  
  493.         public static function resetObjectInformationCache() {
  494.                 self::$object_info_cache = null;
  495.         }
  496.  
  497.         const CACHE_CONFIDENTIAL = 0; // TODO: An object would be better so you can use $cacheitem->isConfidential() etc.
  498.         const CACHE_PARENT = 1;
  499.         const CACHE_RA_EMAIL = 2;
  500.         const CACHE_TITLE = 3;
  501.  
  502.         private static function buildObjectInformationCache() {
  503.                 if (is_null(self::$object_info_cache)) {
  504.                         self::$object_info_cache = array();
  505.                         $res = OIDplus::db()->query("select id, parent, confidential, ra_email, title from ###objects");
  506.                         while ($row = $res->fetch_array()) {
  507.                                 self::$object_info_cache[$row['id']] = array($row['confidential'], $row['parent'], $row['ra_email'], $row['title']);
  508.                         }
  509.                 }
  510.         }
  511.  
  512.         // override this function if you want your object type to save
  513.         // attachments in directories with easy names.
  514.         // Take care that your custom directory name will not allow jailbreaks (../) !
  515.         public function getDirectoryName() {
  516.                 if ($this->isRoot()) return $this->ns();
  517.                 return $this->getLegacyDirectoryName();
  518.         }
  519.  
  520.         public final function getLegacyDirectoryName() {
  521.                 if ($this::ns() == 'oid') {
  522.                         $oid = $this->nodeId(false);
  523.                 } else {
  524.                         $oid = null;
  525.                         $alt_ids = $this->getAltIds();
  526.                         foreach ($alt_ids as $alt_id) {
  527.                                 if ($alt_id->getNamespace() == 'oid') {
  528.                                         $oid = $alt_id->getId();
  529.                                         break; // we prefer the first OID (for GUIDs, the first OID is the OIDplus-OID, and the second OID is the UUID OID)
  530.                                 }
  531.                         }
  532.                 }
  533.  
  534.                 if (!is_null($oid) && ($oid != '')) {
  535.                         // For OIDs, it is the OID, for other identifiers
  536.                         // it it the OID alt ID (generated using the SystemID)
  537.                         return str_replace('.', '_', $oid);
  538.                 } else {
  539.                         // Can happen if you don't have a system ID (due to missing OpenSSL plugin)
  540.                         return md5($this->nodeId(true)); // we don't use $id, because $this->nodeId(true) is possibly more canonical than $id
  541.                 }
  542.         }
  543.  
  544.         public static function treeIconFilename($mode) {
  545.                 // for backwards-compatibility with older plugins
  546.                 return 'img/treeicon_'.$mode.'.png';
  547.         }
  548.  
  549. }
  550.