Passed
Push — master ( 0d04df...f4f19a )
by Andreas
10:58
created

midcom_helper_nav::get_breadcrumb_line()   B

Complexity

Conditions 9
Paths 21

Size

Total Lines 36
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 9.2363

Importance

Changes 0
Metric Value
cc 9
eloc 21
nc 21
nop 5
dl 0
loc 36
ccs 18
cts 21
cp 0.8571
crap 9.2363
rs 8.0555
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package midcom.helper
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
 * Main Navigation interface class.
11
 *
12
 * Basically, this class proxies all requests to a midcom_helper_nav_backend
13
 * class. See the interface definition of it for further details.
14
 *
15
 * Additionally this class implements a couple of helper functions to make
16
 * common NAP tasks easier.
17
 *
18
 * <b>Important note:</b> Whenever you add new code to this class, or extend it through
19
 * inheritance, never call the proxy-functions of the backend directly, this is strictly
20
 * forbidden.
21
 *
22
 * @todo End-User documentation of node and leaf data, as the one in the backend is incomplete too.
23
 * @package midcom.helper
24
 * @see midcom_helper_nav_backend
25
 */
26
class midcom_helper_nav
27
{
28
    private midcom_helper_nav_backend $_backend;
29
30
    /**
31
     * @var midcom_helper_nav_backend[]
32
     */
33
    private static array $_backends = [];
34
35
    private midcom_core_context $context;
36
37
    /**
38
     * Create a NAP instance for the currently active context
39
     */
40 453
    public function __construct()
41
    {
42 453
        $this->context = midcom_core_context::get();
43 453
        $this->_backend = $this->_get_backend();
44
    }
45
46
    /**
47
     * This function maintains one NAP Class per context. Usually this is enough,
48
     * since you mostly will access it in context 0, the default. The problem is, that
49
     * this is not 100% efficient: If you instantiate two different NAP Classes in
50
     * different contexts both referring to the same root node, you will get two
51
     * different instances.
52
     *
53
     * @see midcom_helper_nav
54
     */
55 453
    private function _get_backend() : midcom_helper_nav_backend
56
    {
57 453
        if (!isset(self::$_backends[$this->context->id])) {
58 286
            $root = $this->context->get_key(MIDCOM_CONTEXT_ROOTTOPIC);
59 286
            $urltopics = $this->context->get_key(MIDCOM_CONTEXT_URLTOPICS);
60 286
            self::$_backends[$this->context->id] = new midcom_helper_nav_backend($root, $urltopics);
0 ignored issues
show
Bug introduced by
It seems like $root can also be of type false; however, parameter $root of midcom_helper_nav_backend::__construct() does only seem to accept midcom_db_topic, 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

60
            self::$_backends[$this->context->id] = new midcom_helper_nav_backend(/** @scrutinizer ignore-type */ $root, $urltopics);
Loading history...
Bug introduced by
It seems like $urltopics can also be of type false; however, parameter $urltopics of midcom_helper_nav_backend::__construct() does only seem to accept array, 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

60
            self::$_backends[$this->context->id] = new midcom_helper_nav_backend($root, /** @scrutinizer ignore-type */ $urltopics);
Loading history...
61
        }
62
63 453
        return self::$_backends[$this->context->id];
64
    }
65
66
    /* The following methods are just interfaces to midcom_helper_nav_backend */
67
68
    /**
69
     * Retrieve the ID of the currently displayed node. Defined by the topic of
70
     * the component that declared able to handle the request.
71
     *
72
     * @see midcom_helper_nav_backend::get_current_node()
73
     */
74 240
    public function get_current_node() : int
75
    {
76 240
        return $this->_backend->get_current_node();
77
    }
78
79
    /**
80
     * Retrieve the ID of the currently displayed leaf. This is a leaf that is
81
     * displayed by the handling topic. If no leaf is active, this function
82
     * returns null. (Remember to make a type sensitive check, e.g.
83
     * nav::get_current_leaf() !== null to distinguish '0' and 'null'.)
84
     *
85
     * @see midcom_helper_nav_backend::get_current_leaf()
86
     */
87 257
    public function get_current_leaf() : ?string
88
    {
89 257
        return $this->_backend->get_current_leaf();
90
    }
91
92
    /**
93
     * Retrieve the ID of the root node. Note that this ID is dependent from the
94
     * ID of the MidCOM Root topic and therefore will change as easily as the
95
     * root topic ID might. The MIDCOM_NAV_URL entry of the root node's data will
96
     * always be empty.
97
     *
98
     * @see midcom_helper_nav_backend::get_root_node()
99
     */
100 97
    public function get_root_node() : int
101
    {
102 97
        return $this->_backend->get_root_node();
103
    }
104
105
    /**
106
     * Lists all Sub-nodes of $parent_node. If there are no subnodes you will get
107
     * an empty array
108
     *
109
     * @param boolean $show_noentry Show all objects on-site which have the noentry flag set.
110
     *     This defaults to false.
111
     * @see midcom_helper_nav_backend::list_nodes()
112
     */
113 12
    public function get_nodes(int $parent_node_id, bool $show_noentry = false) : array
114
    {
115 12
        return array_map([$this, 'get_node'], $this->_backend->list_nodes($parent_node_id, $show_noentry));
116
    }
117
118
    /**
119
     * Lists all leaves of $parent_node. If there are no leaves you will get an
120
     * empty array.
121
     *
122
     * @param boolean $show_noentry Show all objects on-site which have the noentry flag set.
123
     *     This defaults to false.
124
     * @see midcom_helper_nav_backend::list_leaves()
125
     */
126 17
    public function get_leaves(int $parent_node_id, bool $show_noentry = false) : array
127
    {
128 17
        return array_map([$this, 'get_leaf'], $this->_backend->list_leaves($parent_node_id, $show_noentry));
129
    }
130
131
    /**
132
     * This will give you a key-value pair describing the node with the ID
133
     * $node_id. The defined keys are described above in Node data interchange
134
     * format. You will get false if the node ID is invalid.
135
     *
136
     * @see midcom_helper_nav_backend::get_node()
137
     */
138 311
    public function get_node($node_id) : ?array
139
    {
140 311
        return $this->_backend->get_node($node_id);
141
    }
142
143
    /**
144
     * This will give you a key-value pair describing the leaf with the ID
145
     * $node_id. The defined keys are described above in leaf data interchange
146
     * format. You will get false if the leaf ID is invalid.
147
     *
148
     * @see midcom_helper_nav_backend::get_leaf()
149
     */
150 40
    public function get_leaf(string $leaf_id) : ?array
151
    {
152 40
        return $this->_backend->get_leaf($leaf_id);
153
    }
154
155
    /**
156
     * Returns the ID of the node to which $leaf_id is associated to, false
157
     * on failure.
158
     *
159
     * @param string $leaf_id    The Leaf-ID to search an uplink for.
160
     * @return int             The ID of the Node for which we have a match, or false on failure.
161
     * @see midcom_helper_nav_backend::get_leaf_uplink()
162
     */
163
    function get_leaf_uplink($leaf_id)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
164
    {
165
        return $this->_backend->get_leaf_uplink($leaf_id);
166
    }
167
168
    /**
169
     * Returns the ID of the node to which $node_id is associated to, false
170
     * on failure. The root node's uplink is -1.
171
     *
172
     * @param int $node_id    The Leaf-ID to search an uplink for.
173
     * @return int             The ID of the Node for which we have a match, -1 for the root node, or false on failure.
174
     * @see midcom_helper_nav_backend::get_node_uplink()
175
     */
176 73
    public function get_node_uplink($node_id)
177
    {
178 73
        return $this->_backend->get_node_uplink($node_id);
179
    }
180
181
    /**
182
     * Checks if the given node is within the tree of another node.
183
     *
184
     * @param int    $node_id    The node in question.
185
     * @param int    $root_id    The root node to use.
186
     */
187 78
    public function is_node_in_tree($node_id, $root_id) : bool
188
    {
189 78
        if ($node_id == $root_id) {
190 9
            return true;
191
        }
192 73
        $uplink = $this->get_node_uplink($node_id);
193
194 73
        if ($uplink == $root_id) {
195 18
            return true;
196
        }
197 55
        if (in_array($uplink, [false, -1])) {
198 55
            return false;
199
        }
200 6
        return $this->is_node_in_tree($uplink, $root_id);
201
    }
202
203
    /**
204
     * List all child elements, nodes and leaves alike, of the node with ID
205
     * $parent_node_id. For every child element, an array of ID and type (node/leaf)
206
     * is given as
207
     *
208
     * - MIDCOM_NAV_ID => 0,
209
     * - MIDCOM_NAV_TYPE => 'node'
210
     *
211
     * If there are no child elements at all the method will return an empty array,
212
     * in case of an error false.  NOTE: This method should be quite slow, there's
213
     * room for improvement... :-)
214
     */
215 9
    public function list_child_elements(int $parent_node_id) : ?array
216
    {
217 9
        if ($parent_node = $this->get_node($parent_node_id)) {
218 9
            $nav_object = midcom_helper_nav_itemlist::factory($this, $parent_node);
219 9
            return $nav_object->get_sorted_list();
220
        }
221
        return null;
222
    }
223
224
    /**
225
     * Try to resolve a guid into a NAP object.
226
     *
227
     * The code is optimized trying to avoid a full-scan if possible. To do this it
228
     * will treat topic and article guids specially: In both cases the system will
229
     * translate it using the topic id into a node id and scan only that part of the
230
     * tree non-recursively.
231
     *
232
     * A full scan of the NAP data is only done if another MidgardObject is used.
233
     *
234
     * Note: If you want to resolve a GUID you got from a Permalink, use the Permalinks
235
     * service within MidCOM, as it covers more objects than the NAP listings.
236
     *
237
     * @param boolean $node_is_sufficient if we could return a good guess of correct parent node but said node does not list the $guid in leaves return the node or try to do a full (and very expensive) NAP scan ?
238
     * @return ?array Either a node or leaf structure, distinguishable by MIDCOM_NAV_TYPE, or null on failure.
239
     * @see midcom_services_permalinks
240
     */
241 28
    public function resolve_guid(string $guid, bool $node_is_sufficient = false) : ?array
242
    {
243
        // First, check if the GUID is already known by the backend:
244 28
        if ($cached_result = $this->_backend->get_loaded_object_by_guid($guid)) {
245 1
            debug_add('The GUID was already known by the backend instance, returning the cached copy directly.');
246 1
            return $cached_result;
247
        }
248
249
        // Fetch the object in question for a start, so that we know what to do (tm)
250
        // Note, that objects that cannot be resolved will still be processed using a full-scan of
251
        // the tree. This is, for example, used by the on-delete cache invalidation.
252
        try {
253 27
            $object = midcom::get()->dbfactory->get_object_by_guid($guid);
254
        } catch (midcom_error $e) {
255
            debug_add("Could not load GUID {$guid}, trying to continue anyway. Last error was: " . $e->getMessage(), MIDCOM_LOG_WARN);
256
        }
257 27
        if (!empty($object)) {
258 27
            if ($object instanceof midcom_db_topic) {
259
                // Ok. This topic should be within the content tree,
260
                // we check this and return the node if everything is ok.
261 20
                if (!$this->is_node_in_tree($object->id, $this->get_root_node())) {
262 18
                    debug_add("The GUID {$guid} leads to an unknown topic not in our tree.", MIDCOM_LOG_WARN);
263 18
                    return null;
264
                }
265
266 2
                return $this->get_node($object->id);
267
            }
268
269 8
            if ($object instanceof midcom_db_article) {
270
                // Ok, let's try to find the article using the topic in the tree.
271
                if (!$this->is_node_in_tree($object->topic, $this->get_root_node())) {
272
                    debug_add("The GUID {$guid} leads to an unknown topic not in our tree.", MIDCOM_LOG_WARN);
273
                    return null;
274
                }
275
                if ($leaf = $this->_find_leaf_in_topic($object->topic, $guid)) {
276
                    return $leaf;
277
                }
278
279
                debug_add("The Article GUID {$guid} is somehow hidden from the NAP data in its topic, no results shown.", MIDCOM_LOG_INFO);
280
                return null;
281
            }
282
283
            // Ok, unfortunately, this is not an immediate topic. We try to traverse
284
            // upwards in the object chain to find a topic.
285 8
            if ($topic = $this->find_closest_topic($object)) {
286
                debug_add("Found topic #{$topic->id}, searching the leaves");
287
                if ($leaf = $this->_find_leaf_in_topic($topic->id, $guid)) {
288
                    return $leaf;
289
                }
290
                if ($node_is_sufficient) {
291
                    debug_add("Could not find guid in leaves (maybe not listed?), but node is sufficient, returning node");
292
                    return $this->get_node($topic->id);
293
                }
294
            }
295
        }
296
297
        // this is the rest of the lot, we need to traverse everything, unfortunately.
298
        // First, we traverse a list of nodes to be checked on by one, avoiding a recursive
299
        // function call.
300 8
        $unprocessed_node_ids = [$this->get_root_node()];
301
302 8
        while (!empty($unprocessed_node_ids)) {
303 8
            $node_id = array_shift($unprocessed_node_ids);
304
305
            // Check leaves of this node first.
306 8
            if ($leaf = $this->_find_leaf_in_topic($node_id, $guid)) {
307
                return $leaf;
308
            }
309
310
            // Ok, append all subnodes to the queue.
311 8
            $unprocessed_node_ids = array_merge($unprocessed_node_ids, $this->_backend->list_nodes($node_id, false));
312
        }
313
314 8
        debug_add("We were unable to find the GUID {$guid} in the MidCOM tree even with a full scan.", MIDCOM_LOG_INFO);
315 8
        return null;
316
    }
317
318 8
    private function _find_leaf_in_topic(int $topic, string $guid) : ?array
319
    {
320 8
        foreach ($this->get_leaves($topic, true) as $leaf) {
321
            if ($leaf[MIDCOM_NAV_GUID] == $guid) {
322
                return $leaf;
323
            }
324
        }
325 8
        return null;
326
    }
327
328 275
    public function find_closest_topic(midcom_core_dbaobject $object) : ?midcom_db_topic
329
    {
330 275
        debug_add('Looking for a topic to use via get_parent()');
331 275
        while ($parent = $object->get_parent()) {
332
            // Verify that this topic is within the current sites tree, if it is not,
333
            // we ignore it.
334 139
            if (   $parent instanceof midcom_db_topic
335 139
                && $this->is_node_in_tree($parent->id, $this->get_root_node())) {
336 22
                return $parent;
337
            }
338 130
            $object = $parent;
339
        }
340 254
        return null;
341
    }
342
343
    /* The more complex interface methods starts here */
344
345
    /**
346
     * Construct a breadcrumb line.
347
     *
348
     * Gives you a line like 'Start > Topic1 > Topic2 > Article' using NAP to
349
     * traverse upwards till the root node. $separator is inserted between the
350
     * pairs, $class, if non-null, will be used as CSS-class for the A-Tags.
351
     *
352
     * The parameter skip_levels indicates how much nodes should be skipped at
353
     * the beginning of the current path. Default is to show the complete path. A
354
     * value of 1 will skip the home link, 2 will skip the home link and the first
355
     * subtopic and so on. If a leaf or node is selected, that normally would be
356
     * hidden, only its name will be shown.
357
     *
358
     * @param string    $separator        The separator to use between the elements.
359
     * @param string    $class            If not-null, it will be assigned to all A tags.
360
     * @param int       $skip_levels      The number of topic levels to skip before starting to work (use this to skip 'Home' links etc.).
361
     * @param string    $current_class    The class that should be assigned to the currently active element.
362
     */
363 10
    public function get_breadcrumb_line(string $separator = ' &gt; ', string $class = null, int $skip_levels = 0, string $current_class = null, array $skip_guids = []) : string
364
    {
365 10
        $breadcrumb_data = $this->get_breadcrumb_data();
366 10
        $result = '';
367
368
        // Detect real starting Node
369 10
        if ($skip_levels > 0) {
370 3
            if ($skip_levels >= count($breadcrumb_data)) {
371
                debug_add('We were asked to skip all breadcrumb elements that were present (or even more). Returning an empty breadcrumb line therefore.', MIDCOM_LOG_INFO);
372
                return $result;
373
            }
374 3
            $breadcrumb_data = array_slice($breadcrumb_data, $skip_levels);
375
        }
376
377 10
        $class = $class === null ? '' : ' class="' . $class . '"';
378 10
        while (current($breadcrumb_data) !== false) {
379 10
            $data = current($breadcrumb_data);
380 10
            $entry = htmlspecialchars($data[MIDCOM_NAV_NAME]);
381
382
            // Add the next element sensitive to the fact whether we are at the end or not.
383 10
            if (next($breadcrumb_data) === false) {
384 10
                if ($current_class !== null) {
385 10
                    $entry = "<span class=\"{$current_class}\">{$entry}</span>";
386
                }
387
            } else {
388 9
                if (   !empty($data['napobject'][MIDCOM_NAV_GUID])
389 9
                    && in_array($data['napobject'][MIDCOM_NAV_GUID], $skip_guids)) {
390
                    continue;
391
                }
392
393 9
                $entry = "<a href=\"{$data[MIDCOM_NAV_URL]}\"{$class}>{$entry}</a>{$separator}";
394
            }
395 10
            $result .= $entry;
396
        }
397
398 10
        return $result;
399
    }
400
401
    /**
402
     * Construct source data for a breadcrumb line.
403
     *
404
     * Gives you the data needed to construct a line like
405
     * 'Start > Topic1 > Topic2 > Article' using NAP to
406
     * traverse upwards till the root node. The components custom breadcrumb
407
     * data is inserted at the end of the computed breadcrumb line after any
408
     * set NAP leaf.
409
     *
410
     * See get_breadcrumb_line for a more end-user oriented way of life.
411
     *
412
     * <b>Return Value</b>
413
     *
414
     * The breadcrumb data will be returned as a list of associative arrays each
415
     * containing these keys:
416
     *
417
     * - MIDCOM_NAV_URL The fully qualified URL to the node.
418
     * - MIDCOM_NAV_NAME The clear-text name of the node.
419
     * - MIDCOM_NAV_TYPE One of 'node', 'leaf', 'custom' indicating what type of entry
420
     *   this is.
421
     * - MIDCOM_NAV_ID The Identifier of the structure used to build this entry, this is
422
     *   either a NAP node/leaf ID or the list key set by the component for custom data.
423
     * - 'napobject' This contains the original NAP object retrieved by the function.
424
     *   Just in case you need more information than is available directly.
425
     *
426
     * The entry of every level is indexed by its MIDCOM_NAV_ID, where custom keys preserve
427
     * their original key (as passed by the component) and prefixing it with 'custom-'. This
428
     * allows you to easily check if a given node/leave is within the current breadcrumb-line
429
     * by checking with array_key_exists.
430
     *
431
     * <b>Adding custom data</b>
432
     *
433
     * Custom elements are added to this array by using the MidCOM custom component context
434
     * at this time. You need to add a list with the same structure as above into the
435
     * custom component context key <i>midcom.helper.nav.breadcrumb</i>. (This needs
436
     * to be an array always, even if you return only one element.)
437
     *
438
     * Note, that the URL you pass in that list is always prepended with the current anchor
439
     * prefix. It is not possible to specify absolute URLs there. No leading slash is required.
440
     *
441
     * Example:
442
     *
443
     * <code>
444
     * $tmp = [
445
     *     [
446
     *         MIDCOM_NAV_URL => "list/{$this->_category}/{$this->_mode}/1/",
447
     *         MIDCOM_NAV_NAME => $this->_category_name,
448
     *     ],
449
     * ];
450
     * midcom_core_context::get()->set_custom_key('midcom.helper.nav.breadcrumb', $tmp);
451
     * </code>
452
     */
453 10
    public function get_breadcrumb_data($id = null) : array
454
    {
455 10
        $prefix = $this->context->get_key(MIDCOM_CONTEXT_ANCHORPREFIX);
456 10
        $result = [];
457
458 10
        if (!$id) {
459 10
            $curr_leaf = $this->get_current_leaf();
460 10
            $curr_node = $this->get_current_node();
461
        } else {
462
            $curr_leaf = false;
463
            $curr_node = -1;
464
465
            if ($leaf = $this->get_leaf($id)) {
466
                $curr_leaf = $leaf[MIDCOM_NAV_ID];
467
                $curr_node = $leaf[MIDCOM_NAV_NODEID];
468
            } elseif ($node = $this->get_node($id)) {
469
                $curr_node = $node[MIDCOM_NAV_ID];
470
            }
471
        }
472 10
        foreach ($this->get_node_path($curr_node) as $node_id) {
473 10
            $node = $this->get_node($node_id);
474 10
            $result[$node[MIDCOM_NAV_ID]] = [
475 10
                MIDCOM_NAV_URL => $node[MIDCOM_NAV_ABSOLUTEURL],
476 10
                MIDCOM_NAV_NAME => $node[MIDCOM_NAV_NAME],
477 10
                MIDCOM_NAV_TYPE => 'node',
478 10
                MIDCOM_NAV_ID => $node_id,
479 10
                'napobject' => $node,
480 10
            ];
481
        }
482 10
        if ($curr_leaf && $leaf = $this->get_leaf($curr_leaf)) {
483
            // Ignore Index Article Leaves
484
            if ($leaf[MIDCOM_NAV_URL] != '') {
485
                $result[$leaf[MIDCOM_NAV_ID]] = [
486
                    MIDCOM_NAV_URL => $leaf[MIDCOM_NAV_ABSOLUTEURL],
487
                    MIDCOM_NAV_NAME => $leaf[MIDCOM_NAV_NAME],
488
                    MIDCOM_NAV_TYPE => 'leaf',
489
                    MIDCOM_NAV_ID => $curr_leaf,
490
                    'napobject' => $leaf,
491
                ];
492
            }
493
        }
494
495 10
        if (midcom_core_context::get()->has_custom_key('midcom.helper.nav.breadcrumb')) {
496 7
            $customdata = midcom_core_context::get()->get_custom_key('midcom.helper.nav.breadcrumb');
497 7
            if (is_array($customdata)) {
498 7
                foreach ($customdata as $key => $entry) {
499 7
                    $id = "custom-{$key}";
500
501 7
                    $url = "{$prefix}{$entry[MIDCOM_NAV_URL]}";
502 7
                    if (   str_starts_with($entry[MIDCOM_NAV_URL], '/')
503 7
                        || preg_match('|^https?://|', $entry[MIDCOM_NAV_URL])) {
504 5
                        $url = $entry[MIDCOM_NAV_URL];
505
                    }
506
507 7
                    $result[$id] = [
508 7
                        MIDCOM_NAV_URL => $url,
509 7
                        MIDCOM_NAV_NAME => $entry[MIDCOM_NAV_NAME],
510 7
                        MIDCOM_NAV_TYPE => 'custom',
511 7
                        MIDCOM_NAV_ID => $id,
512 7
                        'napobject' => $entry,
513 7
                    ];
514
                }
515
            }
516
        }
517 10
        return $result;
518
    }
519
520
    /**
521
     * Retrieve the IDs of the nodes from the URL. First value at key 0 is
522
     * the root node ID, possible second value is the first subnode ID etc.
523
     * Contains only visible nodes (nodes which can be loaded).
524
     */
525 12
    public function get_node_path($node_id = null) : array
526
    {
527 12
        if ($node_id === null) {
528 9
            return $this->_backend->get_node_path();
529
        }
530 10
        $path = [];
531 10
        $node = $this->get_node($node_id);
532 10
        while ($node) {
533 10
            $path[] = $node[MIDCOM_NAV_ID];
534 10
            if ($node[MIDCOM_NAV_NODEID] === -1) {
535 10
                break;
536
            }
537 7
            $node = $this->get_node($node[MIDCOM_NAV_NODEID]);
538
        }
539 10
        return array_reverse($path);
540
    }
541
542
    /**
543
     * Retrieve the ID of the upper node of the currently displayed node.
544
     *
545
     * @return mixed    The ID of the node in question.
546
     */
547 1
    public function get_current_upper_node()
548
    {
549 1
        return $this->_backend->get_current_upper_node();
550
    }
551
}
552