CategoriesHelper::isTree()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 8
c 0
b 0
f 0
dl 0
loc 14
rs 10
cc 4
nc 4
nop 0
1
<?php
2
/**
3
 * BEdita, API-first content management framework
4
 * Copyright 2021 ChannelWeb Srl, Chialab Srl
5
 *
6
 * This file is part of BEdita: you can redistribute it and/or modify
7
 * it under the terms of the GNU Lesser General Public License as published
8
 * by the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * See LICENSE.LGPL or <http://gnu.org/licenses/lgpl-3.0.html> for more details.
12
 */
13
namespace App\View\Helper;
14
15
use Cake\Utility\Hash;
16
use Cake\View\Helper;
17
18
/**
19
 * Categories helper
20
 *
21
 * @property \Cake\View\Helper\FormHelper $Form
22
 * @property \App\View\Helper\PropertyHelper $Property
23
 */
24
class CategoriesHelper extends Helper
25
{
26
    /**
27
     * Helpers
28
     *
29
     * @var array
30
     */
31
    public array $helpers = ['Form', 'Property'];
32
33
    /**
34
     * Control for categories
35
     *
36
     * @param string $name The name
37
     * @param mixed|null $value The value
38
     * @return string
39
     */
40
    public function control(string $name, mixed $value): string
41
    {
42
        $options = ['label' => false];
43
        if (!$this->isTree()) {
44
            return sprintf(
45
                '<div class="categories"><h3>%s</h3>%s</div>',
46
                __('Global'),
47
                $this->Property->control($name, $value, $options),
48
            );
49
        }
50
51
        return $this->html($name, $value, $options);
52
    }
53
54
    /**
55
     * Html for categories tree
56
     *
57
     * @param string $name The name
58
     * @param mixed|null $value The value
59
     * @param array $options The options
60
     * @return string
61
     */
62
    public function html(string $name, mixed $value, array $options): string
63
    {
64
        $html = '';
65
        $tree = $this->tree();
66
        $hiddenField = true; // hiddenField false on all controls except the first
67
        foreach ($tree as $node) {
68
            $html .= $this->node($node, $name, $value, $options, $hiddenField);
69
        }
70
71
        return $html;
72
    }
73
74
    /**
75
     * Html for single node of categories tree
76
     *
77
     * @param array $node The category node
78
     * @param string $name The name
79
     * @param mixed|null $value The value
80
     * @param array $options The options
81
     * @param bool $hiddenField The hiddenField flag
82
     * @return string
83
     */
84
    public function node(array $node, string $name, mixed $value, array $options, bool &$hiddenField): string
85
    {
86
        $title = sprintf('<h3>%s</h3>', $node['label'] = $node['label'] ?: $node['name']);
87
        $controlOptions = $this->controlOptions($node, $value, $options, $hiddenField);
88
89
        return sprintf(
90
            '<div class="categories">%s%s</div>',
91
            $title,
92
            $this->Form->control($name, $controlOptions),
93
        );
94
    }
95
96
    /**
97
     * Control options for category node.
98
     *
99
     * @param array $node The category node
100
     * @param mixed|null $value The value
101
     * @param array $options The options
102
     * @param bool $hiddenField The hiddenField flag
103
     * @return array
104
     */
105
    public function controlOptions(array $node, mixed $value, array $options, bool &$hiddenField): array
106
    {
107
        $controlOptions = $options + [
108
            'type' => 'select',
109
            'multiple' => 'checkbox',
110
            'value' => (array)Hash::extract((array)$value, '{n}.name'),
111
            'hiddenField' => $hiddenField,
112
        ];
113
        $hiddenField = false;
114
        if (empty($node['children'])) {
115
            $controlOptions['options'][0] = ['value' => $node['name'], 'text' => $node['label'] = $node['label'] ?: $node['name']];
116
117
            return $controlOptions;
118
        }
119
        foreach ($node['children'] as $key => $child) {
120
            if ((bool)Hash::get($child, 'enabled', true)) {
121
                $controlOptions['options'][$key] = ['value' => $child['name'], 'text' => $child['label'] = $child['label'] ?: $child['name']];
122
            }
123
        }
124
125
        return $controlOptions;
126
    }
127
128
    /**
129
     * Categories tree.
130
     * Return roots with children in 'children'.
131
     *
132
     * @return array
133
     */
134
    public function tree(): array
135
    {
136
        $schema = (array)$this->_View->get('schema');
137
        $categories = (array)Hash::get($schema, 'categories');
138
        if (empty($categories)) {
139
            return [];
140
        }
141
142
        $globalCategories = [];
143
        $roots = [];
144
        foreach ($categories as $category) {
145
            if (empty($category['parent_id'])) { // root
146
                $roots[$category['id']] = array_merge($category, (array)Hash::get($roots, $category['id']));
147
            } else { // child
148
                $roots[$category['parent_id']]['children'][] = $category;
149
            }
150
        }
151
152
        foreach ($roots as $key => &$root) {
153
            if (!empty($root['children'])) {
154
                usort($root['children'], [$this, 'sortRoots']);
155
            }
156
            if (empty($root['parent_id']) && empty($root['children'])) {
157
                $globalCategories[] = $root;
158
                unset($roots[$key]);
159
            }
160
        }
161
162
        if (!empty($globalCategories)) {
163
            array_unshift($roots, [
164
                'id' => '0',
165
                'name' => '_',
166
                'label' => __('Global'),
167
                'parent_id' => null,
168
                'children' => $globalCategories,
169
            ]);
170
        }
171
172
        usort($roots, [$this, 'sortRoots']);
173
174
        return $roots;
175
    }
176
177
    /**
178
     * Sort roots. Global first.
179
     *
180
     * @param array $a The first node
181
     * @param array $b The second node
182
     * @return int
183
     */
184
    public function sortRoots(array $a, array $b): int
185
    {
186
        return strcmp(
187
            strtolower((string)Hash::get($a, 'name')),
188
            strtolower((string)Hash::get($b, 'name')),
189
        );
190
    }
191
192
    /**
193
     * Return true when a category parent id is not null, among schema.categories.
194
     * False otherwise.
195
     *
196
     * @return bool
197
     */
198
    public function isTree(): bool
199
    {
200
        $schema = (array)$this->_View->get('schema');
201
        $categories = (array)Hash::get($schema, 'categories');
202
        if (empty($categories)) {
203
            return false;
204
        }
205
        foreach ($categories as $category) {
206
            if (!empty($category['parent_id'])) {
207
                return true;
208
            }
209
        }
210
211
        return false;
212
    }
213
}
214