Completed
Push — master ( 7862a4...44d3af )
by Andreas
07:38
created

midcom_helper_reflector_tree::resolve_path()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 3
nop 2
dl 0
loc 15
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package midcom.helper.reflector
4
 * @author The Midgard Project, http://www.midgard-project.org
5
 * @copyright The Midgard Project, http://www.midgard-project.org
6
 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
7
 */
8
9
/**
10
 * The Grand Unified Reflector, Tree information
11
 *
12
 * @package midcom.helper.reflector
13
 */
14
class midcom_helper_reflector_tree extends midcom_helper_reflector
1 ignored issue
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
15
{
16
    /**
17
     * Creates a QB instance for get_root_objects
18
     */
19
    public function _root_objects_qb($deleted)
20
    {
21
        $schema_type = $this->mgdschema_class;
22
        $root_classes = self::get_root_classes();
23
        if (!in_array($schema_type, $root_classes))
24
        {
25
            debug_add("Type {$schema_type} is not a \"root\" type", MIDCOM_LOG_ERROR);
26
            return false;
27
        }
28
29
        $qb = $this->_get_type_qb($schema_type, $deleted);
30
        if (!$qb)
31
        {
32
            debug_add("Could not get QB for type '{$schema_type}'", MIDCOM_LOG_ERROR);
33
            return false;
34
        }
35
36
        // Figure out constraint to use to get root level objects
37
        $upfield = midgard_object_class::get_property_up($schema_type);
38
        if (!empty($upfield))
39
        {
40
            $uptype = $this->_mgd_reflector->get_midgard_type($upfield);
41
            switch ($uptype)
42
            {
43
                case MGD_TYPE_STRING:
44
                case MGD_TYPE_GUID:
45
                    $qb->add_constraint($upfield, '=', '');
46
                    break;
47
                case MGD_TYPE_INT:
48
                case MGD_TYPE_UINT:
49
                    $qb->add_constraint($upfield, '=', 0);
50
                    break;
51
                default:
52
                    debug_add("Do not know how to handle upfield '{$upfield}' has type {$uptype}", MIDCOM_LOG_ERROR);
53
                    return false;
54
            }
55
        }
56
        return $qb;
57
    }
58
59
    /**
60
     * Get "root" objects for the class this reflector was instantiated for
61
     *
62
     * NOTE: deleted objects can only be listed as admin, also: they do not come
63
     * MidCOM DBA wrapped (since you cannot normally instantiate such object)
64
     *
65
     * @param boolean $deleted whether to get (only) deleted or not-deleted objects
66
     * @return array of objects or false on failure
67
     */
68
    public function get_root_objects($deleted = false)
69
    {
70
        if (!self::_check_permissions($deleted))
71
        {
72
            return false;
73
        }
74
75
        $qb = $this->_root_objects_qb($deleted);
76
        if (!$qb)
77
        {
78
            debug_add('Could not get QB instance', MIDCOM_LOG_ERROR);
79
            return false;
80
        }
81
        self::add_schema_sorts_to_qb($qb, $this->mgdschema_class);
82
83
        return $qb->execute();
84
    }
85
86
    /**
87
     * Get rendered path for object
88
     *
89
     * @param midgard_object $object The object to get path for
90
     * @param string $separator the string used to separate path components
91
     * @return string resolved path
92
     */
93
    public static function resolve_path($object, $separator = ' &gt; ')
94
    {
95
        $parts = self::resolve_path_parts($object);
96
        $d = count($parts);
97
        $ret = '';
98
        foreach ($parts as $part)
99
        {
100
            $ret .= $part['label'];
101
            if (--$d)
102
            {
103
                $ret .= $separator;
104
            }
105
        }
106
        return $ret;
107
    }
108
109
    /**
110
     * Get path components for object
111
     *
112
     * @param midgard_object $object The object to get path for
113
     * @return array path components
114
     */
115
    public static function resolve_path_parts($object)
116
    {
117
        static $cache = array();
118
        if (isset($cache[$object->guid]))
119
        {
120
            return $cache[$object->guid];
121
        }
122
123
        $ret = array();
124
        $part = array
125
        (
126
            'object' => $object,
127
            'label' => parent::get($object)->get_object_label($object),
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (get() instead of resolve_path_parts()). Are you sure this is correct? If so, you might want to change this to $this->get().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
128
        );
129
        $ret[] = $part;
130
131
        $parent = self::get_parent($object);
132
        while (is_object($parent))
133
        {
134
            $part = array
135
            (
136
                'object' => $parent,
137
                'label' => parent::get($parent)->get_object_label($parent),
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (get() instead of resolve_path_parts()). Are you sure this is correct? If so, you might want to change this to $this->get().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
138
            );
139
            $ret[] = $part;
140
            $parent = self::get_parent($parent);
141
        }
142
143
        $cache[$object->guid] = array_reverse($ret);
144
        return $cache[$object->guid];
145
    }
146
147
    /**
148
     * Get the parent object of given object
149
     *
150
     * Tries to utilize MidCOM DBA features first but can fallback on pure MgdSchema
151
     * as necessary
152
     *
153
     * NOTE: since this might fall back to pure MgdSchema never trust that MidCOM DBA features
154
     * are available, check for is_callable/method_exists first !
155
     *
156
     * @param midgard_object $object the object to get parent for
157
     */
158
    public static function get_parent($object)
159
    {
160
        if (method_exists($object, 'get_parent'))
161
        {
162
            /**
163
             * The object might have valid reasons for returning empty value here, but we can't know if it's
164
             * because it's valid or because the get_parent* methods have not been overridden in the actually
165
             * used class
166
             */
167
            return $object->get_parent();
168
        }
169
170
        return false;
171
    }
172
173
    private static function _check_permissions($deleted)
174
    {
175
        // PONDER: Check for some generic user privilege instead  ??
176
        if (   $deleted
177
            && !midcom_connection::is_admin()
178
            && !midcom::get()->auth->is_component_sudo())
179
        {
180
            debug_add('Non-admins are not allowed to list deleted objects', MIDCOM_LOG_ERROR);
181
            return false;
182
        }
183
        return true;
184
    }
185
186
    /**
187
     * Get children of given object
188
     *
189
     * @param midgard_object $object object to get children for
190
     * @param boolean $deleted whether to get (only) deleted or not-deleted objects
191
     * @return array multidimensional array (keyed by classname) of objects or false on failure
192
     */
193
    public static function get_child_objects($object, $deleted = false)
194
    {
195
        if (!self::_check_permissions($deleted))
196
        {
197
            return false;
198
        }
199
        $resolver = new self($object);
200
        $child_classes = $resolver->get_child_classes();
201
        if (!$child_classes)
0 ignored issues
show
Bug Best Practice introduced by
The expression $child_classes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
202
        {
203
            if ($child_classes === false)
204
            {
205
                debug_add('resolver returned false (critical failure) from get_child_classes()', MIDCOM_LOG_ERROR);
206
            }
207
            return false;
208
        }
209
210
        $child_objects = array();
211
        foreach ($child_classes as $schema_type)
212
        {
213
            $type_children = $resolver->_get_child_objects_type($schema_type, $object, $deleted);
214
            // PONDER: check for boolean false as result ??
215
            if (empty($type_children))
216
            {
217
                continue;
218
            }
219
            $child_objects[$schema_type] = $type_children;
220
        }
221
        return $child_objects;
222
    }
223
224
    private function _get_type_qb($schema_type, $deleted)
225
    {
226
        if (empty($schema_type))
227
        {
228
            debug_add('Passed schema_type argument is empty, this is fatal', MIDCOM_LOG_ERROR);
229
            return false;
230
        }
231
        if ($deleted)
232
        {
233
            $qb = new midgard_query_builder($schema_type);
234
            $qb->include_deleted();
235
            $qb->add_constraint('metadata.deleted', '<>', 0);
236
            return $qb;
237
        }
238
        // Figure correct MidCOM DBA class to use and get midcom QB
239
        $midcom_dba_classname = midcom::get()->dbclassloader->get_midcom_class_name_for_mgdschema_object($schema_type);
240
        if (empty($midcom_dba_classname))
241
        {
242
            debug_add("MidCOM DBA does not know how to handle {$schema_type}", MIDCOM_LOG_ERROR);
243
            return false;
244
        }
245
246
        if (!midcom::get()->dbclassloader->load_component_for_class($midcom_dba_classname))
247
        {
248
            debug_add("Failed to load the handling component for {$midcom_dba_classname}, cannot continue.", MIDCOM_LOG_ERROR);
249
            return false;
250
        }
251
252
        return call_user_func(array($midcom_dba_classname, 'new_query_builder'));
253
    }
254
255
    /**
256
     * Figure out constraint(s) to use to get child objects
257
     */
258
    private function _get_link_fields($schema_type, $for_object)
259
    {
260
        static $cache = array();
261
        $cache_key = $schema_type . '-' . get_class($for_object);
262
        if (empty($cache[$cache_key]))
263
        {
264
            $ref = new midgard_reflection_property($schema_type);
265
266
            $linkfields = array();
267
            $linkfields['up'] = midgard_object_class::get_property_up($schema_type);
268
            $linkfields['parent'] = midgard_object_class::get_property_parent($schema_type);
269
            $object_baseclass = midcom_helper_reflector::resolve_baseclass(get_class($for_object));
270
271
            $linkfields = array_filter($linkfields);
272
            $data = array();
273
            foreach ($linkfields as $link_type => $field)
274
            {
275
                $info = array
276
                (
277
                    'name' => $field,
278
                    'type' => $ref->get_midgard_type($field),
279
                    'target' => $ref->get_link_target($field)
280
                );
281
                $linked_class = $ref->get_link_name($field);
282
                if (   empty($linked_class)
283
                    && $info['type'] === MGD_TYPE_GUID)
284
                {
285
                    // Guid link without class specification, valid for all classes
286
                    if (empty($info['target']))
287
                    {
288
                        $info['target'] = 'guid';
289
                    }
290
                }
291
                else if ($linked_class != $object_baseclass)
292
                {
293
                    // This link points elsewhere
294
                    continue;
295
                }
296
                $data[$link_type] = $info;
297
            }
298
            $cache[$cache_key] = $data;
299
        }
300
        return $cache[$cache_key];
301
    }
302
303
    /**
304
     * Creates a QB instance for _get_child_objects_type
305
     */
306
    public function _child_objects_type_qb($schema_type, $for_object, $deleted)
307
    {
308
        if (!is_object($for_object))
309
        {
310
            debug_add('Passed for_object argument is not object, this is fatal', MIDCOM_LOG_ERROR);
311
            return false;
312
        }
313
        $qb = $this->_get_type_qb($schema_type, $deleted);
314
        if (!$qb)
315
        {
316
            debug_add("Could not get QB for type '{$schema_type}'", MIDCOM_LOG_ERROR);
317
            return false;
318
        }
319
320
        $linkfields = $this->_get_link_fields($schema_type, $for_object);
321
322
        if (count($linkfields) === 0)
323
        {
324
            debug_add("Class '{$schema_type}' has no valid link properties pointing to class '" . get_class($for_object) . "', this should not happen here", MIDCOM_LOG_ERROR);
325
            return false;
326
        }
327
328
        $multiple_links = false;
329
        if (count($linkfields) > 1)
330
        {
331
            $multiple_links = true;
332
            $qb->begin_group('OR');
333
        }
334
335
        foreach ($linkfields as $link_type => $field_data)
336
        {
337
            $field_target = $field_data['target'];
338
            $field_type = $field_data['type'];
339
            $field = $field_data['name'];
340
341
            if (   !$field_target
342
                || !isset($for_object->$field_target))
343
            {
344
                // Why return false ???
345
                return false;
346
            }
347
            switch ($field_type)
348
            {
349
                case MGD_TYPE_STRING:
350
                case MGD_TYPE_GUID:
351
                    $qb->add_constraint($field, '=', (string) $for_object->$field_target);
352
                    break;
353
                case MGD_TYPE_INT:
354
                case MGD_TYPE_UINT:
355
                    if ($link_type == 'up')
356
                    {
357
                        $qb->add_constraint($field, '=', (int) $for_object->$field_target);
358
                    }
359
                    else if ($link_type == 'parent')
360
                    {
361
                        $up_property = midgard_object_class::get_property_up($schema_type);
362
                        if (!empty($up_property))
363
                        {
364
                            //we only return direct children (otherwise they would turn up twice in recursive queries)
365
                            $qb->begin_group('AND');
366
                            $qb->add_constraint($field, '=', (int) $for_object->$field_target);
367
                            $qb->add_constraint($up_property, '=', 0);
368
                            $qb->end_group();
369
                        }
370
                        else
371
                        {
372
                            $qb->add_constraint($field, '=', (int) $for_object->$field_target);
373
                        }
374
                    }
375
                    else
376
                    {
377
                        $qb->begin_group('AND');
378
                            $qb->add_constraint($field, '=', (int) $for_object->$field_target);
379
                            // make sure we don't accidentally find other objects with the same id
380
                            $qb->add_constraint($field . '.guid', '=', (string) $for_object->guid);
381
                        $qb->end_group();
382
                    }
383
                    break;
384
                default:
385
                    debug_add("Do not know how to handle linked field '{$field}', has type {$field_type}", MIDCOM_LOG_INFO);
386
387
                    // Why return false ???
388
                    return false;
389
            }
390
        }
391
392
        if ($multiple_links)
393
        {
394
            $qb->end_group();
395
        }
396
397
        return $qb;
398
    }
399
400
    /**
401
     * Used by get_child_objects
402
     *
403
     * @return array of objects
404
     */
405
    public function _get_child_objects_type($schema_type, $for_object, $deleted)
406
    {
407
        $qb = $this->_child_objects_type_qb($schema_type, $for_object, $deleted);
408
        if (!$qb)
409
        {
410
            debug_add('Could not get QB instance', MIDCOM_LOG_ERROR);
411
            return false;
412
        }
413
414
        // Sort by title and name if available
415
        self::add_schema_sorts_to_qb($qb, $schema_type);
416
417
        return $qb->execute();
418
    }
419
420
    /**
421
     * Get the parent class of the class this reflector was instantiated for
422
     *
423
     * @return string class name (or false if the type has no parent)
424
     */
425
    public function get_parent_class()
426
    {
427
        $parent_property = midgard_object_class::get_property_parent($this->mgdschema_class);
428
        if (!$parent_property)
429
        {
430
            return false;
431
        }
432
        $ref = new midgard_reflection_property($this->mgdschema_class);
433
        return $ref->get_link_name($parent_property);
434
    }
435
436
    /**
437
     * Get the child classes of the class this reflector was instantiated for
438
     *
439
     * @return array of class names
440
     */
441
    public function get_child_classes()
442
    {
443
        static $child_classes_all = array();
444
        if (!isset($child_classes_all[$this->mgdschema_class]))
445
        {
446
            $child_classes_all[$this->mgdschema_class] = $this->_resolve_child_classes();
447
        }
448
        return $child_classes_all[$this->mgdschema_class];
449
    }
450
451
    /**
452
     * Resolve the child classes of the class this reflector was instantiated for, used by get_child_classes()
453
     *
454
     * @return array of class names
455
     */
456
    private function _resolve_child_classes()
457
    {
458
        $child_class_exceptions_neverchild = $this->_config->get('child_class_exceptions_neverchild');
459
460
        // Safety against misconfiguration
461
        if (!is_array($child_class_exceptions_neverchild))
462
        {
463
            debug_add("config->get('child_class_exceptions_neverchild') did not return array, invalid configuration ??", MIDCOM_LOG_ERROR);
464
            $child_class_exceptions_neverchild = array();
465
        }
466
        $child_classes = array();
467
        $types = array_diff(midcom_connection::get_schema_types(), $child_class_exceptions_neverchild);
468
        foreach ($types as $schema_type)
469
        {
470
            $parent_property = midgard_object_class::get_property_parent($schema_type);
471
            $up_property = midgard_object_class::get_property_up($schema_type);
472
473
            if (   !$this->_resolve_child_classes_links_back($parent_property, $schema_type, $this->mgdschema_class)
474
                && !$this->_resolve_child_classes_links_back($up_property, $schema_type, $this->mgdschema_class))
475
            {
476
                continue;
477
            }
478
            $child_classes[] = $schema_type;
479
        }
480
481
        // TODO: handle exceptions
482
483
        //make sure children of the same type come out on top
484
        if ($key = array_search($this->mgdschema_class, $child_classes))
485
        {
486
            unset($child_classes[$key]);
487
            array_unshift($child_classes, $this->mgdschema_class);
488
        }
489
        return $child_classes;
490
    }
491
492
    private function _resolve_child_classes_links_back($property, $prospect_type, $schema_type)
493
    {
494
        if (empty($property))
495
        {
496
            return false;
497
        }
498
499
        $ref = new midgard_reflection_property($prospect_type);
500
        $link_class = $ref->get_link_name($property);
501
        if (   empty($link_class)
502
            && $ref->get_midgard_type($property) === MGD_TYPE_GUID)
503
        {
504
            return true;
505
        }
506
        return (midcom_helper_reflector::is_same_class($link_class, $schema_type));
507
    }
508
509
    /**
510
     * Get an array of "root level" classes, can (and should) be called statically
511
     *
512
     * @return array of classnames (or false on critical failure)
513
     */
514
    public static function get_root_classes()
515
    {
516
        static $root_classes = false;
517
        if (empty($root_classes))
518
        {
519
            $root_classes = self::_resolve_root_classes();
520
        }
521
        return $root_classes;
522
    }
523
524
    /**
525
     * Resolves the "root level" classes, used by get_root_classes()
526
     *
527
     * @return array of classnames (or false on critical failure)
528
     */
529
    private static function _resolve_root_classes()
530
    {
531
        $root_exceptions_notroot = midcom_baseclasses_components_configuration::get('midcom.helper.reflector', 'config')->get('root_class_exceptions_notroot');
532
        // Safety against misconfiguration
533
        if (!is_array($root_exceptions_notroot))
534
        {
535
            debug_add("config->get('root_class_exceptions_notroot') did not return array, invalid configuration ??", MIDCOM_LOG_ERROR);
536
            $root_exceptions_notroot = array();
537
        }
538
        $root_classes = array();
539
        $types = array_diff(midcom_connection::get_schema_types(), $root_exceptions_notroot);
540
        foreach ($types as $schema_type)
541
        {
542
            if (substr($schema_type, 0, 2) == '__')
543
            {
544
                continue;
545
            }
546
547
            // Class extensions mapping
548
            $schema_type = midcom_helper_reflector::class_rewrite($schema_type);
549
550
            // Make sure we only add classes once
551
            if (in_array($schema_type, $root_classes))
552
            {
553
                // Already listed
554
                continue;
555
            }
556
557
            $parent = midgard_object_class::get_property_parent($schema_type);
558
            if (!empty($parent))
559
            {
560
                // type has parent set, thus cannot be root type
561
                continue;
562
            }
563
564
            if (!midcom::get()->dbclassloader->get_midcom_class_name_for_mgdschema_object($schema_type))
565
            {
566
                // Not a MidCOM DBA object, skip
567
                continue;
568
            }
569
570
            $root_classes[] = $schema_type;
571
        }
572
573
        $root_exceptions_forceroot = midcom_baseclasses_components_configuration::get('midcom.helper.reflector', 'config')->get('root_class_exceptions_forceroot');
574
        // Safety against misconfiguration
575
        if (!is_array($root_exceptions_forceroot))
576
        {
577
            debug_add("config->get('root_class_exceptions_forceroot') did not return array, invalid configuration ??", MIDCOM_LOG_ERROR);
578
            $root_exceptions_forceroot = array();
579
        }
580
        $root_exceptions_forceroot = array_diff($root_exceptions_forceroot, $root_classes);
581
        foreach ($root_exceptions_forceroot as $schema_type)
582
        {
583
            if (!class_exists($schema_type))
584
            {
585
                // Not a valid class
586
                debug_add("Type {$schema_type} has been listed to always be root class, but the class does not exist", MIDCOM_LOG_WARN);
587
                continue;
588
            }
589
            $root_classes[] = $schema_type;
590
        }
591
592
        usort($root_classes, 'strnatcmp');
593
        return $root_classes;
594
    }
595
596
    /**
597
     * Add default ("title" and "name") sorts to a QB instance
598
     *
599
     * @param midgard_query_builder $qb QB instance
600
     * @param string $schema_type valid mgdschema class name
601
     */
602
    public static function add_schema_sorts_to_qb($qb, $schema_type)
603
    {
604
        // Sort by "title" and "name" if available
605
        $ref = self::get($schema_type);
606
        $dummy = new $schema_type();
607
        $title_property = $ref->get_title_property($dummy);
608
        if (   is_string($title_property)
609
            && midcom::get()->dbfactory->property_exists($schema_type, $title_property))
610
        {
611
            $qb->add_order($title_property);
612
        }
613
        $name_property = $ref->get_name_property($dummy);
614
        if (   is_string($name_property)
615
            && midcom::get()->dbfactory->property_exists($schema_type, $name_property))
616
        {
617
            $qb->add_order($name_property);
618
        }
619
    }
620
621
    /**
622
     * List object children
623
     *
624
     * @param midcom_core_dbaobject $parent
625
     * @return array
626
     */
627
    public static function get_tree(midcom_core_dbaobject $parent)
628
    {
629
        static $shown_guids = array();
630
        $tree = array();
631
        try
632
        {
633
            $children = self::get_child_objects($parent);
634
        }
635
        catch (midcom_error $e)
636
        {
637
            return $tree;
638
        }
639
640
        foreach ($children as $class => $objects)
0 ignored issues
show
Bug introduced by
The expression $children of type false|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
641
        {
642
            $reflector = parent::get($class);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (get() instead of get_tree()). Are you sure this is correct? If so, you might want to change this to $this->get().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
643
644
            foreach ($objects as $object)
645
            {
646
                if (array_key_exists($object->guid, $shown_guids))
647
                {
648
                    //we might see objects twice if they have both up and parent
649
                    continue;
650
                }
651
                $shown_guids[$object->guid] = true;
652
653
                $title = $reflector->get_object_label($object);
654
                $icon = $reflector->get_object_icon($object);
655
                $leaf = array
656
                (
657
                    'title' => $title,
658
                    'icon' => $icon,
659
                    'class' => $class
660
                );
661
                $grandchildren = self::get_tree($object);
662
                if (!empty($grandchildren))
663
                {
664
                    $leaf['children'] = $grandchildren;
665
                }
666
                $tree[] = $leaf;
667
            }
668
        }
669
        return $tree;
670
    }
671
}
672