Passed
Push — master ( ac2cf9...6ee9d0 )
by Andreas
24:39
created

_draw_select_navigation()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 10
nc 3
nop 0
dl 0
loc 15
ccs 0
cts 10
cp 0
crap 12
rs 9.9332
c 1
b 0
f 0
1
<?php
2
/**
3
 * @package midgard.admin.asgard
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
 * Navigation class for Asgard
11
 *
12
 * @package midgard.admin.asgard
13
 */
14
class midgard_admin_asgard_navigation extends midcom_baseclasses_components_purecode
15
{
16
    /**
17
     * Root types
18
     *
19
     * @var array
20
     */
21
    public $root_types = [];
22
23
    /**
24
     * Some object
25
     *
26
     * @var midgard\portable\api\mgdobject
27
     */
28
    protected $_object;
29
30
    /**
31
     * Object path to the current object.
32
     *
33
     * @var Array
34
     */
35
    private $_object_path = [];
36
37
    private $_request_data = [];
38
    private $expanded_root_types = [];
39
    protected $shown_objects = [];
40
41 2
    public function __construct(?object $object, array &$request_data)
42
    {
43 2
        parent::__construct();
44
45 2
        $this->_object = $object;
46 2
        $this->_request_data =& $request_data;
47
48 2
        $this->root_types = midcom_helper_reflector_tree::get_root_classes();
49
50 2
        if (array_key_exists('current_type', $this->_request_data)) {
51
            $expanded_type = $this->_request_data['current_type'];
52
            if (!in_array($expanded_type, $this->root_types)) {
53
                $expanded_type = midcom_helper_reflector_tree::get($expanded_type)->get_parent_class();
54
            }
55
            $this->expanded_root_types[] = $expanded_type;
56 2
        } elseif (isset($this->_object)) {
57 2
            $this->_object_path = array_column(midcom_helper_reflector_tree::resolve_path_parts($this->_object), 'object');
58
59
            // we go through the path bottom up and show the first root type we find
60 2
            foreach (array_reverse($this->_object_path) as $node) {
61 2
                foreach ($this->root_types as $root_type) {
62 2
                    if (   is_a($node, $root_type)
63 2
                        || midcom_helper_reflector::is_same_class($root_type, $node->__midcom_class_name__)) {
64 2
                        $this->expanded_root_types[] = $root_type;
65 2
                        break;
66
                    }
67
                }
68
            }
69
        }
70 2
    }
71
72
    protected function _is_collapsed(string $type, int $total) : bool
73
    {
74
        return (   $total > $this->_config->get('max_navigation_entries')
75
                && empty($_GET['show_all_' . $type]));
76
    }
77
78 2
    protected function _list_child_elements($object, int $level = 0)
79
    {
80 2
        if ($level > 25) {
81
            debug_add('Recursion level 25 exceeded, aborting', MIDCOM_LOG_ERROR);
82
            return;
83
        }
84 2
        $ref = midcom_helper_reflector_tree::get($object);
85
86 2
        $child_types = [];
87 2
        foreach ($ref->get_child_classes() as $class) {
88 2
            $qb = $ref->_child_objects_type_qb($class, $object, false);
89
90 2
            if (   !$qb
91 2
                || !($count = $qb->count_unchecked())) {
92 2
                continue;
93
            }
94 1
            midcom_helper_reflector_tree::add_schema_sorts_to_qb($qb, $class);
95 1
            if ($this->_is_collapsed($class, $count)) {
96
                $qb->set_limit($this->_config->get('max_navigation_entries'));
97
            }
98 1
            $child_types[$class] = ['total' => $count, 'qb' => $qb];
99
        }
100
101 2
        if (!empty($child_types)) {
102 1
            echo "<ul>\n";
103 1
            foreach ($child_types as $type => $data) {
104 1
                $children = $data['qb']->execute();
105 1
                $label_mapping = [];
106 1
                foreach ($children as $i => $child) {
107 1
                    if (isset($this->shown_objects[$child->guid])) {
108
                        continue;
109
                    }
110
111 1
                    $ref = midcom_helper_reflector_tree::get($child);
112 1
                    $label_mapping[$i] = htmlspecialchars($ref->get_object_label($child));
0 ignored issues
show
Bug introduced by
It seems like $ref->get_object_label($child) can also be of type null; however, parameter $string of htmlspecialchars() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

112
                    $label_mapping[$i] = htmlspecialchars(/** @scrutinizer ignore-type */ $ref->get_object_label($child));
Loading history...
113
                }
114
115 1
                asort($label_mapping);
116
117 1
                foreach ($label_mapping as $index => $label) {
118 1
                    $child = $children[$index];
119 1
                    $this->_draw_element($child, $label, $level);
120
                }
121 1
                if ($this->_is_collapsed($type, $data['total'])) {
122
                    $this->_draw_collapsed_element($level, $type, $data['total']);
123
                }
124
            }
125 1
            echo "</ul>\n";
126
        }
127 2
    }
128
129
    /**
130
     * Renders the given root objects to HTML and calls _list_child_elements()
131
     */
132
    private function _list_root_elements(midcom_helper_reflector_tree $ref)
133
    {
134
        $qb = $ref->_root_objects_qb();
135
136
        if (   !$qb
137
            || !($total = $qb->count_unchecked())) {
138
            return;
139
        }
140
        midcom_helper_reflector_tree::add_schema_sorts_to_qb($qb, $ref->mgdschema_class);
0 ignored issues
show
Bug introduced by
It seems like $ref->mgdschema_class can also be of type null; however, parameter $schema_type of midcom_helper_reflector_...dd_schema_sorts_to_qb() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

140
        midcom_helper_reflector_tree::add_schema_sorts_to_qb($qb, /** @scrutinizer ignore-type */ $ref->mgdschema_class);
Loading history...
141
        if ($this->_is_collapsed($ref->mgdschema_class, $total)) {
0 ignored issues
show
Bug introduced by
It seems like $ref->mgdschema_class can also be of type null; however, parameter $type of midgard_admin_asgard_navigation::_is_collapsed() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

141
        if ($this->_is_collapsed(/** @scrutinizer ignore-type */ $ref->mgdschema_class, $total)) {
Loading history...
142
            $qb->set_limit($this->_config->get('max_navigation_entries'));
143
        }
144
145
        echo "<ul class=\"midgard_admin_asgard_navigation\">\n";
146
147
        $root_objects = $qb->execute();
148
149
        $label_mapping = [];
150
        foreach ($root_objects as $i => $object) {
151
            $label_mapping[$i] = htmlspecialchars($ref->get_object_label($object));
0 ignored issues
show
Bug introduced by
It seems like $ref->get_object_label($object) can also be of type null; however, parameter $string of htmlspecialchars() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

151
            $label_mapping[$i] = htmlspecialchars(/** @scrutinizer ignore-type */ $ref->get_object_label($object));
Loading history...
152
        }
153
154
        asort($label_mapping);
155
        $autoexpand = (count($root_objects) == 1);
156
        foreach ($label_mapping as $index => $label) {
157
            $object = $root_objects[$index];
158
            $this->_draw_element($object, $label, 1, $autoexpand);
159
        }
160
        if ($this->_is_collapsed($ref->mgdschema_class, $total)) {
161
            $this->_draw_collapsed_element(0, $ref->mgdschema_class, $total);
0 ignored issues
show
Bug introduced by
It seems like $ref->mgdschema_class can also be of type null; however, parameter $type of midgard_admin_asgard_nav...raw_collapsed_element() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

161
            $this->_draw_collapsed_element(0, /** @scrutinizer ignore-type */ $ref->mgdschema_class, $total);
Loading history...
162
        }
163
164
        echo "</ul>\n";
165
    }
166
167
    private function _draw_collapsed_element(int $level, string $type, int $total)
168
    {
169
        $ref = midcom_helper_reflector::get($type);
170
        if (!empty($this->_object_path[$level])) {
171
            if ($this->_object_path[$level]->__mgdschema_class_name__ == $type) {
172
                $object = $this->_object_path[$level];
173
            } elseif ($level == 0) {
174
                // this is the case where our object has parents, but we're in its type view directly
175
                foreach ($this->_object_path as $candidate) {
176
                    if ($candidate->__mgdschema_class_name__ == $type) {
177
                        $object = $candidate;
178
                        break;
179
                    }
180
                }
181
            }
182
            if (!empty($object)) {
183
                $label = htmlspecialchars($ref->get_object_label($object));
0 ignored issues
show
Bug introduced by
It seems like $ref->get_object_label($object) can also be of type null; however, parameter $string of htmlspecialchars() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

183
                $label = htmlspecialchars(/** @scrutinizer ignore-type */ $ref->get_object_label($object));
Loading history...
184
                $this->_draw_element($object, $label, $level);
185
            }
186
        }
187
        $icon = midcom_helper_reflector::get_object_icon(new $type);
188
        echo '<li><a class="expand-type-children" href="?show_all_' . $type . '=1">' . $icon . ' ' . sprintf($this->_l10n->get('show all %s %s entries'), $total, $ref->get_class_label()) . '</a></li>';
189
    }
190
191
    protected function _draw_element($object, string $label, int $level, bool $autoexpand = false)
192
    {
193
        $ref = midcom_helper_reflector_tree::get($object);
194
195
        $selected = $this->_is_selected($object);
196
        $css_class = $this->get_css_classes($object, $ref->mgdschema_class);
0 ignored issues
show
Bug introduced by
It seems like $ref->mgdschema_class can also be of type null; however, parameter $mgdschema_class of midgard_admin_asgard_navigation::get_css_classes() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

196
        $css_class = $this->get_css_classes($object, /** @scrutinizer ignore-type */ $ref->mgdschema_class);
Loading history...
197
198
        $mode = $this->_request_data['default_mode'];
199
        if (str_contains($css_class, 'readonly')) {
200
            $mode = 'view';
201
        }
202
203
        $this->shown_objects[$object->guid] = true;
204
205
        echo "    <li class=\"{$css_class}\">";
206
207
        $icon = $ref->get_object_icon($object);
208
209
        if (trim($label) == '') {
210
            $label = $ref->get_class_label() . ' #' . $object->id;
211
        }
212
213
        echo "<a href=\"" . midcom_connection::get_url('self') . "__mfa/asgard/object/{$mode}/{$object->guid}/\" title=\"GUID: {$object->guid}, ID: {$object->id}\">{$icon}{$label}</a>\n";
214
        if (   $selected
215
            || $autoexpand) {
216
            $this->_list_child_elements($object, $level + 1);
217
        }
218
        echo "    </li>\n";
219
    }
220
221
    private function _draw_plugins()
222
    {
223
        $customdata = midcom::get()->componentloader->get_all_manifest_customdata('asgard_plugin');
224
        foreach ($customdata as $component => $plugin_config) {
225
            $this->_request_data['section_url'] = midcom_connection::get_url('self') . "__mfa/asgard_{$component}/";
226
            $this->_request_data['section_name'] = $this->_i18n->get_string($component, $component);
227
            $class = $plugin_config['class'];
228
229
            if (!midcom::get()->auth->can_user_do("{$component}:access", null, $class)) {
230
                // Disabled plugin
231
                continue;
232
            }
233
234
            if (   method_exists($class, 'navigation')
235
                && ($this->_request_data['plugin_name'] == "asgard_{$component}")) {
236
                $this->_request_data['expanded'] = true;
237
                midcom_show_style('midgard_admin_asgard_navigation_section_header');
238
                $class::navigation();
239
            } else {
240
                $this->_request_data['expanded'] = false;
241
                midcom_show_style('midgard_admin_asgard_navigation_section_header');
242
            }
243
244
            midcom_show_style('midgard_admin_asgard_navigation_section_footer');
245
        }
246
    }
247
248 1
    private function _is_selected($object) : bool
249
    {
250 1
        foreach ($this->_object_path as $path_object) {
251 1
            if ($object->guid == $path_object->guid) {
252
                return true;
253
            }
254
        }
255 1
        return false;
256
    }
257
258 1
    protected function get_css_classes($object, string $mgdschema_class) : string
259
    {
260 1
        $css_class = get_class($object) . " {$mgdschema_class}";
261
262
        // Populate common properties
263 1
        $css_class = midcom::get()->metadata->get_object_classes($object, $css_class);
264
265 1
        if ($this->_is_selected($object)) {
266
            $css_class .= ' selected';
267
        }
268 1
        if (   is_object($this->_object)
269 1
            && (   $object->guid == $this->_object->guid
270 1
                || (   is_a($this->_object, midcom_db_parameter::class)
271 1
                    && $object->guid == $this->_object->parentguid))) {
272
            $css_class .= ' current';
273
        }
274 1
        if ( !$object->can_do('midgard:update')) {
275
            $css_class .= ' readonly';
276
        }
277 1
        return $css_class;
278
    }
279
280
    /**
281
     * Apply visibility restrictions from various sources
282
     *
283
     * @return array Alphabetically sorted list of class => title pairs
284
     */
285
    private function _process_root_types() : array
286
    {
287
        // Get the types that might have special display conditions
288
        // @TODO: Should this just include to the configuration selection, although it would break the consistency
289
        // of other similar preference sets, which simply override the global settings?
290
        $selected = midgard_admin_asgard_plugin::get_preference('midgard_types');
291
292
        // Get the inclusion/exclusion model
293
        $model = midgard_admin_asgard_plugin::get_preference('midgard_types_model');
294
        $exclude = ($model == 'exclude');
295
296
        $label_mapping = midgard_admin_asgard_plugin::get_root_classes();
297
298
        if (preg_match_all('/\|([a-z0-9\.\-_]+)/', $selected, $regs)) {
299
            $types = array_flip($regs[1]);
300
            if ($exclude) {
301
                $label_mapping = array_diff_key($label_mapping, $types);
302
            } else {
303
                $label_mapping = array_intersect_key($label_mapping, $types);
304
            }
305
        }
306
307
        // Get the possible regular expression
308
        $regexp = midgard_admin_asgard_plugin::get_preference('midgard_types_regexp');
309
310
        // "Convert" quickly to PERL regular expression
311
        if (!preg_match('/^[\/|]/', $regexp)) {
312
            $regexp = "/{$regexp}/";
313
        }
314
315
        // If the regular expression has been set, check which types should be shown
316
        if ($regexp !== '//') {
317
            $label_mapping = array_filter($label_mapping, function ($root_type) use ($regexp, $exclude) {
318
                return preg_match($regexp, $root_type) == $exclude;
319
            }, ARRAY_FILTER_USE_KEY);
320
        }
321
322
        return $label_mapping;
323
    }
324
325
    public function draw()
326
    {
327
        $this->_request_data['chapter_name'] = midcom::get()->config->get('midcom_site_title');
328
        midcom_show_style('midgard_admin_asgard_navigation_chapter');
329
330
        $this->_draw_plugins();
331
332
        if (!midcom::get()->auth->can_user_do('midgard.admin.asgard:manage_objects', null, 'midgard_admin_asgard_plugin')) {
333
            return;
334
        }
335
336
        $label_mapping = $this->_process_root_types();
337
338
        $expanded_types = array_intersect(array_keys($label_mapping), $this->expanded_root_types);
339
340
        /*
341
         * Use a dropdown for displaying the navigation if at least one type is expanded
342
         * and the user has the corresponding preference set. That way, you expanded types
343
         * can take up the maximum available space while all types are still accessible with one
344
         * click if nothing is expanded
345
         */
346
        $types_shown = false;
347
        if (    !empty($expanded_types)
348
             && midgard_admin_asgard_plugin::get_preference('navigation_type') === 'dropdown') {
349
            $this->_draw_select_navigation();
350
            $types_shown = true;
351
        }
352
353
        foreach ($expanded_types as $root_type) {
354
            $this->_request_data['section_url'] = midcom_connection::get_url('self') . "__mfa/asgard/{$root_type}";
355
            $this->_request_data['section_name'] = $label_mapping[$root_type];
356
            $this->_request_data['expanded'] = true;
357
            midcom_show_style('midgard_admin_asgard_navigation_section_header');
358
            $ref = midcom_helper_reflector_tree::get($root_type);
359
            $this->_list_root_elements($ref);
360
361
            midcom_show_style('midgard_admin_asgard_navigation_section_footer');
362
        }
363
364
        if (!$types_shown) {
365
            $this->_request_data['section_name'] = $this->_l10n->get('midgard objects');
366
            $this->_request_data['section_url'] = null;
367
            $this->_request_data['expanded'] = true;
368
            midcom_show_style('midgard_admin_asgard_navigation_section_header');
369
            $collapsed_types = array_diff_key($label_mapping, array_flip($expanded_types));
370
371
            $this->_draw_type_list($collapsed_types);
372
373
            midcom_show_style('midgard_admin_asgard_navigation_section_footer');
374
        }
375
    }
376
377
    private function _draw_type_list(array $types)
378
    {
379
        echo "<ul class=\"midgard_admin_asgard_navigation\">\n";
380
381
        foreach ($types as $type => $label) {
382
            $url = midcom_connection::get_url('self') . "__mfa/asgard/{$type}/";
383
            echo "    <li class=\"mgdschema-type\">";
384
385
            if ($dbaclass = midcom::get()->dbclassloader->get_midcom_class_name_for_mgdschema_object($type)) {
386
                $object = new $dbaclass;
387
            } else {
388
                $object = new $type;
389
            }
390
            $icon = midcom_helper_reflector::get_object_icon($object);
391
392
            echo "<a href=\"" . $url . "\" title=\"{$label}\">{$icon}{$label}</a>\n";
393
394
            echo "    </li>\n";
395
        }
396
397
        echo "</ul>\n";
398
    }
399
400
    private function _draw_select_navigation()
401
    {
402
        if (!empty($this->_object_path)) {
403
            $this->_request_data['root_object'] = $this->_object_path[0];
404
            $this->_request_data['navigation_type'] = $this->_object_path[0]->__mgdschema_class_name__;
405
        } elseif (isset($this->expanded_root_types[0])) {
406
            $this->_request_data['navigation_type'] = $this->expanded_root_types[0];
407
        } else {
408
            $this->_request_data['navigation_type'] = '';
409
        }
410
411
        $this->_request_data['label_mapping'] = midgard_admin_asgard_plugin::get_root_classes();
412
        $this->_request_data['expanded_root_types'] = $this->expanded_root_types;
413
414
        midcom_show_style('midgard_admin_asgard_navigation_sections');
415
    }
416
}
417