Completed
Branch dev (0c2712)
by Andrey
01:41
created

MenuWidget   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 237
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
wmc 29
lcom 1
cbo 4
dl 0
loc 237
rs 10
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A run() 0 6 1
C groupLevels() 0 29 7
B renderItems() 0 22 4
A currentItemTemplate() 0 4 2
A currentItemTemplateParams() 0 4 2
A currentMainContainerTag() 0 4 2
A currentMainContainerOptions() 0 4 2
A currentItemContainerTag() 0 4 2
A currentItemContainerOptions() 0 4 2
B checkConfiguration() 0 10 5
1
<?php
2
3
namespace Itstructure\MultiLevelMenu;
4
5
use yii\db\ActiveRecord;
6
use yii\helpers\{Html, ArrayHelper};
7
use yii\base\{Widget, InvalidConfigException};
8
9
/**
10
 * Class MenuWidget.
11
 * Multilevel menu widget.
12
 *
13
 * @property string $primaryKeyName Primary key name.
14
 * @property string $parentKeyName Relation key name.
15
 * @property string $mainContainerTag Main container html tag.
16
 * @property array $mainContainerOptions Main container html options.
17
 * @property string $subMainContainerTag Sub main container html tag.
18
 * @property array $subMainContainerOptions Sub main container html options.
19
 * @property string $itemContainerTag Item container html tag.
20
 * @property array $itemContainerOptions Item container html options.
21
 * @property string $subItemContainerTag Sub item container html tag.
22
 * @property array $subItemContainerOptions Sub item container html options.
23
 * @property string $itemTemplate Item template to display widget elements.
24
 * @property array $itemTemplateParams Addition item template params.
25
 * @property string $subItemTemplate Sub item template to display widget elements.
26
 * @property array $subItemTemplateParams Addition sub item template params.
27
 * @property ActiveRecord[] $data Data records.
28
 *
29
 * @package Itstructure\MultiLevelMenu
30
 *
31
 * @author Andrey Girnik <[email protected]>
32
 */
33
class MenuWidget extends Widget
34
{
35
    /**
36
     * Primary key name.
37
     * @var string
38
     */
39
    public $primaryKeyName = 'id';
40
41
    /**
42
     * Relation key name.
43
     * @var string
44
     */
45
    public $parentKeyName = 'parentId';
46
47
    /**
48
     * Main container html tag.
49
     * @var string
50
     */
51
    public $mainContainerTag = 'ul';
52
53
    /**
54
     * Main container html options.
55
     * @var array
56
     */
57
    public $mainContainerOptions = [];
58
59
    /**
60
     * Sub main container html tag.
61
     * @var string
62
     */
63
    public $subMainContainerTag = 'ul';
64
65
    /**
66
     * Sub main container html options.
67
     * @var array
68
     */
69
    public $subMainContainerOptions = [];
70
71
    /**
72
     * Item container html tag.
73
     * @var string
74
     */
75
    public $itemContainerTag = 'li';
76
77
    /**
78
     * Item container html options.
79
     * @var array
80
     */
81
    public $itemContainerOptions = [];
82
83
    /**
84
     * Sub item container html tag.
85
     * @var string
86
     */
87
    public $subItemContainerTag = 'li';
88
89
    /**
90
     * Sub item container html options.
91
     * @var array
92
     */
93
    public $subItemContainerOptions = [];
94
95
    /**
96
     * Item template to display widget elements.
97
     * @var string
98
     */
99
    public $itemTemplate;
100
101
    /**
102
     * Addition item template params.
103
     * @var array
104
     */
105
    public $itemTemplateParams = [];
106
107
    /**
108
     * Sub item template to display widget elements.
109
     * @var string
110
     */
111
    public $subItemTemplate;
112
113
    /**
114
     * Addition sub item template params.
115
     * @var array
116
     */
117
    public $subItemTemplateParams = [];
118
119
    /**
120
     * Data records.
121
     * @var ActiveRecord[]
122
     */
123
    public $data;
124
125
    /**
126
     * Starts the output widget of the multi level view records according with the menu type.
127
     * @throws InvalidConfigException
128
     */
129
    public function run()
130
    {
131
        $this->checkConfiguration();
132
133
        return $this->renderItems($this->groupLevels($this->data));
134
    }
135
136
    /**
137
     * Group records in to sub levels according with the relation to parent records.
138
     * @param array $models
139
     * @throws InvalidConfigException
140
     * @return array
141
     */
142
    private function groupLevels(array $models): array
143
    {
144
        if (count($models) == 0){
145
            return [];
146
        }
147
148
        $items = [];
149
150
        /** @var ActiveRecord $item */
151
        $modelsCount = count($models);
152
        for ($i=0; $i < $modelsCount; $i++) {
153
            $item = $models[$i];
154
155
            if (!($item instanceof ActiveRecord)){
156
                throw  new InvalidConfigException('Record with '.$i.' key must be an instance of ActiveRecord.');
157
            }
158
159
            $items[$item->{$this->primaryKeyName}]['data'] = $item;
160
        }
161
162
        /** @var ActiveRecord $data */
163
        foreach($items as $row) {
164
            $data = $row['data'];
165
            $parentKey = !isset($data->{$this->parentKeyName}) || empty($data->{$this->parentKeyName}) ? 0 : $data->{$this->parentKeyName};
166
            $items[$parentKey]['items'][$data->{$this->primaryKeyName}] = &$items[$data->{$this->primaryKeyName}];
167
        }
168
169
        return $items[0]['items'];
170
    }
171
172
    /**
173
     * Base render.
174
     * @param array $items
175
     * @param bool $initLevel
176
     * @return string
177
     */
178
    private function renderItems(array $items, bool $initLevel = true): string
179
    {
180
        if (count($items) == 0){
181
            return '';
182
        }
183
184
        $outPut = '';
185
186
        /** @var array $item */
187
        foreach ($items as $item) {
188
            $contentLi = $this->render($this->currentItemTemplate($initLevel), ArrayHelper::merge([
189
                'data' => $item['data']
190
            ], $this->currentItemTemplateParams($initLevel)));
191
192
            if (isset($item['items'])){
193
                $contentLi .= $this->renderItems($item['items'], false);
194
            }
195
            $outPut .= Html::tag($this->currentItemContainerTag($initLevel), $contentLi, $this->currentItemContainerOptions($initLevel));
196
        }
197
198
        return Html::tag($this->currentMainContainerTag($initLevel), $outPut, $this->currentMainContainerOptions($initLevel));
199
    }
200
201
    /**
202
     * @param bool $initLevel
203
     * @return string
204
     */
205
    private function currentItemTemplate(bool $initLevel = true): string
206
    {
207
        return $initLevel ? $this->itemTemplate : $this->subItemTemplate;
208
    }
209
210
    /**
211
     * @param bool $initLevel
212
     * @return array
213
     */
214
    private function currentItemTemplateParams(bool $initLevel = true): array
215
    {
216
        return $initLevel ? $this->itemTemplateParams : $this->subItemTemplateParams;
217
    }
218
219
    /**
220
     * @param bool $initLevel
221
     * @return string
222
     */
223
    private function currentMainContainerTag(bool $initLevel = true): string
224
    {
225
        return $initLevel ? $this->mainContainerTag : $this->subMainContainerTag;
226
    }
227
228
    /**
229
     * @param bool $initLevel
230
     * @return array
231
     */
232
    private function currentMainContainerOptions(bool $initLevel = true): array
233
    {
234
        return $initLevel ? $this->mainContainerOptions : $this->subMainContainerOptions;
235
    }
236
237
    /**
238
     * @param bool $initLevel
239
     * @return string
240
     */
241
    private function currentItemContainerTag(bool $initLevel = true): string
242
    {
243
        return $initLevel ? $this->itemContainerTag : $this->subItemContainerTag;
244
    }
245
246
    /**
247
     * @param bool $initLevel
248
     * @return array
249
     */
250
    private function currentItemContainerOptions(bool $initLevel = true): array
251
    {
252
        return $initLevel ? $this->itemContainerOptions : $this->subItemContainerOptions;
253
    }
254
255
    /**
256
     * Check for configure.
257
     * @throws InvalidConfigException
258
     */
259
    private function checkConfiguration()
260
    {
261
        if (null === $this->itemTemplate || !is_string($this->itemTemplate)){
262
            throw  new InvalidConfigException('Item template is not defined.');
263
        }
264
265
        if (null === $this->subItemTemplate || !is_string($this->subItemTemplate)){
266
            $this->subItemTemplate = $this->itemTemplate;
267
        }
268
    }
269
}
270