1
|
|
|
<?php |
2
|
|
|
namespace TheCodingMachine\CMS\StaticRegistry\Menu; |
3
|
|
|
|
4
|
|
|
/** |
5
|
|
|
* This class represent a menu item. |
6
|
|
|
*/ |
7
|
|
|
class MenuItem { |
8
|
|
|
|
9
|
|
|
/** |
10
|
|
|
* The text for the menu item |
11
|
|
|
* |
12
|
|
|
* @var string |
13
|
|
|
*/ |
14
|
|
|
private $label; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* The link for the menu (relative to the root url), unless it starts with / or http:// or https:// or # or ?. |
18
|
|
|
* |
19
|
|
|
* @var string|null |
20
|
|
|
*/ |
21
|
|
|
private $url; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* The children menu item of this menu (if any). |
25
|
|
|
* |
26
|
|
|
* @var MenuItem[] |
27
|
|
|
*/ |
28
|
|
|
private $children; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* The CSS class for the menu, if any. |
32
|
|
|
* |
33
|
|
|
* @var string |
34
|
|
|
*/ |
35
|
|
|
private $cssClass; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* Whether the menu is in an active state or not. |
39
|
|
|
* |
40
|
|
|
* @var bool |
41
|
|
|
*/ |
42
|
|
|
private $isActive; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* Whether the menu is extended or not. |
46
|
|
|
* This should not have an effect if the menu has no child. |
47
|
|
|
* |
48
|
|
|
* @var bool |
49
|
|
|
*/ |
50
|
|
|
private $isExtended; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Level of priority used to order the menu items. |
54
|
|
|
* |
55
|
|
|
* @var float |
56
|
|
|
*/ |
57
|
|
|
private $priority = 0.0; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* @var bool |
61
|
|
|
*/ |
62
|
|
|
private $activateBasedOnUrl; |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* @var bool |
66
|
|
|
*/ |
67
|
|
|
private $sorted = false; |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* @param string $label The text for the menu item |
71
|
|
|
* @param string|null $url The link for the menu (relative to the root url), unless it starts with / or http:// or https:// or # or ?. |
72
|
|
|
* @param MenuItem[] $children |
73
|
|
|
*/ |
74
|
|
|
public function __construct(string $label, string $url=null, array $children=[]) { |
75
|
|
|
$this->label = $label; |
76
|
|
|
$this->url = $url; |
77
|
|
|
$this->children = $children; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* Returns the label for the menu item. |
82
|
|
|
* @return string |
83
|
|
|
*/ |
84
|
|
|
public function getLabel(): string { |
85
|
|
|
return $this->label; |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* Returns the URL for this menu (or null if this menu is not a link). |
90
|
|
|
* @return string|null |
91
|
|
|
*/ |
92
|
|
|
public function getUrl(): ?string { |
93
|
|
|
return $this->url; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
public function findChild(string $label): ?MenuItem |
97
|
|
|
{ |
98
|
|
|
foreach ($this->children as $child) { |
99
|
|
|
if ($child->getLabel() === $label) { |
100
|
|
|
return $child; |
101
|
|
|
} |
102
|
|
|
} |
103
|
|
|
return null; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Returns a list of children elements for the menu (if there are some). |
108
|
|
|
* |
109
|
|
|
* @return MenuItem[] |
110
|
|
|
*/ |
111
|
|
|
public function getChildren(): array { |
112
|
|
|
if ($this->sorted === false && $this->children) { |
|
|
|
|
113
|
|
|
// First, let's make 2 arrays: the array of children with a priority, and the array without. |
114
|
|
|
$childrenWithPriorities = array(); |
115
|
|
|
$childrenWithoutPriorities = array(); |
116
|
|
|
foreach ($this->children as $child) { |
117
|
|
|
/* @var $child MenuItem */ |
118
|
|
|
$priority = $child->getPriority(); |
119
|
|
|
if ($priority === null) { |
120
|
|
|
$childrenWithoutPriorities[] = $child; |
121
|
|
|
} else { |
122
|
|
|
$childrenWithPriorities[] = $child; |
123
|
|
|
} |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
usort($childrenWithPriorities, [$this, 'compareMenuItems']); |
127
|
|
|
$this->children = array_merge($childrenWithPriorities, $childrenWithoutPriorities); |
128
|
|
|
$this->sorted = true; |
129
|
|
|
} |
130
|
|
|
return $this->children; |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
public function compareMenuItems(MenuItem $item1, MenuItem $item2): int { |
134
|
|
|
return $item1->getPriority() <=> $item2->getPriority(); |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* The children menu item of this menu (if any). |
139
|
|
|
* |
140
|
|
|
* @param MenuItem[] $children |
141
|
|
|
* @return MenuItem |
142
|
|
|
*/ |
143
|
|
|
public function setChildren(array $children): self { |
144
|
|
|
$this->sorted = false; |
145
|
|
|
$this->children = $children; |
146
|
|
|
return $this; |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* Adds a menu item as a child of this menu item. |
151
|
|
|
* |
152
|
|
|
* @param MenuItem $menuItem |
153
|
|
|
* @return MenuItem |
154
|
|
|
*/ |
155
|
|
|
public function addMenuItem(MenuItem $menuItem): self { |
156
|
|
|
$this->sorted = false; |
157
|
|
|
$this->children[] = $menuItem; |
158
|
|
|
return $this; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* Returns true if the menu is in active state (if we are on the page for this menu). |
163
|
|
|
* @return bool |
164
|
|
|
*/ |
165
|
|
|
public function isActive(string $rootUrl) { |
166
|
|
|
if ($this->isActive) { |
167
|
|
|
return true; |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
if($this->activateBasedOnUrl && $this->url !== null) { |
171
|
|
|
$urlParts = parse_url($_SERVER['REQUEST_URI']); |
172
|
|
|
$menuUrlParts = parse_url($this->getLink($rootUrl)); |
173
|
|
|
|
174
|
|
|
if (isset($menuUrlParts['path'])) { |
175
|
|
|
$menuUrl = $menuUrlParts['path']; |
176
|
|
|
} else { |
177
|
|
|
$menuUrl = '/'; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
if (isset($urlParts['path'])) { |
181
|
|
|
$requestUrl = $urlParts['path']; |
182
|
|
|
} else { |
183
|
|
|
$requestUrl = '/'; |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
if($requestUrl === $menuUrl) { |
187
|
|
|
return true; |
188
|
|
|
} |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
return false; |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* Set the active state of the menu. |
196
|
|
|
* |
197
|
|
|
* @param bool $isActive |
198
|
|
|
* @return MenuItem |
199
|
|
|
*/ |
200
|
|
|
public function setIsActive(bool $isActive): self { |
201
|
|
|
$this->isActive = $isActive; |
202
|
|
|
return $this; |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
/** |
206
|
|
|
* Enables the menu item (activates it). |
207
|
|
|
* |
208
|
|
|
*/ |
209
|
|
|
public function enable(): self { |
210
|
|
|
$this->isActive = true; |
211
|
|
|
return $this; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* Returns true if the menu should be in extended state (if we can see the children directly). |
216
|
|
|
* @return bool |
217
|
|
|
*/ |
218
|
|
|
public function isExtended(): bool { |
219
|
|
|
// TODO: is extended if one of the sub menus is active! |
220
|
|
|
return $this->isExtended; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
/** |
224
|
|
|
* Whether the menu is extended or not. |
225
|
|
|
* This should not have an effect if the menu has no child. |
226
|
|
|
* |
227
|
|
|
* @param bool $isExtended |
228
|
|
|
* @return MenuItem |
229
|
|
|
*/ |
230
|
|
|
public function setIsExtended(bool $isExtended = true): self { |
231
|
|
|
$this->isExtended = $isExtended; |
232
|
|
|
return $this; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
/** |
236
|
|
|
* Returns an optional CSS class to apply to the menu item. |
237
|
|
|
* @return string|null |
238
|
|
|
*/ |
239
|
|
|
public function getCssClass(): ?string { |
240
|
|
|
return $this->cssClass; |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* An optional CSS class to apply to the menu item. |
245
|
|
|
* Use of this property depends on the menu implementation. |
246
|
|
|
* |
247
|
|
|
* @param string $cssClass |
248
|
|
|
*/ |
249
|
|
|
public function setCssClass($cssClass) { |
250
|
|
|
$this->cssClass = $cssClass; |
251
|
|
|
return $this; |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
/** |
255
|
|
|
* Level of priority used to order the menu items. |
256
|
|
|
* |
257
|
|
|
* @param float $priority |
258
|
|
|
*/ |
259
|
|
|
public function setPriority(float $priority) { |
260
|
|
|
$this->priority = $priority; |
261
|
|
|
return $this; |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
/** |
265
|
|
|
* Returns the level of priority. It is used to order the menu items. |
266
|
|
|
* @return float |
267
|
|
|
*/ |
268
|
|
|
public function getPriority(): float { |
269
|
|
|
return $this->priority; |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
/** |
273
|
|
|
* Returns the absolute URL, with parameters if required. |
274
|
|
|
* @return string |
275
|
|
|
*/ |
276
|
|
|
public function getLink(string $rootUrl) { |
277
|
|
|
if ($this->url === null) { |
278
|
|
|
return null; |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
if (strpos($this->url, "/") === 0 |
282
|
|
|
|| strpos($this->url, "javascript:") === 0 |
283
|
|
|
|| strpos($this->url, "http://") === 0 |
284
|
|
|
|| strpos($this->url, "https://") === 0 |
285
|
|
|
|| strpos($this->url, "?") === 0 |
286
|
|
|
|| strpos($this->url, "#") === 0) { |
287
|
|
|
return $this->url; |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
return $rootUrl.$this->url; |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
/** |
294
|
|
|
* If the URL of the current page matches the URL of the link, the link will be considered as "active". |
295
|
|
|
* |
296
|
|
|
* @param bool $activateBasedOnUrl |
297
|
|
|
* @return MenuItem |
298
|
|
|
*/ |
299
|
|
|
public function setActivateBasedOnUrl(bool $activateBasedOnUrl = true): self { |
300
|
|
|
$this->activateBasedOnUrl = $activateBasedOnUrl; |
301
|
|
|
return $this; |
302
|
|
|
} |
303
|
|
|
} |
304
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.