Passed
Push — master ( ab3f5d...01e1f2 )
by Jan
02:56
created

StructuralDBElement::getFullPath()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 2
nop 1
dl 0
loc 16
rs 9.9666
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
/**
3
 *
4
 * Part-DB Version 0.4+ "nextgen"
5
 * Copyright (C) 2016 - 2019 Jan Böhmer
6
 * https://github.com/jbtronics
7
 *
8
 * This program is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU General Public License
10
 * as published by the Free Software Foundation; either version 2
11
 * of the License, or (at your option) any later version.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
21
 *
22
 */
23
24
namespace App\Entity;
25
26
use Doctrine\Common\Collections\ArrayCollection;
27
use Doctrine\ORM\Mapping as ORM;
28
use Doctrine\ORM\PersistentCollection;
29
use Symfony\Bundle\MakerBundle\Str;
30
31
/**
32
 *
33
 * All elements with the fields "id", "name" and "parent_id" (at least)
34
 *
35
 * This class is for managing all database objects with a structural design.
36
 * All these sub-objects must have the table columns 'id', 'name' and 'parent_id' (at least)!
37
 * The root node has always the ID '0'.
38
 * It's allowed to have instances of root elements, but if you try to change
39
 * an attribute of a root element, you will get an exception!
40
 *
41
 * @ORM\MappedSuperclass()
42
 */
43
abstract class StructuralDBElement extends AttachmentContainingDBElement
44
{
45
    const ID_ROOT_ELEMENT = 0;
46
47
    //This is a not standard character, so build a const, so a dev can easily use it
48
    const PATH_DELIMITER_ARROW = ' → ';
49
50
51
    // We can not define the mapping here or we will get an exception. Unfortunatly we have to do the mapping in the
52
    // subclasses
53
    /**
54
     * @var StructuralDBElement[]
55
     */
56
    protected $children;
57
    /**
58
     * @var StructuralDBElement
59
     */
60
    protected $parent;
61
62
    /**
63
     * @var string The comment info for this element
64
     * @ORM\Column(type="string", nullable=true)
65
     */
66
    protected $comment;
67
68
    /**
69
     * @var int
70
     * @ORM\Column(type="integer", nullable=true)
71
     */
72
    protected $parent_id;
73
74
    /**
75
     * @var int
76
     */
77
    protected $level=0;
78
79
    /** @var string[] all names of all parent elements as a array of strings,
80
     *  the last array element is the name of the element itself */
81
    private $full_path_strings =  null;
82
83
    /******************************************************************************
84
     * StructuralDBElement constructor.
85
     *****************************************************************************/
86
87
    /**
88
     * Check if this element is a child of another element (recursive)
89
     *
90
     * @param StructuralDBElement $another_element       the object to compare
91
     *        IMPORTANT: both objects to compare must be from the same class (for example two "Device" objects)!
92
     *
93
     * @return bool True, if this element is child of $another_element.
94
     *
95
     * @throws \InvalidArgumentException if there was an error
96
     */
97
    public function isChildOf(StructuralDBElement $another_element)
98
    {
99
        $class_name = \get_class($this);
100
101
        //Check if both elements compared, are from the same type:
102
        if ($class_name != \get_class($another_element)) {
103
            throw new \InvalidArgumentException('isChildOf() funktioniert nur mit Elementen des gleichen Typs!');
104
        }
105
106
        if ($this->getID() == null) { // this is the root node
107
            return false;
108
        } else {
109
            //If this' parents element, is $another_element, then we are finished
110
            return (($this->parent->getID() == $another_element->getID())
111
                || $this->parent->isChildOf($another_element)); //Otherwise, check recursivley
112
        }
113
    }
114
115
116
    /******************************************************************************
117
     *
118
     * Getters
119
     *
120
     ******************************************************************************/
121
122
    /**
123
     * @brief Get the parent-ID
124
     *
125
     * @retval integer          * the ID of the parent element
126
     *                          * NULL means, the parent is the root node
127
     *                          * the parent ID of the root node is -1
128
     */
129
    public function getParentID() : int
130
    {
131
        return $this->parent_id ?? self::ID_ROOT_ELEMENT; //Null means root element
132
    }
133
134
    /**
135
     * Get the parent of this element.
136
     * @return StructuralDBElement|null The parent element. Null if this element, does not have a parent.
137
     */
138
    public function getParent() : ?self
139
    {
140
        return $this->parent;
141
    }
142
143
    /**
144
     *  Get the comment of the element.
145
     *
146
     * @param boolean $parse_bbcode Should BBCode converted to HTML, before returning
147
     * @return string       the comment
148
     */
149
    public function getComment(bool $parse_bbcode = true) : string
150
    {
151
        $val = htmlspecialchars($this->comment ?? '');
152
        if ($parse_bbcode) {
153
            //$bbcode = new BBCodeParser();
154
            //$val = $bbcode->parse($val);
155
        }
156
157
        return $val;
158
    }
159
160
    /**
161
     * Get the level
162
     *
163
     *     The level of the root node is -1.
164
     *
165
     * @return integer      the level of this element (zero means a most top element
166
     *                      [a subelement of the root node])
167
     *
168
     */
169
    public function getLevel() : int
170
    {
171
        if ($this->level === 0) {
172
            $element = $this->parent;
173
            $parent_id = $element->getParentID();
174
            while ($parent_id > 0) {
175
                /** @var StructuralDBElement $element */
176
                $element = $element->parent;
177
                $parent_id = $element->getParentID();
178
                $this->level++;
179
            }
180
        }
181
182
        return $this->level;
183
    }
184
185
    /**
186
     * Get the full path
187
     *
188
     * @param string $delimeter     the delimeter of the returned string
189
     *
190
     * @return string       the full path (incl. the name of this element), delimeted by $delimeter
191
     *
192
     * @throws Exception    if there was an error
193
     */
194
    public function getFullPath(string $delimeter = self::PATH_DELIMITER_ARROW) : string
195
    {
196
        if (! \is_array($this->full_path_strings)) {
0 ignored issues
show
introduced by
The condition is_array($this->full_path_strings) is always true.
Loading history...
197
            $this->full_path_strings = array();
198
            $this->full_path_strings[] = $this->getName();
199
            $element = $this;
200
201
            while ($element->parent != null) {
202
                $element = $element->parent;
203
                $this->full_path_strings[] = $element->getName();
204
            }
205
206
            $this->full_path_strings = array_reverse($this->full_path_strings);
207
        }
208
209
        return implode($delimeter, $this->full_path_strings);
210
    }
211
212
    /**
213
     * Get all subelements of this element
214
     *
215
     * @param boolean $recursive        if true, the search is recursive
216
     *
217
     * @return static[]    all subelements as an array of objects (sorted by their full path)
218
     */
219
    public function getSubelements(bool $recursive) : PersistentCollection
220
    {
221
        if ($this->children == null) {
222
            $this->children = new \Doctrine\Common\Collections\ArrayCollection();
0 ignored issues
show
Documentation Bug introduced by
It seems like new Doctrine\Common\Collections\ArrayCollection() of type Doctrine\Common\Collections\ArrayCollection is incompatible with the declared type App\Entity\StructuralDBElement[] of property $children.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
223
        }
224
225
        if (! $recursive) {
226
            return $this->children;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->children returns the type App\Entity\StructuralDBE...ections\ArrayCollection which is incompatible with the type-hinted return Doctrine\ORM\PersistentCollection.
Loading history...
227
        } else {
228
            $all_elements = array();
229
            foreach ($this->children as $subelement) {
230
                $all_elements[] = $subelement;
231
                $all_elements = array_merge($all_elements, $subelement->getSubelements(true));
232
            }
233
234
            return $all_elements;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $all_elements returns the type array which is incompatible with the type-hinted return Doctrine\ORM\PersistentCollection.
Loading history...
235
        }
236
    }
237
238
    /******************************************************************************
239
     *
240
     * Setters
241
     *
242
     ******************************************************************************/
243
244
    /**
245
     * Change the parent ID of this element
246
     *
247
     * @param integer|null $new_parent_id           * the ID of the new parent element
248
     *                                              * NULL if the parent should be the root node
249
     */
250
    public function setParentID($new_parent_id) : self
251
    {
252
        $this->parent_id = $new_parent_id;
253
        return $this;
254
    }
255
256
    /**
257
     *  Set the comment
258
     *
259
     * @param string $new_comment       the new comment
260
     * @throws Exception if there was an error
261
     */
262
    public function setComment(string $new_comment) : self
263
    {
264
        $this->comment = $new_comment;
265
        return $this;
266
    }
267
268
    /********************************************************************************
269
     *
270
     *   Tree / Table Builders
271
     *
272
     *********************************************************************************/
273
274
    /**
275
     * Build a HTML tree with all subcategories of this element
276
     *
277
     * This method prints a <option>-Line for every item.
278
     * <b>The <select>-tags are not printed here, you have to print them yourself!</b>
279
     * Deeper levels have more spaces in front.
280
     *
281
     * @param integer   $selected_id    the ID of the selected item
282
     * @param boolean   $recursive      if true, the tree will be recursive
283
     * @param boolean   $show_root      if true, the root node will be displayed
284
     * @param string    $root_name      if the root node is the very root element, you can set its name here
285
     * @param string    $value_prefix   This string is used as a prefix before the id in the value part of the option.
286
     *
287
     * @return string       HTML string if success
288
     *
289
     * @throws Exception    if there was an error
290
     */
291
    public function buildHtmlTree(
292
        $selected_id = null,
293
        bool $recursive = true,
294
        bool $show_root = true,
295
        string $root_name = '$$',
296
        string $value_prefix = ''
297
    ) : string {
298
        if ($root_name == '$$') {
299
            $root_name = _('Oberste Ebene');
300
        }
301
302
        $html = array();
303
304
        if ($show_root) {
305
            $root_level = $this->getLevel();
306
            if ($this->getID() > 0) {
307
                $root_name = htmlspecialchars($this->getName());
308
            }
309
310
            $html[] = '<option value="'. $value_prefix . $this->getID() . '">' . $root_name . '</option>';
311
        } else {
312
            $root_level =  $this->getLevel() + 1;
313
        }
314
315
        // get all subelements
316
        $subelements = $this->getSubelements($recursive);
317
318
        foreach ($subelements as $element) {
319
            $level = $element->getLevel() - $root_level;
320
            $selected = ($element->getID() == $selected_id) ? 'selected' : '';
321
322
            $html[] = '<option ' . $selected . ' value="' . $value_prefix . $element->getID() . '">';
323
            for ($i = 0; $i < $level; $i++) {
324
                $html[] = '&nbsp;&nbsp;&nbsp;';
325
            }
326
            $html[] = htmlspecialchars($element->getName()) . '</option>';
327
        }
328
329
        return implode("\n", $html);
330
    }
331
332
333
    public function buildBootstrapTree(
334
        $page,
335
        $parameter,
336
        $recursive = false,
0 ignored issues
show
Unused Code introduced by
The parameter $recursive is not used and could be removed. ( Ignorable by Annotation )

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

336
        /** @scrutinizer ignore-unused */ $recursive = false,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
337
        $show_root = false,
338
        $use_db_root_name = true,
339
        $root_name = '$$'
340
    ): array
341
    {
342
        if ($root_name == '$$') {
343
            $root_name = _('Oberste Ebene');
344
        }
345
346
        $subelements = $this->getSubelements(false);
347
        $nodes = array();
348
349
        foreach ($subelements as $element) {
350
            $nodes[] = $element->buildBootstrapTree($page, $parameter);
351
        }
352
353
        // if we are on root level?
354
        if ($this->getParentID() == -1) {
355
            if ($show_root) {
356
                $tree = array(
357
                    array('text' => $use_db_root_name ? htmlspecialchars($this->getName()) : $root_name ,
358
                        'href' => $page . '?' . $parameter . '=' . $this->getID(),
359
                        'nodes' => $nodes)
360
                );
361
            } else { //Dont show root node
362
                $tree = $nodes;
363
            }
364
        } elseif (!empty($nodes)) {
365
            $tree = array('text' => htmlspecialchars($this->getName()),
366
                'href' => $page . '?' . $parameter . '=' . $this->getID(),
367
                'nodes' => $nodes
368
            );
369
        } else {
370
            $tree = array('text' => htmlspecialchars($this->getName()),
371
                'href' => $page . '?' . $parameter . '=' .  $this->getID()
372
            );
373
        }
374
375
376
        return $tree;
377
    }
378
379
    /**
380
     * Creates a template loop for a Breadcrumb bar, representing the structural DB element.
381
     * @param $page string The base page, to which the breadcrumb links should be directing to.
382
     * @param $parameter string The parameter, which selects the ID of the StructuralDBElement.
383
     * @param bool $show_root Show the root as its own breadcrumb.
384
     * @param string $root_name The label which should be used for the root breadcrumb.
385
     * @return array An Loop containing multiple arrays, which contains href and caption for the breadcrumb.
386
     */
387
    public function buildBreadcrumbLoop(string $page, string $parameter, bool $show_root = false, $root_name = '$$', bool $element_is_link = false) : array
388
    {
389
        $breadcrumb = array();
390
391
        if ($root_name == '$$') {
392
            $root_name = _('Oberste Ebene');
393
        }
394
395
        if ($show_root) {
396
            $breadcrumb[] = array('label' => $root_name,
397
                'disabled' => true);
398
        }
399
400
        if (!$this->current_user->canDo(static::getPermissionName(), StructuralPermission::READ)) {
0 ignored issues
show
Bug Best Practice introduced by
The property current_user does not exist on App\Entity\StructuralDBElement. Did you maybe forget to declare it?
Loading history...
Bug introduced by
The type App\Entity\StructuralPermission was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
Bug introduced by
The method getPermissionName() does not exist on App\Entity\StructuralDBElement. ( Ignorable by Annotation )

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

400
        if (!$this->current_user->canDo(static::/** @scrutinizer ignore-call */ getPermissionName(), StructuralPermission::READ)) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
401
            return array('label' => '???',
402
                'disabled' => true);
403
        }
404
405
        $tmp = array();
406
407
        if ($element_is_link) {
408
            $tmp[] = array('label' => $this->getName(), 'href' => $page . '?' . $parameter . '=' .$this->getID(), 'selected' => true);
409
        } else {
410
            $tmp[] = array('label' => $this->getName(), 'selected' => true);
411
        }
412
413
        $parent_id = $this->getParentID();
414
        while ($parent_id > 0) {
415
            /** @var StructuralDBElement $element */
416
            $element = static::getInstance($this->database, $this->current_user, $this->log, $parent_id);
0 ignored issues
show
Bug Best Practice introduced by
The property database does not exist on App\Entity\StructuralDBElement. Did you maybe forget to declare it?
Loading history...
Bug Best Practice introduced by
The property log does not exist on App\Entity\StructuralDBElement. Did you maybe forget to declare it?
Loading history...
Bug introduced by
The method getInstance() does not exist on App\Entity\StructuralDBElement. ( Ignorable by Annotation )

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

416
            /** @scrutinizer ignore-call */ 
417
            $element = static::getInstance($this->database, $this->current_user, $this->log, $parent_id);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
417
            $parent_id = $element->getParentID();
418
            $tmp[] = array('label' => $element->getName(), 'href' => $page . '?' . $parameter . '=' . $element->getID());
419
        }
420
        $tmp = array_reverse($tmp);
421
422
        $breadcrumb = array_merge($breadcrumb, $tmp);
423
424
        return $breadcrumb;
425
    }
426
427
}