MultiMenuWidget::run()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 1
eloc 2
c 2
b 1
f 0
nc 1
nop 2
dl 0
loc 5
rs 10
1
<?php
2
3
namespace Itstructure\MultiMenu;
4
5
use Exception;
6
use Illuminate\Database\Eloquent\{Model, Collection};
7
8
/**
9
 * Class MultiMenuWidget
10
 *
11
 * @package Itstructure\MultiMenu
12
 *
13
 * @author Andrey Girnik <[email protected]>
14
 */
15
class MultiMenuWidget {
16
17
    /**
18
     * Primary key name.
19
     * @var string
20
     */
21
    private $primaryKeyName = 'id';
22
23
    /**
24
     * Relation key name.
25
     * @var string
26
     */
27
    private $parentKeyName = 'parent_id';
28
29
    /**
30
     * Main container template to display widget elements.
31
     * @var string|array
32
     */
33
    private $mainTemplate = 'main';
34
35
    /**
36
     * Item container template to display widget elements.
37
     * @var string|array
38
     */
39
    private $itemTemplate = 'item';
40
41
    /**
42
     * Addition cross cutting data.
43
     * @var array
44
     */
45
    private $additionData = [];
46
47
    /**
48
     * MultiMenuWidget constructor.
49
     * @param array $config
50
     */
51
    public function __construct(array $config)
52
    {
53
        $this->setAttributes($config);
54
    }
55
56
    /**
57
     * Starts the output widget of the multi level view records according with the menu type.
58
     * @param Collection $data
59
     * @param array $additionData
60
     * @return string
61
     */
62
    public function run(Collection $data, array $additionData = []): string
63
    {
64
        $this->additionData = $additionData;
65
66
        return $this->renderItems($this->groupLevels($data));
67
    }
68
69
    /**
70
     * Check whether a particular record can be used as a parent.
71
     * @param Model $mainModel
72
     * @param int $newParentId
73
     * @param string|null $primaryKeyName
74
     * @param string|null $parentKeyName
75
     * @return bool
76
     */
77
    public static function checkNewParentId(
78
        Model $mainModel,
79
        int $newParentId,
80
        string $primaryKeyName = null,
81
        string $parentKeyName = null
82
    ): bool {
83
84
        $primaryKeyName = empty($primaryKeyName) ? config('multimenu.primaryKeyName', 'id') : $primaryKeyName;
85
        $parentKeyName = empty($parentKeyName) ? config('multimenu.parentKeyName', 'parent_id') : $parentKeyName;
86
87
        $parentRecord = $mainModel::where($primaryKeyName, $newParentId)
88
            ->select($primaryKeyName, $parentKeyName)
89
            ->first();
90
91
        if ($mainModel->{$primaryKeyName} === $parentRecord->{$primaryKeyName}) {
92
            return false;
93
        }
94
95
        if (null === $parentRecord->{$parentKeyName}) {
96
            return true;
97
        }
98
99
        return static::checkNewParentId($mainModel, $parentRecord->{$parentKeyName}, $primaryKeyName, $parentKeyName);
100
    }
101
102
    /**
103
     * Reassigning child objects to their new parent after delete the main model record.
104
     * @param Model $mainModel
105
     * @param string|null $primaryKeyName
106
     * @param string|null $parentKeyName
107
     */
108
    public static function afterDeleteMainModel(
109
        Model $mainModel,
110
        string $primaryKeyName = null,
111
        string $parentKeyName = null
112
    ): void {
113
114
        $primaryKeyName = empty($primaryKeyName) ? config('multimenu.primaryKeyName', 'id') : $primaryKeyName;
115
        $parentKeyName = empty($parentKeyName) ? config('multimenu.parentKeyName', 'parent_id') : $parentKeyName;
116
117
        $mainModel::where($parentKeyName, $mainModel->{$primaryKeyName})
118
            ->update([
119
                $parentKeyName => $mainModel->{$parentKeyName}
120
            ]);
121
    }
122
123
    /**
124
     * @param array $config
125
     * @return void
126
     */
127
    private function setAttributes(array $config): void
128
    {
129
        foreach ($config as $key => $value) {
130
            $this->{$key} = $value;
131
        }
132
    }
133
134
    /**
135
     * Group records in to sub levels according with the relation to parent records.
136
     * @param Collection $models
137
     * @return array
138
     * @throws Exception
139
     */
140
    private function groupLevels(Collection $models): array
141
    {
142
        if (!is_string($this->parentKeyName) || empty($this->primaryKeyName)) {
0 ignored issues
show
introduced by
The condition is_string($this->parentKeyName) is always true.
Loading history...
143
            throw new Exception('The parent key name is nod defined correctly.');
144
        }
145
146
        if (!is_string($this->primaryKeyName) || empty($this->primaryKeyName)) {
0 ignored issues
show
introduced by
The condition is_string($this->primaryKeyName) is always true.
Loading history...
147
            throw new Exception('The primary key name is nod defined correctly.');
148
        }
149
150
        if ($models->count() == 0) {
151
            return [];
152
        }
153
154
        $items = [];
155
156
        foreach ($models as $model) {
157
            $items[$model->{$this->primaryKeyName}]['data'] = $model;
158
        }
159
160
        foreach($items as $row) {
161
            $data = $row['data'];
162
            $parentKey = !isset($data->{$this->parentKeyName}) || empty($data->{$this->parentKeyName}) ?
163
                0 : $data->{$this->parentKeyName};
164
            $items[$parentKey]['items'][$data->{$this->primaryKeyName}] = &$items[$data->{$this->primaryKeyName}];
165
        }
166
167
        return $items[0]['items'];
168
    }
169
170
    /**
171
     * Base render.
172
     * @param array $items
173
     * @param int $level
174
     * @return string
175
     */
176
    private function renderItems(array $items, int $level = 0): string
177
    {
178
        if (count($items) == 0) {
179
            return '';
180
        }
181
182
        $itemsContent = '';
183
184
        /** @var array $item */
185
        foreach ($items as $item) {
186
187
            $itemsContent .= view('multimenu::'.$this->levelAttributeValue($this->itemTemplate, $level),
188
                array_merge([
189
                    'data' => $item['data']
190
                ], $this->levelAttributeValue($this->additionData, $level))
191
            );
192
193
            if (isset($item['items'])) {
194
                $itemsContent .= $this->renderItems($item['items'], $level + 1);
195
            }
196
        }
197
198
        return view('multimenu::'.$this->levelAttributeValue($this->mainTemplate, $level), array_merge([
199
                'items' => $itemsContent,
200
                'level' => $level
201
            ], $this->levelAttributeValue($this->additionData, $level))
202
        );
203
    }
204
205
    /**
206
     * Get attribute values in current level.
207
     * @param $attributeValue
208
     * @param int $level
209
     * @return mixed
210
     * @throws Exception
211
     */
212
    private function levelAttributeValue($attributeValue, int $level)
213
    {
214
        if (is_string($attributeValue)) {
215
            return $attributeValue;
216
        }
217
218
        if (is_array($attributeValue) && !isset($attributeValue['levels'])) {
219
            return $attributeValue;
220
        }
221
222
        if (is_array($attributeValue) && isset($attributeValue['levels'])) {
223
224
            $countLevels = count($attributeValue['levels']);
225
226
            if ($countLevels == 0) {
227
                throw new Exception('Level values are not defined for attribute.');
228
            }
229
230
            if (isset($attributeValue['levels'][$level])) {
231
                return $attributeValue['levels'][$level];
232
233
            } else {
234
                $levelKeys = array_keys($attributeValue['levels']);
235
                return $attributeValue['levels'][$levelKeys[$countLevels-1]];
236
            }
237
        }
238
239
        throw new Exception('Attribute is not defined correctly.');
240
    }
241
}
242