Subversion Repositories oidplus

Rev

Rev 1137 | Rev 1148 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

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