1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Oc\Menu\Renderer; |
4
|
|
|
|
5
|
|
|
use Knp\Menu\ItemInterface; |
6
|
|
|
use Knp\Menu\Matcher\MatcherInterface; |
7
|
|
|
use Knp\Menu\Renderer\Renderer; |
8
|
|
|
use Knp\Menu\Renderer\RendererInterface; |
9
|
|
|
|
10
|
|
|
/** |
11
|
|
|
* Class MainRenderer |
12
|
|
|
* |
13
|
|
|
* @package Oc\Menu\Renderer |
14
|
|
|
*/ |
15
|
|
|
class MainRenderer extends Renderer implements RendererInterface |
16
|
|
|
{ |
17
|
|
|
protected $matcher; |
18
|
|
|
protected $defaultOptions; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* @param MatcherInterface $matcher |
22
|
|
|
* @param array $defaultOptions |
23
|
|
|
* @param string $charset |
24
|
|
|
*/ |
25
|
|
|
public function __construct(MatcherInterface $matcher, array $defaultOptions = array(), $charset = null) |
26
|
|
|
{ |
27
|
|
|
$this->matcher = $matcher; |
28
|
|
|
$this->defaultOptions = array_merge(array( |
29
|
|
|
'depth' => null, |
30
|
|
|
'matchingDepth' => null, |
31
|
|
|
'currentAsLink' => true, |
32
|
|
|
'currentClass' => 'navigation-item--current', |
33
|
|
|
'ancestorClass' => null, |
34
|
|
|
'listClass' => 'navigation', |
35
|
|
|
'listLevelPrefixClass' => 'navigation--level-', |
36
|
|
|
'itemClass' => 'navigation__item navigation-item', |
37
|
|
|
'textClass' => 'navigation-item__text', |
38
|
|
|
'linkClass' => 'navigation-item__link', |
39
|
|
|
'firstClass' => 'navigation-item--first', |
40
|
|
|
'lastClass' => 'navigation-item--last', |
41
|
|
|
'compressed' => false, |
42
|
|
|
'allow_safe_labels' => false, |
43
|
|
|
'clear_matcher' => true, |
44
|
|
|
'leaf_class' => null, |
45
|
|
|
'branch_class' => null, |
46
|
|
|
), $defaultOptions); |
47
|
|
|
|
48
|
|
|
parent::__construct($charset); |
49
|
|
|
} |
50
|
|
|
|
51
|
|
|
public function render(ItemInterface $item, array $options = array()) |
52
|
|
|
{ |
53
|
|
|
$options = array_merge($this->defaultOptions, $options); |
54
|
|
|
|
55
|
|
|
$childrenClass = (array) $item->getChildrenAttribute('class'); |
56
|
|
|
|
57
|
|
|
if ($options['listClass']) { |
58
|
|
|
$childrenClass[] = $options['listClass']; |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
if ($options['listLevelPrefixClass']) { |
62
|
|
|
$childrenClass[] = $options['listLevelPrefixClass'] . $item->getLevel(); |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
$childrenAttributes = $item->getChildrenAttributes(); |
66
|
|
|
$childrenAttributes['class'] = implode(' ', $childrenClass); |
67
|
|
|
|
68
|
|
|
$html = $this->renderList($item, $childrenAttributes, $options); |
69
|
|
|
|
70
|
|
|
if ($options['clear_matcher']) { |
71
|
|
|
$this->matcher->clear(); |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
return $html; |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
protected function renderList(ItemInterface $item, array $attributes, array $options) |
78
|
|
|
{ |
79
|
|
|
/** |
80
|
|
|
* Return an empty string if any of the following are true: |
81
|
|
|
* a) The menu has no children eligible to be displayed |
82
|
|
|
* b) The depth is 0 |
83
|
|
|
* c) This menu item has been explicitly set to hide its children |
84
|
|
|
*/ |
85
|
|
|
if (!$item->hasChildren() || 0 === $options['depth'] || !$item->getDisplayChildren()) { |
86
|
|
|
return ''; |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
$html = $this->format('<ul'.$this->renderHtmlAttributes($attributes).'>', 'ul', $item->getLevel(), $options); |
90
|
|
|
$html .= $this->renderChildren($item, $options); |
91
|
|
|
$html .= $this->format('</ul>', 'ul', $item->getLevel(), $options); |
92
|
|
|
|
93
|
|
|
return $html; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* Renders all of the children of this menu. |
98
|
|
|
* |
99
|
|
|
* This calls ->renderItem() on each menu item, which instructs each |
100
|
|
|
* menu item to render themselves as an <li> tag (with nested ul if it |
101
|
|
|
* has children). |
102
|
|
|
* This method updates the depth for the children. |
103
|
|
|
* |
104
|
|
|
* @param ItemInterface $item |
105
|
|
|
* @param array $options The options to render the item. |
106
|
|
|
* |
107
|
|
|
* @return string |
108
|
|
|
*/ |
109
|
|
|
protected function renderChildren(ItemInterface $item, array $options) |
110
|
|
|
{ |
111
|
|
|
// render children with a depth - 1 |
112
|
|
|
if (null !== $options['depth']) { |
113
|
|
|
$options['depth'] = $options['depth'] - 1; |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
if (null !== $options['matchingDepth'] && $options['matchingDepth'] > 0) { |
117
|
|
|
$options['matchingDepth'] = $options['matchingDepth'] - 1; |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
$html = ''; |
121
|
|
|
foreach ($item->getChildren() as $child) { |
122
|
|
|
$html .= $this->renderItem($child, $options); |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
return $html; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* Called by the parent menu item to render this menu. |
130
|
|
|
* |
131
|
|
|
* This renders the li tag to fit into the parent ul as well as its |
132
|
|
|
* own nested ul tag if this menu item has children |
133
|
|
|
* |
134
|
|
|
* @param ItemInterface $item |
135
|
|
|
* @param array $options The options to render the item |
136
|
|
|
* |
137
|
|
|
* @return string |
138
|
|
|
*/ |
139
|
|
|
protected function renderItem(ItemInterface $item, array $options) |
140
|
|
|
{ |
141
|
|
|
// if we don't have access or this item is marked to not be shown |
142
|
|
|
if (!$item->isDisplayed()) { |
143
|
|
|
return ''; |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
// create an array than can be imploded as a class list |
147
|
|
|
$class = (array) $item->getAttribute('class'); |
148
|
|
|
|
149
|
|
|
if ($options['itemClass']) { |
150
|
|
|
$class[] = $options['itemClass']; |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
if ($this->matcher->isCurrent($item)) { |
154
|
|
|
$class[] = $options['currentClass']; |
155
|
|
|
} elseif ($this->matcher->isAncestor($item, $options['matchingDepth'])) { |
156
|
|
|
$class[] = $options['ancestorClass']; |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
if ($item->actsLikeFirst()) { |
160
|
|
|
$class[] = $options['firstClass']; |
161
|
|
|
} |
162
|
|
|
if ($item->actsLikeLast()) { |
163
|
|
|
$class[] = $options['lastClass']; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
if ($item->hasChildren() && $options['depth'] !== 0) { |
167
|
|
|
if (null !== $options['branch_class'] && $item->getDisplayChildren()) { |
168
|
|
|
$class[] = $options['branch_class']; |
169
|
|
|
} |
170
|
|
|
} elseif (null !== $options['leaf_class']) { |
171
|
|
|
$class[] = $options['leaf_class']; |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
// retrieve the attributes and put the final class string back on it |
175
|
|
|
$attributes = $item->getAttributes(); |
176
|
|
|
if (!empty($class)) { |
177
|
|
|
$attributes['class'] = implode(' ', $class); |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
// opening li tag |
181
|
|
|
$html = $this->format('<li'.$this->renderHtmlAttributes($attributes).'>', 'li', $item->getLevel(), $options); |
182
|
|
|
|
183
|
|
|
// render the text/link inside the li tag |
184
|
|
|
//$html .= $this->format($item->getUri() ? $item->renderLink() : $item->renderLabel(), 'link', $item->getLevel()); |
|
|
|
|
185
|
|
|
$html .= $this->renderLink($item, $options); |
186
|
|
|
|
187
|
|
|
// renders the embedded ul |
188
|
|
|
$childrenClass = (array) $item->getChildrenAttribute('class'); |
189
|
|
|
if ($options['listClass']) { |
190
|
|
|
$childrenClass[] = $options['listClass']; |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
if ($options['listLevelPrefixClass']) { |
194
|
|
|
$childrenClass[] = $options['listLevelPrefixClass'] . $item->getLevel(); |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
$childrenAttributes = $item->getChildrenAttributes(); |
198
|
|
|
$childrenAttributes['class'] = implode(' ', $childrenClass); |
199
|
|
|
|
200
|
|
|
$html .= $this->renderList($item, $childrenAttributes, $options); |
201
|
|
|
|
202
|
|
|
// closing li tag |
203
|
|
|
$html .= $this->format('</li>', 'li', $item->getLevel(), $options); |
204
|
|
|
|
205
|
|
|
return $html; |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
/** |
209
|
|
|
* Renders the link in a a tag with link attributes or |
210
|
|
|
* the label in a span tag with label attributes |
211
|
|
|
* |
212
|
|
|
* Tests if item has a an uri and if not tests if it's |
213
|
|
|
* the current item and if the text has to be rendered |
214
|
|
|
* as a link or not. |
215
|
|
|
* |
216
|
|
|
* @param ItemInterface $item The item to render the link or label for |
217
|
|
|
* @param array $options The options to render the item |
218
|
|
|
* |
219
|
|
|
* @return string |
220
|
|
|
*/ |
221
|
|
|
protected function renderLink(ItemInterface $item, array $options = array()) |
222
|
|
|
{ |
223
|
|
|
if ($item->getUri() && (!$item->isCurrent() || $options['currentAsLink'])) { |
|
|
|
|
224
|
|
|
$text = $this->renderLinkElement($item, $options); |
225
|
|
|
} else { |
226
|
|
|
$text = $this->renderSpanElement($item, $options); |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
return $this->format($text, 'link', $item->getLevel(), $options); |
230
|
|
|
} |
231
|
|
|
|
232
|
|
View Code Duplication |
protected function renderLinkElement(ItemInterface $item, array $options) |
|
|
|
|
233
|
|
|
{ |
234
|
|
|
$childrenClass = (array) $item->getChildrenAttribute('class'); |
235
|
|
|
|
236
|
|
|
if ($options['linkClass']) { |
237
|
|
|
$childrenClass[] = $options['linkClass']; |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
$childrenAttributes = $item->getChildrenAttributes(); |
241
|
|
|
$childrenAttributes['class'] = implode(' ', $childrenClass); |
242
|
|
|
|
243
|
|
|
return sprintf('<a href="%s"%s>%s</a>', $this->escape($item->getUri()), $this->renderHtmlAttributes($childrenAttributes), $this->renderLabel($item, $options)); |
244
|
|
|
} |
245
|
|
|
|
246
|
|
View Code Duplication |
protected function renderSpanElement(ItemInterface $item, array $options) |
|
|
|
|
247
|
|
|
{ |
248
|
|
|
$childrenClass = (array) $item->getChildrenAttribute('class'); |
249
|
|
|
|
250
|
|
|
if ($options['textClass']) { |
251
|
|
|
$childrenClass[] = $options['textClass']; |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
$childrenAttributes = $item->getChildrenAttributes(); |
255
|
|
|
$childrenAttributes['class'] = implode(' ', $childrenClass); |
256
|
|
|
|
257
|
|
|
return sprintf('<span%s>%s</span>', $this->renderHtmlAttributes($childrenAttributes), $this->renderLabel($item, $options)); |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
protected function renderLabel(ItemInterface $item, array $options) |
261
|
|
|
{ |
262
|
|
|
if ($options['allow_safe_labels'] && $item->getExtra('safe_label', false)) { |
263
|
|
|
return $item->getLabel(); |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
return $this->escape($item->getLabel()); |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
/** |
270
|
|
|
* If $this->renderCompressed is on, this will apply the necessary |
271
|
|
|
* spacing and line-breaking so that the particular thing being rendered |
272
|
|
|
* makes up its part in a fully-rendered and spaced menu. |
273
|
|
|
* |
274
|
|
|
* @param string $html The html to render in an (un)formatted way |
275
|
|
|
* @param string $type The type [ul,link,li] of thing being rendered |
276
|
|
|
* @param integer $level |
277
|
|
|
* @param array $options |
278
|
|
|
* |
279
|
|
|
* @return string |
280
|
|
|
*/ |
281
|
|
|
protected function format($html, $type, $level, array $options) |
282
|
|
|
{ |
283
|
|
|
if ($options['compressed']) { |
284
|
|
|
return $html; |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
switch ($type) { |
288
|
|
|
case 'ul': |
289
|
|
|
case 'link': |
290
|
|
|
$spacing = $level * 4; |
291
|
|
|
break; |
292
|
|
|
|
293
|
|
|
case 'li': |
294
|
|
|
$spacing = $level * 4 - 2; |
295
|
|
|
break; |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
return str_repeat(' ', $spacing).$html."\n"; |
|
|
|
|
299
|
|
|
} |
300
|
|
|
} |
301
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.