Passed
Push — master ( 82761a...afd45d )
by Jan
03:53
created

StructuralDBElement::getParentID()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
/**
5
 * Part-DB Version 0.4+ "nextgen"
6
 * Copyright (C) 2016 - 2019 Jan Böhmer
7
 * https://github.com/jbtronics.
8
 *
9
 * This program is free software; you can redistribute it and/or
10
 * modify it under the terms of the GNU General Public License
11
 * as published by the Free Software Foundation; either version 2
12
 * of the License, or (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program; if not, write to the Free Software
21
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
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
30
/**
31
 * All elements with the fields "id", "name" and "parent_id" (at least).
32
 *
33
 * This class is for managing all database objects with a structural design.
34
 * All these sub-objects must have the table columns 'id', 'name' and 'parent_id' (at least)!
35
 * The root node has always the ID '0'.
36
 * It's allowed to have instances of root elements, but if you try to change
37
 * an attribute of a root element, you will get an exception!
38
 *
39
 * @ORM\MappedSuperclass(repositoryClass="App\Repository\StructuralDBElementRepository")
40
 */
41
abstract class StructuralDBElement extends AttachmentContainingDBElement
42
{
43
    public const ID_ROOT_ELEMENT = 0;
44
45
    //This is a not standard character, so build a const, so a dev can easily use it
46
    public const PATH_DELIMITER_ARROW = ' → ';
47
48
    // We can not define the mapping here or we will get an exception. Unfortunatly we have to do the mapping in the
49
    // subclasses
50
    /**
51
     * @var StructuralDBElement[]
52
     */
53
    protected $children;
54
    /**
55
     * @var StructuralDBElement
56
     */
57
    protected $parent;
58
59
    /**
60
     * @var string The comment info for this element
61
     * @ORM\Column(type="string", nullable=true)
62
     */
63
    protected $comment;
64
65
    /**
66
     * @var int
67
     * @ORM\Column(type="integer", nullable=true)
68
     */
69
    protected $parent_id;
70
71
    /**
72
     * @var int
73
     */
74
    protected $level = 0;
75
76
    /** @var string[] all names of all parent elements as a array of strings,
77
     *  the last array element is the name of the element itself */
78
    private $full_path_strings;
79
80
    /******************************************************************************
81
     * StructuralDBElement constructor.
82
     *****************************************************************************/
83
84
    /**
85
     * Check if this element is a child of another element (recursive).
86
     *
87
     * @param StructuralDBElement $another_element the object to compare
88
     *                                             IMPORTANT: both objects to compare must be from the same class (for example two "Device" objects)!
89
     *
90
     * @return bool True, if this element is child of $another_element.
91
     *
92
     * @throws \InvalidArgumentException if there was an error
93
     */
94
    public function isChildOf(StructuralDBElement $another_element)
95
    {
96
        $class_name = \get_class($this);
97
98
        //Check if both elements compared, are from the same type:
99
        if ($class_name != \get_class($another_element)) {
100
            throw new \InvalidArgumentException('isChildOf() funktioniert nur mit Elementen des gleichen Typs!');
101
        }
102
103
        if (null == $this->getID()) { // this is the root node
104
            return false;
105
        }
106
107
        //If this' parents element, is $another_element, then we are finished
108
        return ($this->parent->getID() == $another_element->getID())
109
            || $this->parent->isChildOf($another_element); //Otherwise, check recursivley
110
    }
111
112
    /******************************************************************************
113
     *
114
     * Getters
115
     *
116
     ******************************************************************************/
117
118
    /**
119
     * @brief Get the parent-ID
120
     *
121
     * @retval integer          * the ID of the parent element
122
     *                          * NULL means, the parent is the root node
123
     *                          * the parent ID of the root node is -1
124
     */
125
    public function getParentID(): int
126
    {
127
        return $this->parent_id ?? self::ID_ROOT_ELEMENT; //Null means root element
128
    }
129
130
    /**
131
     * Get the parent of this element.
132
     *
133
     * @return StructuralDBElement|null The parent element. Null if this element, does not have a parent.
134
     */
135
    public function getParent(): ?self
136
    {
137
        return $this->parent;
138
    }
139
140
    /**
141
     *  Get the comment of the element.
142
     *
143
     * @param bool $parse_bbcode Should BBCode converted to HTML, before returning
144
     *
145
     * @return string the comment
146
     */
147
    public function getComment(bool $parse_bbcode = true): string
0 ignored issues
show
Unused Code introduced by
The parameter $parse_bbcode 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

147
    public function getComment(/** @scrutinizer ignore-unused */ bool $parse_bbcode = true): string

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...
148
    {
149
        return htmlspecialchars($this->comment ?? '');
150
    }
151
152
    /**
153
     * Get the level.
154
     *
155
     *     The level of the root node is -1.
156
     *
157
     * @return int the level of this element (zero means a most top element
158
     *             [a subelement of the root node])
159
     */
160
    public function getLevel(): int
161
    {
162
        if (0 === $this->level) {
163
            $element = $this->parent;
164
            $parent_id = $element->getParentID();
165
            while ($parent_id > 0) {
166
                /** @var StructuralDBElement $element */
167
                $element = $element->parent;
168
                $parent_id = $element->getParentID();
169
                ++$this->level;
170
            }
171
        }
172
173
        return $this->level;
174
    }
175
176
    /**
177
     * Get the full path.
178
     *
179
     * @param string $delimeter the delimeter of the returned string
180
     *
181
     * @return string the full path (incl. the name of this element), delimeted by $delimeter
182
     *
183
     * @throws Exception if there was an error
184
     */
185
    public function getFullPath(string $delimeter = self::PATH_DELIMITER_ARROW): string
186
    {
187
        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...
188
            $this->full_path_strings = array();
189
            $this->full_path_strings[] = $this->getName();
190
            $element = $this;
191
192
            while (null != $element->parent) {
193
                $element = $element->parent;
194
                $this->full_path_strings[] = $element->getName();
195
            }
196
197
            $this->full_path_strings = array_reverse($this->full_path_strings);
198
        }
199
200
        return implode($delimeter, $this->full_path_strings);
201
    }
202
203
    /**
204
     * Get all subelements of this element.
205
     *
206
     * @param bool $recursive if true, the search is recursive
207
     *
208
     * @return static[] all subelements as an array of objects (sorted by their full path)
209
     */
210
    public function getSubelements(): PersistentCollection
211
    {
212
        return $this->children;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->children returns the type App\Entity\StructuralDBElement[] which is incompatible with the type-hinted return Doctrine\ORM\PersistentCollection.
Loading history...
213
    }
214
215
    /******************************************************************************
216
     *
217
     * Setters
218
     *
219
     ******************************************************************************/
220
221
    /**
222
     * Change the parent ID of this element.
223
     *
224
     * @param int|null $new_parent_id * the ID of the new parent element
225
     *                                * NULL if the parent should be the root node
226
     */
227
    public function setParentID($new_parent_id): self
228
    {
229
        $this->parent_id = $new_parent_id;
230
231
        return $this;
232
    }
233
234
    /**
235
     *  Set the comment.
236
     *
237
     * @param string $new_comment the new comment
238
     *
239
     * @throws Exception if there was an error
240
     */
241
    public function setComment(string $new_comment): self
242
    {
243
        $this->comment = $new_comment;
244
245
        return $this;
246
    }
247
248
    /********************************************************************************
249
     *
250
     *   Tree / Table Builders
251
     *
252
     *********************************************************************************/
253
254
    /**
255
     * Build a HTML tree with all subcategories of this element.
256
     *
257
     * This method prints a <option>-Line for every item.
258
     * <b>The <select>-tags are not printed here, you have to print them yourself!</b>
259
     * Deeper levels have more spaces in front.
260
     *
261
     * @param int    $selected_id  the ID of the selected item
262
     * @param bool   $recursive    if true, the tree will be recursive
263
     * @param bool   $show_root    if true, the root node will be displayed
264
     * @param string $root_name    if the root node is the very root element, you can set its name here
265
     * @param string $value_prefix This string is used as a prefix before the id in the value part of the option.
266
     *
267
     * @return string HTML string if success
268
     *
269
     * @throws Exception if there was an error
270
     */
271
    public function buildHtmlTree(
272
        $selected_id = null,
273
        bool $recursive = true,
274
        bool $show_root = true,
275
        string $root_name = '$$',
276
        string $value_prefix = ''
277
    ): string {
278
        if ('$$' == $root_name) {
279
            $root_name = _('Oberste Ebene');
280
        }
281
282
        $html = array();
283
284
        if ($show_root) {
285
            $root_level = $this->getLevel();
286
            if ($this->getID() > 0) {
287
                $root_name = htmlspecialchars($this->getName());
288
            }
289
290
            $html[] = '<option value="'.$value_prefix.$this->getID().'">'.$root_name.'</option>';
291
        } else {
292
            $root_level = $this->getLevel() + 1;
293
        }
294
295
        // get all subelements
296
        $subelements = $this->getSubelements($recursive);
0 ignored issues
show
Unused Code introduced by
The call to App\Entity\StructuralDBElement::getSubelements() has too many arguments starting with $recursive. ( Ignorable by Annotation )

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

296
        /** @scrutinizer ignore-call */ 
297
        $subelements = $this->getSubelements($recursive);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
297
298
        foreach ($subelements as $element) {
299
            $level = $element->getLevel() - $root_level;
300
            $selected = ($element->getID() == $selected_id) ? 'selected' : '';
301
302
            $html[] = '<option '.$selected.' value="'.$value_prefix.$element->getID().'">';
303
            for ($i = 0; $i < $level; ++$i) {
304
                $html[] = '&nbsp;&nbsp;&nbsp;';
305
            }
306
            $html[] = htmlspecialchars($element->getName()).'</option>';
307
        }
308
309
        return implode("\n", $html);
310
    }
311
312
    /**
313
     * Creates a template loop for a Breadcrumb bar, representing the structural DB element.
314
     *
315
     * @param $page string The base page, to which the breadcrumb links should be directing to.
316
     * @param $parameter string The parameter, which selects the ID of the StructuralDBElement.
317
     * @param bool   $show_root Show the root as its own breadcrumb.
318
     * @param string $root_name The label which should be used for the root breadcrumb.
319
     *
320
     * @return array An Loop containing multiple arrays, which contains href and caption for the breadcrumb.
321
     */
322
    public function buildBreadcrumbLoop(string $page, string $parameter, bool $show_root = false, $root_name = '$$', bool $element_is_link = false): array
323
    {
324
        $breadcrumb = array();
325
326
        if ('$$' == $root_name) {
327
            $root_name = _('Oberste Ebene');
328
        }
329
330
        if ($show_root) {
331
            $breadcrumb[] = array('label' => $root_name,
332
                'disabled' => true, );
333
        }
334
335
        if (!$this->current_user->canDo(static::getPermissionName(), StructuralPermission::READ)) {
0 ignored issues
show
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

335
        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...
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...
336
            return array('label' => '???',
337
                'disabled' => true, );
338
        }
339
340
        $tmp = array();
341
342
        if ($element_is_link) {
343
            $tmp[] = array('label' => $this->getName(), 'href' => $page.'?'.$parameter.'='.$this->getID(), 'selected' => true);
344
        } else {
345
            $tmp[] = array('label' => $this->getName(), 'selected' => true);
346
        }
347
348
        $parent_id = $this->getParentID();
349
        while ($parent_id > 0) {
350
            /** @var StructuralDBElement $element */
351
            $element = static::getInstance($this->database, $this->current_user, $this->log, $parent_id);
0 ignored issues
show
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

351
            /** @scrutinizer ignore-call */ 
352
            $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...
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...
352
            $parent_id = $element->getParentID();
353
            $tmp[] = array('label' => $element->getName(), 'href' => $page.'?'.$parameter.'='.$element->getID());
354
        }
355
        $tmp = array_reverse($tmp);
356
357
        $breadcrumb = array_merge($breadcrumb, $tmp);
358
359
        return $breadcrumb;
360
    }
361
}
362