TreeViewGenerator::getTreeView()   F
last analyzed

Complexity

Conditions 16
Paths 408

Size

Total Lines 64
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 16
eloc 33
c 0
b 0
f 0
nc 408
nop 4
dl 0
loc 64
rs 2.2222

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
 * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
4
 *
5
 * Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
6
 *
7
 * This program is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU Affero General Public License as published
9
 * by the Free Software Foundation, either version 3 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU Affero General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Affero General Public License
18
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19
 */
20
21
declare(strict_types=1);
22
23
namespace App\Services\Trees;
24
25
use App\Entity\Base\AbstractDBElement;
26
use App\Entity\Base\AbstractNamedDBElement;
27
use App\Entity\Base\AbstractStructuralDBElement;
28
use App\Entity\ProjectSystem\Project;
29
use App\Entity\Parts\Category;
30
use App\Entity\Parts\Footprint;
31
use App\Entity\Parts\Manufacturer;
32
use App\Entity\Parts\Storelocation;
33
use App\Entity\Parts\Supplier;
34
use App\Helpers\Trees\TreeViewNode;
35
use App\Helpers\Trees\TreeViewNodeIterator;
36
use App\Helpers\Trees\TreeViewNodeState;
37
use App\Repository\StructuralDBElementRepository;
38
use App\Services\EntityURLGenerator;
39
use App\Services\Formatters\MarkdownParser;
40
use App\Services\UserSystem\UserCacheKeyGenerator;
41
use Doctrine\ORM\EntityManagerInterface;
42
use InvalidArgumentException;
43
use RecursiveIteratorIterator;
44
use Symfony\Contracts\Cache\ItemInterface;
45
use Symfony\Contracts\Cache\TagAwareCacheInterface;
46
use Symfony\Contracts\Translation\TranslatorInterface;
47
48
use function count;
49
50
class TreeViewGenerator
51
{
52
    protected $urlGenerator;
53
    protected $em;
54
    protected $cache;
55
    protected $keyGenerator;
56
    protected $translator;
57
58
    protected $rootNodeExpandedByDefault;
59
    protected $rootNodeEnabled;
60
61
    public function __construct(EntityURLGenerator $URLGenerator, EntityManagerInterface $em,
62
        TagAwareCacheInterface $treeCache, UserCacheKeyGenerator $keyGenerator, TranslatorInterface $translator, bool $rootNodeExpandedByDefault, bool $rootNodeEnabled)
63
    {
64
        $this->urlGenerator = $URLGenerator;
65
        $this->em = $em;
66
        $this->cache = $treeCache;
67
        $this->keyGenerator = $keyGenerator;
68
        $this->translator = $translator;
69
70
        $this->rootNodeExpandedByDefault = $rootNodeExpandedByDefault;
71
        $this->rootNodeEnabled = $rootNodeEnabled;
72
    }
73
74
    /**
75
     * Gets a TreeView list for the entities of the given class.
76
     *
77
     * @param string                           $class           The class for which the treeView should be generated
78
     * @param AbstractStructuralDBElement|null $parent          The root nodes in the tree should have this element as parent (use null, if you want to get all entities)
79
     * @param string                           $mode            The link type that will be generated for the hyperlink section of each node (see EntityURLGenerator for possible values).
80
     *                                                          Set to empty string, to disable href field.
81
     * @param AbstractDBElement|null           $selectedElement The element that should be selected. If set to null, no element will be selected.
82
     *
83
     * @return TreeViewNode[] an array of TreeViewNode[] elements of the root elements
84
     */
85
    public function getTreeView(string $class, ?AbstractStructuralDBElement $parent = null, string $mode = 'list_parts', ?AbstractDBElement $selectedElement = null): array
86
    {
87
        $head = [];
88
89
        $href_type = $mode;
90
91
        //When we use the newEdit type, add the New Element node.
92
        if ('newEdit' === $mode) {
93
            //Generate the url for the new node
94
            $href = $this->urlGenerator->createURL(new $class());
95
            $new_node = new TreeViewNode($this->translator->trans('entity.tree.new'), $href);
96
            //When the id of the selected element is null, then we have a new element, and we need to select "new" node
97
            if (null === $selectedElement || null === $selectedElement->getID()) {
98
                $new_node->setSelected(true);
99
            }
100
            $head[] = $new_node;
101
            //Add spacing
102
            $head[] = (new TreeViewNode(''))->setDisabled(true);
103
104
            //Every other treeNode will be used for edit
105
            $href_type = 'edit';
106
        }
107
108
        if ($mode === 'list_parts_root') {
109
            $href_type = 'list_parts';
110
        }
111
112
        if ($mode === 'devices') {
113
            $href_type = 'list_parts';
114
        }
115
116
        $generic = $this->getGenericTree($class, $parent);
117
        $treeIterator = new TreeViewNodeIterator($generic);
118
        $recursiveIterator = new RecursiveIteratorIterator($treeIterator, RecursiveIteratorIterator::SELF_FIRST);
119
        foreach ($recursiveIterator as $item) {
120
            /** @var TreeViewNode $item */
121
            if (null !== $selectedElement && $item->getId() === $selectedElement->getID()) {
122
                $item->setSelected(true);
123
            }
124
125
            if (!empty($item->getNodes())) {
126
                $item->addTag((string) count($item->getNodes()));
127
            }
128
129
            if (!empty($href_type) && null !== $item->getId()) {
130
                $entity = $this->em->getPartialReference($class, $item->getId());
131
                $item->setHref($this->urlGenerator->getURL($entity, $href_type));
132
            }
133
134
            //Translate text if text starts with $$
135
            if (0 === strpos($item->getText(), '$$')) {
136
                $item->setText($this->translator->trans(substr($item->getText(), 2)));
137
            }
138
        }
139
140
        if (($mode === 'list_parts_root' || $mode === 'devices') && $this->rootNodeEnabled) {
141
            $root_node = new TreeViewNode($this->entityClassToRootNodeString($class), null, $generic);
142
            $root_node->setExpanded($this->rootNodeExpandedByDefault);
143
            $root_node->setIcon($this->entityClassToRootNodeIcon($class));
144
145
            $generic = [$root_node];
146
        }
147
148
        return array_merge($head, $generic);
149
    }
150
151
    protected function entityClassToRootNodeString(string $class): string
152
    {
153
        switch ($class) {
154
            case Category::class:
155
                return $this->translator->trans('category.labelp');
156
            case Storelocation::class:
157
                return $this->translator->trans('storelocation.labelp');
158
            case Footprint::class:
159
                return $this->translator->trans('footprint.labelp');
160
            case Manufacturer::class:
161
                return $this->translator->trans('manufacturer.labelp');
162
            case Supplier::class:
163
                return $this->translator->trans('supplier.labelp');
164
            case Project::class:
165
                return $this->translator->trans('project.labelp');
166
            default:
167
                return $this->translator->trans('tree.root_node.text');
168
        }
169
    }
170
171
    protected function entityClassToRootNodeIcon(string $class): ?string
172
    {
173
        $icon = "fa-fw fa-treeview fa-solid ";
174
        switch ($class) {
175
            case Category::class:
176
                return $icon . 'fa-tags';
177
            case Storelocation::class:
178
                return $icon . 'fa-cube';
179
            case Footprint::class:
180
                return $icon . 'fa-microchip';
181
            case Manufacturer::class:
182
                return $icon . 'fa-industry';
183
            case Supplier::class:
184
                return $icon . 'fa-truck';
185
            case Project::class:
186
                return $icon . 'fa-archive';
187
            default:
188
                return null;
189
        }
190
    }
191
192
    /**
193
     * /**
194
     * Gets a tree of TreeViewNode elements. The root elements has $parent as parent.
195
     * The treeview is generic, that means the href are null and ID values are set.
196
     *
197
     * @param string                           $class  The class for which the tree should be generated
198
     * @param AbstractStructuralDBElement|null $parent the parent the root elements should have
199
     *
200
     * @return TreeViewNode[]
201
     */
202
    public function getGenericTree(string $class, ?AbstractStructuralDBElement $parent = null): array
203
    {
204
        if (!is_a($class, AbstractNamedDBElement::class, true)) {
205
            throw new InvalidArgumentException('$class must be a class string that implements StructuralDBElement or NamedDBElement!');
206
        }
207
        if (null !== $parent && !is_a($parent, $class)) {
208
            throw new InvalidArgumentException('$parent must be of the type $class!');
209
        }
210
211
        /** @var StructuralDBElementRepository $repo */
212
        $repo = $this->em->getRepository($class);
213
214
        //If we just want a part of a tree, dont cache it
215
        if (null !== $parent) {
216
            return $repo->getGenericNodeTree($parent);
217
        }
218
219
        $secure_class_name = str_replace('\\', '_', $class);
220
        $key = 'treeview_'.$this->keyGenerator->generateKey().'_'.$secure_class_name;
221
222
        return $this->cache->get($key, function (ItemInterface $item) use ($repo, $parent, $secure_class_name) {
223
            // Invalidate when groups, a element with the class or the user changes
224
            $item->tag(['groups', 'tree_treeview', $this->keyGenerator->generateKey(), $secure_class_name]);
225
226
            return $repo->getGenericNodeTree($parent);
227
        });
228
    }
229
}
230