1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Licensed under The GPL-3.0 License |
4
|
|
|
* For full copyright and license information, please see the LICENSE.txt |
5
|
|
|
* Redistributions of files must retain the above copyright notice. |
6
|
|
|
* |
7
|
|
|
* @since 2.0.0 |
8
|
|
|
* @author Christopher Castro <[email protected]> |
9
|
|
|
* @link http://www.quickappscms.org |
10
|
|
|
* @license http://opensource.org/licenses/gpl-3.0.html GPL-3.0 License |
11
|
|
|
*/ |
12
|
|
|
namespace Menu\View\Helper; |
13
|
|
|
|
14
|
|
|
use Cake\Datasource\EntityInterface; |
15
|
|
|
use Cake\Error\FatalErrorException; |
16
|
|
|
use Cake\ORM\Entity; |
17
|
|
|
use Cake\ORM\TableRegistry; |
18
|
|
|
use Cake\Utility\Hash; |
19
|
|
|
use Cake\Utility\Inflector; |
20
|
|
|
use Cake\View\StringTemplateTrait; |
21
|
|
|
use Cake\View\View; |
22
|
|
|
use CMS\Core\Plugin; |
23
|
|
|
use CMS\View\Helper; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* Menu helper. |
27
|
|
|
* |
28
|
|
|
* Renders nested database records into a well formated `<ul>` menus |
29
|
|
|
* suitable for HTML pages. |
30
|
|
|
*/ |
31
|
|
|
class MenuHelper extends Helper |
32
|
|
|
{ |
33
|
|
|
|
34
|
|
|
use StringTemplateTrait; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* Default configuration for this class. |
38
|
|
|
* |
39
|
|
|
* - `formatter`: Callable method used when formating each item. Function should |
40
|
|
|
* expect two arguments: $item and $info respectively. |
41
|
|
|
* - `beautify`: Set to true to "beautify" the resulting HTML, compacted HTMl will |
42
|
|
|
* be returned if set to FALSE. You can set this option to a string compatible with |
43
|
|
|
* [htmLawed](http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed/htmLawed_README.htm) library. |
44
|
|
|
* e.g: `2s0n`. Defaults to FALSE (compact). |
45
|
|
|
* - `dropdown`: Set to true to automatically apply a few CSS styles for creating a |
46
|
|
|
* "Dropdown" menu. Defaults to FALSE. This option is useful when rendering |
47
|
|
|
* Multi-level menus, such as site's "main menu", etc. |
48
|
|
|
* - `activeClass`: CSS class to use when an item is active (its URL matches current URL). |
49
|
|
|
* - `firstItemClass`: CSS class for the first item. |
50
|
|
|
* - `lastItemClass`: CSS class for the last item. |
51
|
|
|
* - `hasChildrenClass`: CSS class to use when an item has children. |
52
|
|
|
* - `split`: Split menu into multiple root menus (multiple UL's). Must be an integer, |
53
|
|
|
* or false for no split (by default). |
54
|
|
|
* - `breadcrumbGuessing`: Whether to mark an item as "active" if its URL is on |
55
|
|
|
* the breadcrumb stack. Default to true. |
56
|
|
|
* - `templates`: HTML templates used when formating items. |
57
|
|
|
* - `div`: Template of the wrapper element which holds all menus when using `split`. |
58
|
|
|
* - `root`: Top UL/OL menu template. |
59
|
|
|
* - `parent`: Wrapper which holds children of a parent node. |
60
|
|
|
* - `child`: Template for child nodes (leafs). |
61
|
|
|
* - `link`: Template for link elements. |
62
|
|
|
* |
63
|
|
|
* ## Example: |
64
|
|
|
* |
65
|
|
|
* This example shows where each template is used when rendering a menu. |
66
|
|
|
* |
67
|
|
|
* ```html |
68
|
|
|
* <div> // div template (only if split > 1) |
69
|
|
|
* <ul> // root template (first part of split menu) |
70
|
|
|
* <li> // child template |
71
|
|
|
* <a href="">Link 1</a> // link template |
72
|
|
|
* </li> |
73
|
|
|
* <li> // child template |
74
|
|
|
* <a href="">Link 2</a> // link template |
75
|
|
|
* <ul> // parent template |
76
|
|
|
* <li> // child template |
77
|
|
|
* <a href="">Link 2.1</a> // link template |
78
|
|
|
* </li> |
79
|
|
|
* <li> // child template |
80
|
|
|
* <a href="">Link 2.2</a> // link template |
81
|
|
|
* </li> |
82
|
|
|
* ... |
83
|
|
|
* </ul> |
84
|
|
|
* </li> |
85
|
|
|
* ... |
86
|
|
|
* </ul> |
87
|
|
|
* |
88
|
|
|
* <ul> // root template (second part of split menu) |
89
|
|
|
* ... |
90
|
|
|
* </ul> |
91
|
|
|
* |
92
|
|
|
* ... |
93
|
|
|
* </div> |
94
|
|
|
* ``` |
95
|
|
|
* |
96
|
|
|
* @var array |
97
|
|
|
*/ |
98
|
|
|
protected $_defaultConfig = [ |
99
|
|
|
'formatter' => null, |
100
|
|
|
'beautify' => false, |
101
|
|
|
'dropdown' => false, |
102
|
|
|
'activeClass' => 'active', |
103
|
|
|
'firstClass' => 'first-item', |
104
|
|
|
'lastClass' => 'last-item', |
105
|
|
|
'hasChildrenClass' => 'has-children', |
106
|
|
|
'split' => false, |
107
|
|
|
'breadcrumbGuessing' => true, |
108
|
|
|
'templates' => [ |
109
|
|
|
'div' => '<div{{attrs}}>{{content}}</div>', |
110
|
|
|
'root' => '<ul{{attrs}}>{{content}}</ul>', |
111
|
|
|
'parent' => '<ul{{attrs}}>{{content}}</ul>', |
112
|
|
|
'child' => '<li{{attrs}}>{{content}}{{children}}</li>', |
113
|
|
|
'link' => '<a href="{{url}}"{{attrs}}><span>{{content}}</span></a>', |
114
|
|
|
] |
115
|
|
|
]; |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* Flags that indicates this helper is already rendering a menu. |
119
|
|
|
* |
120
|
|
|
* Used to detect loops when using callable formatters. |
121
|
|
|
* |
122
|
|
|
* @var bool |
123
|
|
|
*/ |
124
|
|
|
protected $_rendering = false; |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* Other helpers used by this helper. |
128
|
|
|
* |
129
|
|
|
* @var array |
130
|
|
|
*/ |
131
|
|
|
public $helpers = ['Menu.Link']; |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* Constructor. |
135
|
|
|
* |
136
|
|
|
* @param View $view The View this helper is being attached to |
137
|
|
|
* @param array $config Configuration settings for the helper, will have no |
138
|
|
|
* effect as configuration is set on every `render()` call |
139
|
|
|
*/ |
140
|
|
|
public function __construct(View $view, array $config = []) |
141
|
|
|
{ |
142
|
|
|
$this->_defaultConfig['formatter'] = function ($entity, $info) { |
143
|
|
|
return $this->formatter($entity, $info); |
144
|
|
|
}; |
145
|
|
|
parent::__construct($view, $config); |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Renders a nested menu. |
150
|
|
|
* |
151
|
|
|
* This methods renders a HTML menu using a `threaded` result set: |
152
|
|
|
* |
153
|
|
|
* ```php |
154
|
|
|
* // In controller: |
155
|
|
|
* $this->set('links', $this->Links->find('threaded')); |
156
|
|
|
* |
157
|
|
|
* // In view: |
158
|
|
|
* echo $this->Menu->render('links'); |
159
|
|
|
* ``` |
160
|
|
|
* |
161
|
|
|
* ### Options: |
162
|
|
|
* |
163
|
|
|
* You can pass an associative array `key => value`. Any `key` not in |
164
|
|
|
* `$_defaultConfig` will be treated as an additional attribute for the top |
165
|
|
|
* level UL (root). If `key` is in `$_defaultConfig` it will temporally |
166
|
|
|
* overwrite default configuration parameters, it will be restored to its |
167
|
|
|
* default values after rendering completes: |
168
|
|
|
* |
169
|
|
|
* - `formatter`: Callable method used when formating each item. |
170
|
|
|
* - `activeClass`: CSS class to use when an item is active (its URL matches current URL). |
171
|
|
|
* - `firstItemClass`: CSS class for the first item. |
172
|
|
|
* - `lastItemClass`: CSS class for the last item. |
173
|
|
|
* - `hasChildrenClass`: CSS class to use when an item has children. |
174
|
|
|
* - `split`: Split menu into multiple root menus (multiple UL's) |
175
|
|
|
* - `templates`: The templates you want to use for this menu. Any templates |
176
|
|
|
* will be merged on top of the already loaded templates. This option can |
177
|
|
|
* either be a filename in App/config that contains the templates you want |
178
|
|
|
* to load, or an array of templates to use. |
179
|
|
|
* |
180
|
|
|
* You can also pass a callable function as second argument which will be |
181
|
|
|
* used as formatter: |
182
|
|
|
* |
183
|
|
|
* ```php |
184
|
|
|
* echo $this->Menu->render($links, function ($link, $info) { |
185
|
|
|
* // render $item here |
186
|
|
|
* }); |
187
|
|
|
* ``` |
188
|
|
|
* |
189
|
|
|
* Formatters receives two arguments, the item being rendered as first argument |
190
|
|
|
* and information abut the item (has children, depth, etc) as second. |
191
|
|
|
* |
192
|
|
|
* You can pass the ID or slug of a menu as fist argument to render that menu's |
193
|
|
|
* links: |
194
|
|
|
* |
195
|
|
|
* ```php |
196
|
|
|
* echo $this->Menu->render('management'); |
197
|
|
|
* |
198
|
|
|
* // OR |
199
|
|
|
* |
200
|
|
|
* echo $this->Menu->render(1); |
201
|
|
|
* ``` |
202
|
|
|
* |
203
|
|
|
* @param int|string|array|\Cake\Collection\Collection $items Nested items |
204
|
|
|
* to render, given as a query result set or as an array list. Or an integer as |
205
|
|
|
* menu ID in DB to render, or a string as menu Slug in DB to render. |
206
|
|
|
* @param callable|array $config An array of HTML attributes and options as |
207
|
|
|
* described above or a callable function to use as `formatter` |
208
|
|
|
* @return string HTML |
209
|
|
|
* @throws \Cake\Error\FatalErrorException When loop invocation is detected, |
210
|
|
|
* that is, when "render()" method is invoked within a callable method when |
211
|
|
|
* rendering menus. |
212
|
|
|
*/ |
213
|
|
|
public function render($items, $config = []) |
214
|
|
|
{ |
215
|
|
|
if ($this->_rendering) { |
216
|
|
|
throw new FatalErrorException(__d('menu', 'Loop detected, MenuHelper already rendering.')); |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
$items = $this->_prepareItems($items); |
220
|
|
|
if (empty($items)) { |
221
|
|
|
return ''; |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
list($config, $attrs) = $this->_prepareOptions($config); |
225
|
|
|
$this->_rendering = true; |
226
|
|
|
$this->countItems($items); |
227
|
|
|
$this->config($config); |
228
|
|
|
|
229
|
|
|
if ($this->config('breadcrumbGuessing')) { |
230
|
|
|
$this->Link->config(['breadcrumbGuessing' => $this->config('breadcrumbGuessing')]); |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
$out = ''; |
234
|
|
|
if (intval($this->config('split')) > 1) { |
235
|
|
|
$out .= $this->_renderPart($items, $config, $attrs); |
236
|
|
|
} else { |
237
|
|
|
$out .= $this->formatTemplate('root', [ |
238
|
|
|
'attrs' => $this->templater()->formatAttributes($attrs), |
239
|
|
|
'content' => $this->_render($items) |
240
|
|
|
]); |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
if ($this->config('beautify')) { |
244
|
|
|
include_once Plugin::classPath('Menu') . 'Lib/htmLawed.php'; |
245
|
|
|
$tidy = is_bool($this->config('beautify')) ? '1t0n' : $this->config('beautify'); |
246
|
|
|
$out = htmLawed($out, compact('tidy')); |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
$this->_clear(); |
250
|
|
|
|
251
|
|
|
return $out; |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
/** |
255
|
|
|
* Default callable method (see formatter option). |
256
|
|
|
* |
257
|
|
|
* ### Valid options are: |
258
|
|
|
* |
259
|
|
|
* - `templates`: Array of templates indexed as `templateName` => `templatePattern`. |
260
|
|
|
* Temporally overwrites templates when rendering this item, after item is rendered |
261
|
|
|
* templates are restored to previous values. |
262
|
|
|
* - `childAttrs`: Array of attributes for `child` template. |
263
|
|
|
* - `class`: Array list of multiple CSS classes or a single string (will be merged |
264
|
|
|
* with auto-generated CSS; "active", "has-children", etc). |
265
|
|
|
* - `linkAttrs`: Array of attributes for the `link` template. |
266
|
|
|
* - `class`: Same as childAttrs. |
267
|
|
|
* |
268
|
|
|
* ### Information argument |
269
|
|
|
* |
270
|
|
|
* The second argument `$info` holds a series of useful values when rendering |
271
|
|
|
* each item of the menu. This values are stored as `key` => `value` array. |
272
|
|
|
* |
273
|
|
|
* - `index` (integer): Position of current item. |
274
|
|
|
* - `total` (integer): Total number of items in the menu being rendered. |
275
|
|
|
* - `depth` (integer): Item depth within the tree structure. |
276
|
|
|
* - `hasChildren` (boolean): true|false |
277
|
|
|
* - `children` (string): HTML content of rendered children for this item. |
278
|
|
|
* Empty if has no children. |
279
|
|
|
* |
280
|
|
|
* @param \Cake\ORM\Entity $item The item to render |
281
|
|
|
* @param array $info Array of useful information such as described above |
282
|
|
|
* @param array $options Additional options |
283
|
|
|
* @return string |
284
|
|
|
*/ |
285
|
|
|
public function formatter($item, array $info, array $options = []) |
286
|
|
|
{ |
287
|
|
|
if (!empty($options['templates'])) { |
288
|
|
|
$templatesBefore = $this->templates(); |
289
|
|
|
$this->templates((array)$options['templates']); |
290
|
|
|
unset($options['templates']); |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
$attrs = $this->_prepareItemAttrs($item, $info, $options); |
294
|
|
|
$return = $this->formatTemplate('child', [ |
295
|
|
|
'attrs' => $this->templater()->formatAttributes($attrs['child']), |
296
|
|
|
'content' => $this->formatTemplate('link', [ |
297
|
|
|
'url' => $this->Link->url($item->url), |
298
|
|
|
'attrs' => $this->templater()->formatAttributes($attrs['link']), |
299
|
|
|
'content' => $item->title, |
300
|
|
|
]), |
301
|
|
|
'children' => $info['children'], |
302
|
|
|
]); |
303
|
|
|
|
304
|
|
|
if (isset($templatesBefore)) { |
305
|
|
|
$this->templates($templatesBefore); |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
return $return; |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
/** |
312
|
|
|
* Counts items in menu. |
313
|
|
|
* |
314
|
|
|
* @param \Cake\ORM\Query $items Items to count |
315
|
|
|
* @return int |
316
|
|
|
*/ |
317
|
|
|
public function countItems($items) |
318
|
|
|
{ |
319
|
|
|
if ($this->_count) { |
320
|
|
|
return $this->_count; |
321
|
|
|
} |
322
|
|
|
$this->_count($items); |
323
|
|
|
|
324
|
|
|
return $this->_count; |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
/** |
328
|
|
|
* Restores the default template values built into MenuHelper. |
329
|
|
|
* |
330
|
|
|
* @return void |
331
|
|
|
*/ |
332
|
|
|
public function resetTemplates() |
333
|
|
|
{ |
334
|
|
|
$this->templates($this->_defaultConfig['templates']); |
335
|
|
|
} |
336
|
|
|
|
337
|
|
|
/** |
338
|
|
|
* Prepares options given to "render()" method. |
339
|
|
|
* |
340
|
|
|
* ### Usage: |
341
|
|
|
* |
342
|
|
|
* ```php |
343
|
|
|
* list($options, $attrs) = $this->_prepareOptions($options); |
344
|
|
|
* ``` |
345
|
|
|
* |
346
|
|
|
* @param array|callable $options Options given to `render()` |
347
|
|
|
* @return array Array with two keys: `0 => $options` sanitized and filtered |
348
|
|
|
* options array, and `1 => $attrs` list of attributes for top level UL element |
349
|
|
|
*/ |
350
|
|
|
protected function _prepareOptions($options = []) |
351
|
|
|
{ |
352
|
|
|
$attrs = []; |
353
|
|
|
if (is_callable($options)) { |
354
|
|
|
$this->config('formatter', $options); |
355
|
|
|
$options = []; |
356
|
|
|
} else { |
357
|
|
|
if (!empty($options['templates']) && is_array($options['templates'])) { |
358
|
|
|
$this->templates($options['templates']); |
359
|
|
|
unset($options['templates']); |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
foreach ($options as $key => $value) { |
363
|
|
|
if (isset($this->_defaultConfig[$key])) { |
364
|
|
|
$this->config($key, $value); |
365
|
|
|
} else { |
366
|
|
|
$attrs[$key] = $value; |
367
|
|
|
} |
368
|
|
|
} |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
return [ |
372
|
|
|
$options, |
373
|
|
|
$attrs, |
374
|
|
|
]; |
375
|
|
|
} |
376
|
|
|
|
377
|
|
|
/** |
378
|
|
|
* Prepares item's attributes for rendering. |
379
|
|
|
* |
380
|
|
|
* @param \Cake\Datasource\EntityInterface $item The item being rendered |
381
|
|
|
* @param array $info Item's rendering info |
382
|
|
|
* @param array $options Item's rendering options |
383
|
|
|
* @return array Associative array with two keys, `link` and `child` |
384
|
|
|
* @see \Menu\View\Helper\MenuHelper::formatter() |
385
|
|
|
*/ |
386
|
|
|
protected function _prepareItemAttrs($item, array $info, array $options) |
387
|
|
|
{ |
388
|
|
|
$options = Hash::merge($options, [ |
389
|
|
|
'childAttrs' => ['class' => []], |
390
|
|
|
'linkAttrs' => ['class' => []], |
391
|
|
|
]); |
392
|
|
|
$childAttrs = $options['childAttrs']; |
393
|
|
|
$linkAttrs = $options['linkAttrs']; |
394
|
|
|
|
395
|
|
|
if (is_string($childAttrs['class'])) { |
396
|
|
|
$childAttrs['class'] = [$childAttrs['class']]; |
397
|
|
|
} |
398
|
|
|
|
399
|
|
|
if (is_string($linkAttrs['class'])) { |
400
|
|
|
$linkAttrs['class'] = [$linkAttrs['class']]; |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
if ($info['index'] === 1) { |
404
|
|
|
$childAttrs['class'][] = $this->config('firstClass'); |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
if ($info['index'] === $info['total']) { |
408
|
|
|
$childAttrs['class'][] = $this->config('lastClass'); |
409
|
|
|
} |
410
|
|
|
|
411
|
|
|
if ($info['hasChildren']) { |
412
|
|
|
$childAttrs['class'][] = $this->config('hasChildrenClass'); |
413
|
|
|
if ($this->config('dropdown')) { |
414
|
|
|
$childAttrs['class'][] = 'dropdown'; |
415
|
|
|
$linkAttrs['data-toggle'] = 'dropdown'; |
416
|
|
|
} |
417
|
|
|
} |
418
|
|
|
|
419
|
|
|
if (!empty($item->description)) { |
420
|
|
|
$linkAttrs['title'] = $item->description; |
421
|
|
|
} |
422
|
|
|
|
423
|
|
|
if (!empty($item->target)) { |
424
|
|
|
$linkAttrs['target'] = $item->target; |
425
|
|
|
} |
426
|
|
|
|
427
|
|
|
if ($info['active']) { |
428
|
|
|
$childAttrs['class'][] = $this->config('activeClass'); |
429
|
|
|
$linkAttrs['class'][] = $this->config('activeClass'); |
430
|
|
|
} |
431
|
|
|
|
432
|
|
|
$id = $this->_calculateItemId($item); |
433
|
|
|
if (!empty($id)) { |
434
|
|
|
$childAttrs['class'][] = "menu-link-{$id}"; |
435
|
|
|
} |
436
|
|
|
|
437
|
|
|
$childAttrs['class'] = array_unique($childAttrs['class']); |
438
|
|
|
$linkAttrs['class'] = array_unique($linkAttrs['class']); |
439
|
|
|
|
440
|
|
|
return [ |
441
|
|
|
'link' => $linkAttrs, |
442
|
|
|
'child' => $childAttrs, |
443
|
|
|
]; |
444
|
|
|
} |
445
|
|
|
|
446
|
|
|
/** |
447
|
|
|
* Calculates an item's ID |
448
|
|
|
* |
449
|
|
|
* @param \Cake\Datasource\EntityInterface $item The item |
450
|
|
|
* @return string The ID, it may be an empty |
451
|
|
|
*/ |
452
|
|
|
protected function _calculateItemId(EntityInterface $item) |
453
|
|
|
{ |
454
|
|
|
if ($item->has('id')) { |
455
|
|
|
return $item->id; |
456
|
|
|
} |
457
|
|
|
|
458
|
|
|
if (is_array($item->url)) { |
459
|
|
|
return Inflector::slug(strtolower(implode(' ', array_values($item->url)))); |
|
|
|
|
460
|
|
|
} |
461
|
|
|
|
462
|
|
|
return Inflector::slug(strtolower($item->url)); |
|
|
|
|
463
|
|
|
} |
464
|
|
|
|
465
|
|
|
/** |
466
|
|
|
* Prepares the items (links) to be rendered as part of a menu. |
467
|
|
|
* |
468
|
|
|
* @param mixed $items As described on `render()` |
469
|
|
|
* @return mixed Collection of links to be rendered |
470
|
|
|
*/ |
471
|
|
|
protected function _prepareItems($items) |
472
|
|
|
{ |
473
|
|
|
if (is_integer($items)) { |
474
|
|
|
$id = $items; |
475
|
|
|
$cacheKey = "render({$id})"; |
476
|
|
|
$items = static::cache($cacheKey); |
477
|
|
|
|
478
|
|
|
if ($items === null) { |
479
|
|
|
$items = TableRegistry::get('Menu.MenuLinks') |
480
|
|
|
->find('threaded') |
481
|
|
|
->where(['menu_id' => $id]) |
482
|
|
|
->all(); |
483
|
|
|
static::cache($cacheKey, $items); |
484
|
|
|
} |
485
|
|
|
} elseif (is_string($items)) { |
486
|
|
|
$slug = $items; |
487
|
|
|
$cacheKey = "render({$slug})"; |
488
|
|
|
$items = static::cache($cacheKey); |
489
|
|
|
|
490
|
|
|
if ($items === null) { |
491
|
|
|
$items = []; |
492
|
|
|
$menu = TableRegistry::get('Menu.Menus') |
493
|
|
|
->find() |
494
|
|
|
->select(['id']) |
495
|
|
|
->where(['slug' => $slug]) |
496
|
|
|
->first(); |
497
|
|
|
|
498
|
|
|
if ($menu instanceof EntityInterface) { |
499
|
|
|
$items = TableRegistry::get('Menu.MenuLinks') |
500
|
|
|
->find('threaded') |
501
|
|
|
->where(['menu_id' => $menu->id]) |
502
|
|
|
->all(); |
503
|
|
|
} |
504
|
|
|
static::cache($cacheKey, $items); |
505
|
|
|
} |
506
|
|
|
} |
507
|
|
|
|
508
|
|
|
return $items; |
509
|
|
|
} |
510
|
|
|
|
511
|
|
|
/** |
512
|
|
|
* Starts rendering process of a menu's parts (when using the "split" option). |
513
|
|
|
* |
514
|
|
|
* @param mixed $items Menu links |
515
|
|
|
* @param array $options Options for the rendering process |
516
|
|
|
* @param array $attrs Menu's attributes |
517
|
|
|
* @return string |
518
|
|
|
*/ |
519
|
|
|
protected function _renderPart($items, $options, $attrs) |
520
|
|
|
{ |
521
|
|
|
if (is_object($items) && method_exists($items, 'toArray')) { |
522
|
|
|
$arrayItems = $items->toArray(); |
523
|
|
|
} else { |
524
|
|
|
$arrayItems = (array)$items; |
525
|
|
|
} |
526
|
|
|
|
527
|
|
|
$chunkOut = ''; |
528
|
|
|
$size = round(count($arrayItems) / intval($this->config('split'))); |
529
|
|
|
$chunk = array_chunk($arrayItems, $size); |
530
|
|
|
$i = 1; |
531
|
|
|
|
532
|
|
|
foreach ($chunk as $menu) { |
533
|
|
|
$chunkOut .= $this->formatTemplate('parent', [ |
534
|
|
|
'attrs' => $this->templater()->formatAttributes(['class' => "menu-part part-{$i}"]), |
535
|
|
|
'content' => $this->_render($menu, $this->config('formatter')) |
536
|
|
|
]); |
537
|
|
|
$i++; |
538
|
|
|
} |
539
|
|
|
|
540
|
|
|
return $this->formatTemplate('div', [ |
541
|
|
|
'attrs' => $this->templater()->formatAttributes($attrs), |
542
|
|
|
'content' => $chunkOut, |
543
|
|
|
]); |
544
|
|
|
} |
545
|
|
|
|
546
|
|
|
/** |
547
|
|
|
* Internal method to recursively generate the menu. |
548
|
|
|
* |
549
|
|
|
* @param \Cake\ORM\Query $items Items to render |
550
|
|
|
* @param int $depth Current iteration depth |
551
|
|
|
* @return string HTML |
552
|
|
|
*/ |
553
|
|
|
protected function _render($items, $depth = 0) |
554
|
|
|
{ |
555
|
|
|
$content = ''; |
556
|
|
|
$formatter = $this->config('formatter'); |
557
|
|
|
|
558
|
|
|
foreach ($items as $item) { |
559
|
|
|
$children = ''; |
560
|
|
|
if (is_array($item)) { |
561
|
|
|
$item = new Entity($item); |
562
|
|
|
} |
563
|
|
|
|
564
|
|
|
if ($item->has('children') && !empty($item->children) && $item->expanded) { |
565
|
|
|
$children = $this->formatTemplate('parent', [ |
566
|
|
|
'attrs' => $this->templater()->formatAttributes([ |
567
|
|
|
'class' => ($this->config('dropdown') ? 'dropdown-menu multi-level' : ''), |
568
|
|
|
'role' => 'menu' |
569
|
|
|
]), |
570
|
|
|
'content' => $this->_render($item->children, $depth + 1) |
571
|
|
|
]); |
572
|
|
|
} |
573
|
|
|
|
574
|
|
|
$this->_index++; |
575
|
|
|
$info = [ |
576
|
|
|
'index' => $this->_index, |
577
|
|
|
'total' => $this->_count, |
578
|
|
|
'active' => $this->Link->isActive($item), |
579
|
|
|
'depth' => $depth, |
580
|
|
|
'hasChildren' => !empty($children), |
581
|
|
|
'children' => $children, |
582
|
|
|
]; |
583
|
|
|
$content .= $formatter($item, $info); |
584
|
|
|
} |
585
|
|
|
|
586
|
|
|
return $content; |
587
|
|
|
} |
588
|
|
|
|
589
|
|
|
/** |
590
|
|
|
* Internal method for counting items in menu. |
591
|
|
|
* |
592
|
|
|
* This method will ignore children if parent has been marked as `do no expand`. |
593
|
|
|
* |
594
|
|
|
* @param \Cake\ORM\Query $items Items to count |
595
|
|
|
* @return int |
596
|
|
|
*/ |
597
|
|
|
protected function _count($items) |
598
|
|
|
{ |
599
|
|
|
foreach ($items as $item) { |
600
|
|
|
$this->_count++; |
601
|
|
|
$item = is_array($item) ? new Entity($item) : $item; |
602
|
|
|
|
603
|
|
|
if ($item->has('children') && !empty($item->children) && $item->expanded) { |
604
|
|
|
$this->_count($item->children); |
605
|
|
|
} |
606
|
|
|
} |
607
|
|
|
} |
608
|
|
|
|
609
|
|
|
/** |
610
|
|
|
* Clears all temporary variables used when rendering a menu, so they do not |
611
|
|
|
* interfere when rendering other menus. |
612
|
|
|
* |
613
|
|
|
* @return void |
614
|
|
|
*/ |
615
|
|
|
protected function _clear() |
616
|
|
|
{ |
617
|
|
|
$this->_index = 0; |
618
|
|
|
$this->_count = 0; |
619
|
|
|
$this->_rendering = false; |
620
|
|
|
$this->config($this->_defaultConfig); |
621
|
|
|
$this->Link->config(['breadcrumbGuessing' => $this->_defaultConfig['breadcrumbGuessing']]); |
622
|
|
|
$this->resetTemplates(); |
623
|
|
|
} |
624
|
|
|
} |
625
|
|
|
|
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.