Completed
Push — master ( 309357...7ad46f )
by Andreas
18:00
created

_child_objects_type_qb()   C

Complexity

Conditions 16
Paths 51

Size

Total Lines 76
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 36
CRAP Score 20.7802

Importance

Changes 0
Metric Value
cc 16
eloc 52
nc 51
nop 3
dl 0
loc 76
ccs 36
cts 49
cp 0.7347
crap 20.7802
rs 5.248
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
15
{
16
    /**
17
     * Creates a QB instance for get_root_objects
18
     */
19 8
    public function _root_objects_qb($deleted)
20
    {
21 8
        $schema_type = $this->mgdschema_class;
22 8
        $root_classes = self::get_root_classes();
23 8
        if (!in_array($schema_type, $root_classes)) {
24
            debug_add("Type {$schema_type} is not a \"root\" type", MIDCOM_LOG_ERROR);
25
            return false;
26
        }
27
28 8
        $qb = $this->_get_type_qb($schema_type, $deleted);
29 8
        if (!$qb) {
30
            debug_add("Could not get QB for type '{$schema_type}'", MIDCOM_LOG_ERROR);
31
            return false;
32
        }
33
34
        // Figure out constraint to use to get root level objects
35 8
        $upfield = midgard_object_class::get_property_up($schema_type);
36 8
        if (!empty($upfield)) {
37 8
            $uptype = $this->_mgd_reflector->get_midgard_type($upfield);
38
            switch ($uptype) {
39 8
                case MGD_TYPE_STRING:
40 8
                case MGD_TYPE_GUID:
41
                    $qb->add_constraint($upfield, '=', '');
42
                    break;
43 8
                case MGD_TYPE_INT:
44 8
                case MGD_TYPE_UINT:
45 8
                    $qb->add_constraint($upfield, '=', 0);
46 8
                    break;
47
                default:
48
                    debug_add("Do not know how to handle upfield '{$upfield}' has type {$uptype}", MIDCOM_LOG_ERROR);
49
                    return false;
50
            }
51
        }
52 8
        return $qb;
53
    }
54
55
    /**
56
     * Get "root" objects for the class this reflector was instantiated for
57
     *
58
     * NOTE: deleted objects can only be listed as admin, also: they do not come
59
     * MidCOM DBA wrapped (since you cannot normally instantiate such object)
60
     *
61
     * @param boolean $deleted whether to get (only) deleted or not-deleted objects
62
     * @return array of objects or false on failure
63
     */
64
    public function get_root_objects($deleted = false)
65
    {
66
        if (!self::_check_permissions($deleted)) {
67
            return false;
68
        }
69
70
        $qb = $this->_root_objects_qb($deleted);
71
        if (!$qb) {
72
            debug_add('Could not get QB instance', MIDCOM_LOG_ERROR);
73
            return false;
74
        }
75
        self::add_schema_sorts_to_qb($qb, $this->mgdschema_class);
76
77
        return $qb->execute();
78
    }
79
80
    /**
81
     * Get rendered path for object
82
     *
83
     * @param midgard_object $object The object to get path for
84
     * @param string $separator the string used to separate path components
85
     * @return string resolved path
86
     */
87 1
    public static function resolve_path($object, $separator = ' &gt; ')
88
    {
89 1
        $parts = self::resolve_path_parts($object);
90 1
        $d = count($parts);
91 1
        $ret = '';
92 1
        foreach ($parts as $part) {
93 1
            $ret .= $part['label'];
94 1
            if (--$d) {
95 1
                $ret .= $separator;
96
            }
97
        }
98 1
        return $ret;
99
    }
100
101
    /**
102
     * Get path components for object
103
     *
104
     * @param midgard_object $object The object to get path for
105
     * @return array path components
106
     */
107 3
    public static function resolve_path_parts($object)
108
    {
109 3
        static $cache = [];
110 3
        if (isset($cache[$object->guid])) {
111 1
            return $cache[$object->guid];
112
        }
113
114 2
        $ret = [];
115 2
        $ret[] = [
116 2
            'object' => $object,
117 2
            'label' => parent::get($object)->get_object_label($object),
118
        ];
119
120 2
        $parent = self::get_parent($object);
121 2
        while (is_object($parent)) {
122 1
            $ret[] = [
123 1
                'object' => $parent,
124 1
                'label' => parent::get($parent)->get_object_label($parent),
125
            ];
126 1
            $parent = self::get_parent($parent);
127
        }
128
129 2
        $cache[$object->guid] = array_reverse($ret);
130 2
        return $cache[$object->guid];
131
    }
132
133
    /**
134
     * Get the parent object of given object
135
     *
136
     * Tries to utilize MidCOM DBA features first but can fallback on pure MgdSchema
137
     * as necessary
138
     *
139
     * NOTE: since this might fall back to pure MgdSchema never trust that MidCOM DBA features
140
     * are available, check for is_callable/method_exists first !
141
     *
142
     * @param midgard_object $object the object to get parent for
143
     */
144 89
    public static function get_parent($object)
145
    {
146 89
        if (method_exists($object, 'get_parent')) {
147
            /**
148
             * The object might have valid reasons for returning empty value here, but we can't know if it's
149
             * because it's valid or because the get_parent* methods have not been overridden in the actually
150
             * used class
151
             */
152 89
            return $object->get_parent();
153
        }
154
155
        return false;
156
    }
157
158 20
    private static function _check_permissions($deleted)
159
    {
160
        // PONDER: Check for some generic user privilege instead  ??
161 20
        if (   $deleted
162 20
            && !midcom_connection::is_admin()
163 20
            && !midcom::get()->auth->is_component_sudo()) {
164
            debug_add('Non-admins are not allowed to list deleted objects', MIDCOM_LOG_ERROR);
165
            return false;
166
        }
167 20
        return true;
168
    }
169
170
    /**
171
     * Get children of given object
172
     *
173
     * @param midgard_object $object object to get children for
174
     * @param boolean $deleted whether to get (only) deleted or not-deleted objects
175
     * @return array multidimensional array (keyed by classname) of objects or false on failure
176
     */
177 20
    public static function get_child_objects($object, $deleted = false)
178
    {
179 20
        if (!self::_check_permissions($deleted)) {
180
            return false;
181
        }
182 20
        $resolver = new self($object);
183 20
        $child_classes = $resolver->get_child_classes();
184 20
        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...
185
            return false;
186
        }
187
188 20
        $child_objects = [];
189 20
        foreach ($child_classes as $schema_type) {
190 20
            $type_children = $resolver->_get_child_objects_type($schema_type, $object, $deleted);
191
            // PONDER: check for boolean false as result ??
192 20
            if (empty($type_children)) {
193 20
                continue;
194
            }
195 1
            $child_objects[$schema_type] = $type_children;
196
        }
197 20
        return $child_objects;
198
    }
199
200 95
    private function _get_type_qb($schema_type, $deleted)
201
    {
202 95
        if (empty($schema_type)) {
203
            debug_add('Passed schema_type argument is empty, this is fatal', MIDCOM_LOG_ERROR);
204
            return false;
205
        }
206 95
        if ($deleted) {
207
            $qb = new midgard_query_builder($schema_type);
208
            $qb->include_deleted();
209
            $qb->add_constraint('metadata.deleted', '<>', 0);
210
            return $qb;
211
        }
212
        // Figure correct MidCOM DBA class to use and get midcom QB
213 95
        $midcom_dba_classname = midcom::get()->dbclassloader->get_midcom_class_name_for_mgdschema_object($schema_type);
214 95
        if (empty($midcom_dba_classname)) {
215
            debug_add("MidCOM DBA does not know how to handle {$schema_type}", MIDCOM_LOG_ERROR);
216
            return false;
217
        }
218
219 95
        return call_user_func([$midcom_dba_classname, 'new_query_builder']);
220
    }
221
222
    /**
223
     * Figure out constraint(s) to use to get child objects
224
     */
225 94
    private function _get_link_fields($schema_type, $for_object)
226
    {
227 94
        static $cache = [];
228 94
        $cache_key = $schema_type . '-' . get_class($for_object);
229 94
        if (empty($cache[$cache_key])) {
230 9
            $ref = new midgard_reflection_property($schema_type);
231
232 9
            $linkfields = [];
233 9
            $linkfields['up'] = midgard_object_class::get_property_up($schema_type);
234 9
            $linkfields['parent'] = midgard_object_class::get_property_parent($schema_type);
235 9
            $object_baseclass = midcom_helper_reflector::resolve_baseclass(get_class($for_object));
236
237 9
            $linkfields = array_filter($linkfields);
238 9
            $data = [];
239 9
            foreach ($linkfields as $link_type => $field) {
240
                $info = [
241 9
                    'name' => $field,
242 9
                    'type' => $ref->get_midgard_type($field),
243 9
                    'target' => $ref->get_link_target($field)
244
                ];
245 9
                $linked_class = $ref->get_link_name($field);
246 9
                if (   empty($linked_class)
247 9
                    && $info['type'] === MGD_TYPE_GUID) {
248
                    // Guid link without class specification, valid for all classes
249 8
                    if (empty($info['target'])) {
250 8
                        $info['target'] = 'guid';
251
                    }
252 7
                } elseif ($linked_class != $object_baseclass) {
253
                    // This link points elsewhere
254 5
                    continue;
255
                }
256 8
                $data[$link_type] = $info;
257
            }
258 9
            $cache[$cache_key] = $data;
259
        }
260 94
        return $cache[$cache_key];
261
    }
262
263
    /**
264
     * Creates a QB instance for _get_child_objects_type
265
     */
266 94
    public function _child_objects_type_qb($schema_type, $for_object, $deleted)
267
    {
268 94
        if (!is_object($for_object)) {
269
            debug_add('Passed for_object argument is not object, this is fatal', MIDCOM_LOG_ERROR);
270
            return false;
271
        }
272 94
        $qb = $this->_get_type_qb($schema_type, $deleted);
273 94
        if (!$qb) {
274
            debug_add("Could not get QB for type '{$schema_type}'", MIDCOM_LOG_ERROR);
275
            return false;
276
        }
277
278 94
        $linkfields = $this->_get_link_fields($schema_type, $for_object);
279
280 94
        if (count($linkfields) === 0) {
281 2
            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);
282 2
            return false;
283
        }
284
285 94
        $multiple_links = false;
286 94
        if (count($linkfields) > 1) {
287
            $multiple_links = true;
288
            $qb->begin_group('OR');
289
        }
290
291 94
        foreach ($linkfields as $link_type => $field_data) {
292 94
            $field_target = $field_data['target'];
293 94
            $field_type = $field_data['type'];
294 94
            $field = $field_data['name'];
295
296 94
            if (   !$field_target
297 94
                || !isset($for_object->$field_target)) {
298
                // Why return false ???
299
                return false;
300
            }
301
            switch ($field_type) {
302 94
                case MGD_TYPE_STRING:
303 94
                case MGD_TYPE_GUID:
304 94
                    $qb->add_constraint($field, '=', (string) $for_object->$field_target);
305 94
                    break;
306 91
                case MGD_TYPE_INT:
307 91
                case MGD_TYPE_UINT:
308 91
                    if ($link_type == 'up') {
309 91
                        $qb->add_constraint($field, '=', (int) $for_object->$field_target);
310 85
                    } elseif ($link_type == 'parent') {
311 85
                        $up_property = midgard_object_class::get_property_up($schema_type);
312 85
                        if (!empty($up_property)) {
313
                            //we only return direct children (otherwise they would turn up twice in recursive queries)
314 83
                            $qb->begin_group('AND');
315 83
                            $qb->add_constraint($field, '=', (int) $for_object->$field_target);
316 83
                            $qb->add_constraint($up_property, '=', 0);
317 83
                            $qb->end_group();
318
                        } else {
319 85
                            $qb->add_constraint($field, '=', (int) $for_object->$field_target);
320
                        }
321
                    } else {
322
                        $qb->begin_group('AND');
323
                        $qb->add_constraint($field, '=', (int) $for_object->$field_target);
324
                        // make sure we don't accidentally find other objects with the same id
325
                        $qb->add_constraint($field . '.guid', '=', (string) $for_object->guid);
326
                        $qb->end_group();
327
                    }
328 91
                    break;
329
                default:
330
                    debug_add("Do not know how to handle linked field '{$field}', has type {$field_type}", MIDCOM_LOG_INFO);
331
332
                    // Why return false ???
333 94
                    return false;
334
            }
335
        }
336
337 94
        if ($multiple_links) {
338
            $qb->end_group();
339
        }
340
341 94
        return $qb;
342
    }
343
344
    /**
345
     * Used by get_child_objects
346
     *
347
     * @return array of objects
348
     */
349 20
    public function _get_child_objects_type($schema_type, $for_object, $deleted)
350
    {
351 20
        $qb = $this->_child_objects_type_qb($schema_type, $for_object, $deleted);
352 20
        if (!$qb) {
353 2
            debug_add('Could not get QB instance', MIDCOM_LOG_ERROR);
354 2
            return false;
355
        }
356
357
        // Sort by title and name if available
358 20
        self::add_schema_sorts_to_qb($qb, $schema_type);
359
360 20
        return $qb->execute();
361
    }
362
363
    /**
364
     * Get the parent class of the class this reflector was instantiated for
365
     *
366
     * @return string class name (or false if the type has no parent)
367
     */
368 2
    public function get_parent_class()
369
    {
370 2
        $parent_property = midgard_object_class::get_property_parent($this->mgdschema_class);
371 2
        if (!$parent_property) {
372 2
            return false;
373
        }
374
        $ref = new midgard_reflection_property($this->mgdschema_class);
375
        return $ref->get_link_name($parent_property);
376
    }
377
378
    /**
379
     * Get the child classes of the class this reflector was instantiated for
380
     *
381
     * @return array of class names
382
     */
383 95
    public function get_child_classes()
384
    {
385 95
        static $child_classes_all = [];
386 95
        if (!isset($child_classes_all[$this->mgdschema_class])) {
387 6
            $child_classes_all[$this->mgdschema_class] = $this->_resolve_child_classes();
388
        }
389 95
        return $child_classes_all[$this->mgdschema_class];
390
    }
391
392
    /**
393
     * Resolve the child classes of the class this reflector was instantiated for, used by get_child_classes()
394
     *
395
     * @return array of class names
396
     */
397 6
    private function _resolve_child_classes()
398
    {
399 6
        $child_class_exceptions_neverchild = $this->_config->get('child_class_exceptions_neverchild');
400
401
        // Safety against misconfiguration
402 6
        if (!is_array($child_class_exceptions_neverchild)) {
403
            debug_add("config->get('child_class_exceptions_neverchild') did not return array, invalid configuration ??", MIDCOM_LOG_ERROR);
404
            $child_class_exceptions_neverchild = [];
405
        }
406 6
        $child_classes = [];
407 6
        $types = array_diff(midcom_connection::get_schema_types(), $child_class_exceptions_neverchild);
408 6
        foreach ($types as $schema_type) {
409 6
            $parent_property = midgard_object_class::get_property_parent($schema_type);
410 6
            $up_property = midgard_object_class::get_property_up($schema_type);
411
412 6
            if (   !$this->_resolve_child_classes_links_back($parent_property, $schema_type, $this->mgdschema_class)
413 6
                && !$this->_resolve_child_classes_links_back($up_property, $schema_type, $this->mgdschema_class)) {
414 6
                continue;
415
            }
416 6
            $child_classes[] = $schema_type;
417
        }
418
419
        // TODO: handle exceptions
420
421
        //make sure children of the same type come out on top
422 6
        if ($key = array_search($this->mgdschema_class, $child_classes)) {
423 4
            unset($child_classes[$key]);
424 4
            array_unshift($child_classes, $this->mgdschema_class);
425
        }
426 6
        return $child_classes;
427
    }
428
429 6
    private function _resolve_child_classes_links_back($property, $prospect_type, $schema_type)
430
    {
431 6
        if (empty($property)) {
432 6
            return false;
433
        }
434
435 6
        $ref = new midgard_reflection_property($prospect_type);
436 6
        $link_class = $ref->get_link_name($property);
437 6
        if (   empty($link_class)
438 6
            && $ref->get_midgard_type($property) === MGD_TYPE_GUID) {
439 6
            return true;
440
        }
441 6
        return (midcom_helper_reflector::is_same_class($link_class, $schema_type));
442
    }
443
444
    /**
445
     * Get an array of "root level" classes, can (and should) be called statically
446
     *
447
     * @return array of classnames (or false on critical failure)
448
     */
449 15
    public static function get_root_classes()
450
    {
451 15
        static $root_classes = false;
452 15
        if (empty($root_classes)) {
453 1
            $root_classes = self::_resolve_root_classes();
454
        }
455 15
        return $root_classes;
456
    }
457
458
    /**
459
     * Resolves the "root level" classes, used by get_root_classes()
460
     *
461
     * @return array of classnames (or false on critical failure)
462
     */
463 1
    private static function _resolve_root_classes()
464
    {
465 1
        $root_exceptions_notroot = midcom_baseclasses_components_configuration::get('midcom.helper.reflector', 'config')->get('root_class_exceptions_notroot');
466
        // Safety against misconfiguration
467 1
        if (!is_array($root_exceptions_notroot)) {
468
            debug_add("config->get('root_class_exceptions_notroot') did not return array, invalid configuration ??", MIDCOM_LOG_ERROR);
469
            $root_exceptions_notroot = [];
470
        }
471 1
        $root_classes = [];
472 1
        $types = array_diff(midcom_connection::get_schema_types(), $root_exceptions_notroot);
473 1
        foreach ($types as $schema_type) {
474 1
            if (substr($schema_type, 0, 2) == '__') {
475
                continue;
476
            }
477
478
            // Class extensions mapping
479 1
            $schema_type = midcom_helper_reflector::class_rewrite($schema_type);
480
481
            // Make sure we only add classes once
482 1
            if (in_array($schema_type, $root_classes)) {
483
                // Already listed
484
                continue;
485
            }
486
487 1
            if (midgard_object_class::get_property_parent($schema_type)) {
488
                // type has parent set, thus cannot be root type
489 1
                continue;
490
            }
491
492 1
            if (!midcom::get()->dbclassloader->get_midcom_class_name_for_mgdschema_object($schema_type)) {
493
                // Not a MidCOM DBA object, skip
494
                continue;
495
            }
496
497 1
            $root_classes[] = $schema_type;
498
        }
499
500 1
        $root_exceptions_forceroot = midcom_baseclasses_components_configuration::get('midcom.helper.reflector', 'config')->get('root_class_exceptions_forceroot');
501
        // Safety against misconfiguration
502 1
        if (!is_array($root_exceptions_forceroot)) {
503
            debug_add("config->get('root_class_exceptions_forceroot') did not return array, invalid configuration ??", MIDCOM_LOG_ERROR);
504
            $root_exceptions_forceroot = [];
505
        }
506 1
        $root_exceptions_forceroot = array_diff($root_exceptions_forceroot, $root_classes);
507 1
        foreach ($root_exceptions_forceroot as $schema_type) {
508
            if (!class_exists($schema_type)) {
509
                // Not a valid class
510
                debug_add("Type {$schema_type} has been listed to always be root class, but the class does not exist", MIDCOM_LOG_WARN);
511
                continue;
512
            }
513
            $root_classes[] = $schema_type;
514
        }
515
516 1
        usort($root_classes, 'strnatcmp');
517 1
        return $root_classes;
518
    }
519
520
    /**
521
     * Add default ("title" and "name") sorts to a QB instance
522
     *
523
     * @param midgard_query_builder $qb QB instance
524
     * @param string $schema_type valid mgdschema class name
525
     */
526 20
    public static function add_schema_sorts_to_qb($qb, $schema_type)
527
    {
528
        // Sort by "title" and "name" if available
529 20
        $ref = self::get($schema_type);
530 20
        $dummy = new $schema_type();
531 20
        if ($title_property = $ref->get_title_property($dummy)) {
532 20
            $qb->add_order($title_property);
533
        }
534 20
        if ($name_property = $ref->get_name_property($dummy)) {
535 15
            $qb->add_order($name_property);
536
        }
537 20
    }
538
539
    /**
540
     * List object children
541
     *
542
     * @param midcom_core_dbaobject $parent
543
     * @return array
544
     */
545
    public static function get_tree(midcom_core_dbaobject $parent)
546
    {
547
        static $shown_guids = [];
548
        $tree = [];
549
        try {
550
            $children = self::get_child_objects($parent);
0 ignored issues
show
Bug introduced by
$parent of type midcom_core_dbaobject is incompatible with the type midgard_object expected by parameter $object of midcom_helper_reflector_tree::get_child_objects(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

550
            $children = self::get_child_objects(/** @scrutinizer ignore-type */ $parent);
Loading history...
551
        } catch (midcom_error $e) {
552
            return $tree;
553
        }
554
555
        foreach ($children as $class => $objects) {
556
            $reflector = parent::get($class);
557
558
            foreach ($objects as $object) {
559
                if (array_key_exists($object->guid, $shown_guids)) {
560
                    //we might see objects twice if they have both up and parent
561
                    continue;
562
                }
563
                $shown_guids[$object->guid] = true;
564
565
                $leaf = [
566
                    'title' => $reflector->get_object_label($object),
567
                    'icon' => $reflector->get_object_icon($object),
568
                    'class' => $class
569
                ];
570
                $grandchildren = self::get_tree($object);
571
                if (!empty($grandchildren)) {
572
                    $leaf['children'] = $grandchildren;
573
                }
574
                $tree[] = $leaf;
575
            }
576
        }
577
        return $tree;
578
    }
579
}
580