GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — integration (#2604)
by Brendan
05:01
created

AdministrationPage   F

Complexity

Total Complexity 157

Size/Duplication

Total Lines 1243
Duplicated Lines 0.48 %

Coupling/Cohesion

Components 2
Dependencies 15

Importance

Changes 7
Bugs 0 Features 0
Metric Value
c 7
b 0
f 0
dl 6
loc 1243
rs 0.6314
wmc 157
lcom 2
cbo 15

32 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A setPageType() 0 4 3
A setBodyClass() 0 9 3
A parseContext() 0 3 1
A getContext() 0 4 1
A pageAlert() 0 14 4
B appendSubheading() 0 15 5
A insertAction() 0 21 3
B insertBreadcrumbs() 0 24 5
A insertDrawer() 0 19 3
B build() 0 127 5
C canAccessPage() 6 35 15
A getNavigationArray() 0 8 2
A __buildNavigation() 0 23 3
C buildXmlNavigation() 0 49 9
B buildSectionNavigation() 0 30 6
A __navigationFindGroupIndex() 0 10 3
C buildExtensionsNavigation() 0 59 11
A createParentNavItem() 0 12 3
B createChildNavItem() 0 18 7
B __findActiveNavigationGroup() 0 20 8
B doesAuthorHaveAccess() 0 16 10
A action() 0 4 1
B __switchboard() 0 21 5
A appendUserLinks() 0 20 1
C appendNavigation() 0 75 16
A view() 0 4 1
B appendAlert() 0 29 4
B generate() 0 30 4
A __appendBodyId() 0 21 2
B __appendBodyAttributes() 0 20 6
B sortAlerts() 0 15 6

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like AdministrationPage often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AdministrationPage, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
    /**
4
     * @package toolkit
5
     */
6
7
    /**
8
     * The AdministrationPage class represents a Symphony backend page.
9
     * It extends the HTMLPage class and unlike the Frontend, is generated
10
     * using a number XMLElement objects. Instances of this class override
11
     * the view, switchboard and action functions to construct the page. These
12
     * functions act as pseudo MVC, with the switchboard being controller,
13
     * and the view/action being the view.
14
     */
15
    class AdministrationPage extends HTMLPage
16
    {
17
        /**
18
         * An array of `Alert` objects used to display page level
19
         * messages to Symphony backend users one by one. Prior to Symphony 2.3
20
         * this variable only held a single `Alert` object.
21
         *
22
         * @var array
23
         */
24
        public $Alert = array();
25
26
        /**
27
         * As the name suggests, a `<div>` that holds the following `$Header`,
28
         * `$Contents` and `$Footer`.
29
         *
30
         * @var XMLElement
31
         */
32
        public $Wrapper = null;
33
34
        /**
35
         * A `<div>` that contains the header of a Symphony backend page, which
36
         * typically contains the Site title and the navigation.
37
         *
38
         * @var XMLElement
39
         */
40
        public $Header = null;
41
42
        /**
43
         * A `<div>` that contains the breadcrumbs, the page title and some contextual
44
         * actions (e.g. "Create new").
45
         *
46
         * @since Symphony 2.3
47
         * @var XMLElement
48
         */
49
        public $Context = null;
50
51
        /**
52
         * An object that stores the markup for the breadcrumbs and is only used
53
         * internally.
54
         *
55
         * @since Symphony 2.3
56
         * @var XMLElement
57
         */
58
        public $Breadcrumbs = null;
59
60
        /**
61
         * An array of Drawer widgets for the current page
62
         *
63
         * @since Symphony 2.3
64
         * @var array
65
         */
66
        public $Drawer = array();
67
68
        /**
69
         * A `<div>` that contains the content of a Symphony backend page.
70
         *
71
         * @var XMLElement
72
         */
73
        public $Contents = null;
74
75
        /**
76
         * An associative array of the navigation where the key is the group
77
         * index, and the value is an associative array of 'name', 'index' and
78
         * 'children'. Name is the name of the this group, index is the same as
79
         * the key and children is an associative array of navigation items containing
80
         * the keys 'link', 'name' and 'visible'. In Symphony, all navigation items
81
         * are contained within a group, and the group has no 'default' link, therefore
82
         * it is up to the children to provide the link to pages. This link should be
83
         * relative to the Symphony path, although it is possible to provide an
84
         * absolute link by providing a key, 'relative' with the value false.
85
         *
86
         * @var array
87
         */
88
        public $_navigation = array();
89
90
        /**
91
         *  An associative array describing this pages context. This
92
         *  can include the section handle, the current entry_id, the page
93
         *  name and any flags such as 'saved' or 'created'. This variable
94
         *  often provided in delegates so extensions can manipulate based
95
         *  off the current context or add new keys.
96
         *
97
         * @var array
98
         */
99
        public $_context = null;
100
101
        /**
102
         * The class attribute of the `<body>` element for this page. Defaults
103
         * to an empty string
104
         *
105
         * @var string
106
         */
107
        private $_body_class = '';
108
109
        /**
110
         * Constructor calls the parent constructor to set up
111
         * the basic HTML, Head and Body `XMLElement`'s. This function
112
         * also sets the `XMLElement` element style to be HTML, instead of XML
113
         */
114
        public function __construct()
115
        {
116
            parent::__construct();
117
118
            $this->Html->setElementStyle('html');
119
        }
120
121
        /**
122
         * Specifies the type of page that being created. This is used to
123
         * trigger various styling hooks. If your page is mainly a form,
124
         * pass 'form' as the parameter, if it's displaying a single entry,
125
         * pass 'single'. If any other parameter is passed, the 'index'
126
         * styling will be applied.
127
         *
128
         * @param string $type
129
         *  Accepts 'form' or 'single', any other `$type` will trigger 'index'
130
         *  styling.
131
         */
132
        public function setPageType($type = 'form')
133
        {
134
            $this->setBodyClass($type === 'form' || $type === 'page-single' ? 'page-single' : 'page-index');
135
        }
136
137
        /**
138
         * Setter function to set the class attribute on the `<body>` element.
139
         * This function will respect any previous classes that have been added
140
         * to this `<body>`
141
         *
142
         * @param string $class
143
         *  The string of the classname, multiple classes can be specified by
144
         *  uses a space separator
145
         */
146
        public function setBodyClass($class)
147
        {
148
            // Prevents duplicate "page-index" classes
149
            if (!isset($this->_context['page']) || !in_array('page-index', array($this->_context['page'], $class))) {
150
                $this->_body_class .= $class;
151
            }
152
153
            $this->Body->setAttribute('class', $this->_body_class);
154
        }
155
156
        /**
157
         * Given the current page `$context` and the URL path parts, parse the context for
158
         * the current page. This happens prior to the AdminPagePostCallback delegate
159
         * being fired. The `$context` is passed by reference
160
         *
161
         * @since Symphony 3.0.0
162
         * @param array $context
163
         * @param array $parts
164
         * @return void
165
         */
166
        public function parseContext(array &$context, array $parts)
167
        {
168
        }
169
170
        /**
171
         * Accessor for `$this->_context` which includes contextual information
172
         * about the current page such as the class, file location or page root.
173
         * This information varies depending on if the page is provided by an
174
         * extension, is for the publish area, is the login page or any other page
175
         *
176
         * @since Symphony 2.3
177
         * @return array
178
         */
179
        public function getContext()
180
        {
181
            return $this->_context;
182
        }
183
184
        /**
185
         * Given a `$message` and an optional `$type`, this function will
186
         * add an Alert instance into this page's `$this->Alert` property.
187
         * Since Symphony 2.3, there may be more than one `Alert` per page.
188
         * Unless the Alert is an Error, it is required the `$message` be
189
         * passed to this function.
190
         *
191
         * @param string $message
192
         *  The message to display to users
193
         * @param string $type
194
         *  An Alert constant, being `Alert::NOTICE`, `Alert::ERROR` or
195
         *  `Alert::SUCCESS`. The differing types will show the error
196
         *  in a different style in the backend. If omitted, this defaults
197
         *  to `Alert::NOTICE`.
198
         * @throws Exception
199
         */
200
        public function pageAlert($message = null, $type = Alert::NOTICE)
201
        {
202
            if (is_null($message) && $type === Alert::ERROR) {
203
                $message = __('There was a problem rendering this page. Please check the activity log for more details.');
204
            } else {
205
                $message = __($message);
206
            }
207
208
            if (strlen(trim($message)) === 0) {
209
                throw new Exception(__('A message must be supplied unless the alert is of type Alert::ERROR'));
210
            }
211
212
            $this->Alert[] = new Alert($message, $type);
213
        }
214
215
        /**
216
         * Appends the heading of this Symphony page to the Context element.
217
         * Action buttons can be provided (e.g. "Create new") as second parameter.
218
         *
219
         * @since Symphony 2.3
220
         * @param string $value
221
         *  The heading text
222
         * @param array|XMLElement|string $actions
223
         *  Some contextual actions to append to the heading, they can be provided as
224
         *  an array of XMLElements or strings. Traditionally Symphony uses this to append
225
         *  a "Create new" link to the Context div.
226
         */
227
        public function appendSubheading($value, $actions = null)
228
        {
229
            if (!is_array($actions) && $actions) { // Backward compatibility
230
                $actions = array($actions);
231
            }
232
233
            if (!empty($actions)) {
234
                foreach ($actions as $a) {
0 ignored issues
show
Bug introduced by
The expression $actions of type string|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
235
                    $this->insertAction($a);
236
                }
237
            }
238
239
            $this->Breadcrumbs->appendChild(new XMLElement('h2', $value,
240
                array('role' => 'heading', 'id' => 'symphony-subheading')));
241
        }
242
243
        /**
244
         * This function allows a user to insert an Action button to the page.
245
         * It accepts an `XMLElement` (which should be of the `Anchor` type),
246
         * an optional parameter `$prepend`, which when `true` will add this
247
         * action before any existing actions.
248
         *
249
         * @since Symphony 2.3
250
         * @see core.Widget#Anchor
251
         * @param XMLElement $action
252
         *  An Anchor element to add to the top of the page.
253
         * @param boolean $append
254
         *  If true, this will add the `$action` after existing actions, otherwise
255
         *  it will be added before existing actions. By default this is `true`,
256
         *  which will add the `$action` after current actions.
257
         */
258
        public function insertAction(XMLElement $action, $append = true)
259
        {
260
            $actions = $this->Context->getChildrenByName('ul');
261
262
            // Actions haven't be added yet, create the element
263
            if (empty($actions)) {
264
                $ul = new XMLElement('ul', null, array('class' => 'actions'));
265
                $this->Context->appendChild($ul);
266
            } else {
267
                $ul = current($actions);
268
                $this->Context->replaceChildAt(1, $ul);
269
            }
270
271
            $li = new XMLElement('li', $action);
272
273
            if ($append) {
274
                $ul->prependChild($li);
275
            } else {
276
                $ul->appendChild($li);
277
            }
278
        }
279
280
        /**
281
         * Allows developers to specify a list of nav items that build the
282
         * path to the current page or, in jargon, "breadcrumbs".
283
         *
284
         * @since Symphony 2.3
285
         * @param array $values
286
         *  An array of `XMLElement`'s or strings that compose the path. If breadcrumbs
287
         *  already exist, any new item will be appended to the rightmost part of the
288
         *  path.
289
         */
290
        public function insertBreadcrumbs(array $values)
291
        {
292
            if (empty($values)) {
293
                return;
294
            }
295
296
            if ($this->Breadcrumbs instanceof XMLElement && count($this->Breadcrumbs->getChildrenByName('nav')) === 1) {
297
                $nav = $this->Breadcrumbs->getChildrenByName('nav');
298
                $nav = $nav[0];
299
300
                $p = $nav->getChild(0);
301
            } else {
302
                $p = new XMLElement('p');
303
                $nav = new XMLElement('nav');
304
                $nav->appendChild($p);
305
306
                $this->Breadcrumbs->prependChild($nav);
307
            }
308
309
            foreach ($values as $v) {
310
                $p->appendChild($v);
311
                $p->appendChild(new XMLElement('span', '&#8250;', array('class' => 'sep')));
312
            }
313
        }
314
315
        /**
316
         * Allows a Drawer element to added to the backend page in one of three
317
         * positions, `horizontal`, `vertical-left` or `vertical-right`. The button
318
         * to trigger the visibility of the drawer will be added after existing
319
         * actions by default.
320
         *
321
         * @since Symphony 2.3
322
         * @see core.Widget#Drawer
323
         * @param XMLElement $drawer
324
         *  An XMLElement representing the drawer, use `Widget::Drawer` to construct
325
         * @param string $position
326
         *  Where `$position` can be `horizontal`, `vertical-left` or
327
         *  `vertical-right`. Defaults to `horizontal`.
328
         * @param string $button
329
         *  If not passed, a button to open/close the drawer will not be added
330
         *  to the interface. Accepts 'prepend' or 'append' values, which will
331
         *  add the button before or after existing buttons. Defaults to `prepend`.
332
         *  If any other value is passed, no button will be added.
333
         * @throws InvalidArgumentException
334
         */
335
        public function insertDrawer(XMLElement $drawer, $position = 'horizontal', $button = 'append')
336
        {
337
            $drawer->addClass($position);
338
            $drawer->setAttribute('data-position', $position);
339
            $drawer->setAttribute('role', 'complementary');
340
            $this->Drawer[$position][] = $drawer;
341
342
            if (in_array($button, array('prepend', 'append'))) {
343
                $this->insertAction(
344
                    Widget::Anchor(
345
                        $drawer->getAttribute('data-label'),
346
                        '#' . $drawer->getAttribute('id'),
347
                        null,
348
                        'button drawer ' . $position
349
                    ),
350
                    ($button === 'append' ? true : false)
351
                );
352
            }
353
        }
354
355
        /**
356
         * This function initialises a lot of the basic elements that make up a Symphony
357
         * backend page such as the default stylesheets and scripts, the navigation and
358
         * the footer. Any alerts are also appended by this function. `view()` is called to
359
         * build the actual content of the page. The `InitialiseAdminPageHead` delegate
360
         * allows extensions to add elements to the `<head>`.
361
         *
362
         * @see  view()
363
         * @uses InitialiseAdminPageHead
364
         * @param array $context
365
         *  An associative array describing this pages context. This
366
         *  can include the section handle, the current entry_id, the page
367
         *  name and any flags such as 'saved' or 'created'. This list is not exhaustive
368
         *  and extensions can add their own keys to the array.
369
         * @throws InvalidArgumentException
370
         * @throws SymphonyErrorPage
371
         */
372
        public function build(array $context = array())
373
        {
374
            $this->_context = $context;
375
376
            if (!$this->canAccessPage()) {
377
                Administration::instance()->throwCustomError(
378
                    __('You are not authorised to access this page.'),
379
                    __('Access Denied'),
380
                    Page::HTTP_STATUS_UNAUTHORIZED
381
                );
382
            }
383
384
            $this->Html->setDTD('<!DOCTYPE html>');
385
            $this->Html->setAttribute('lang', Lang::get());
386
            $this->addElementToHead(new XMLElement('meta', null, array('charset' => 'UTF-8')), 0);
387
            $this->addElementToHead(new XMLElement('meta', null,
388
                array('http-equiv' => 'X-UA-Compatible', 'content' => 'IE=edge,chrome=1')), 1);
389
            $this->addElementToHead(new XMLElement('meta', null,
390
                array('name' => 'viewport', 'content' => 'width=device-width, initial-scale=1')), 2);
391
392
            // Add styles
393
            $this->addStylesheetToHead(ASSETS_URL . '/css/symphony.min.css', 'screen', 2, false);
394
395
            // Calculate timezone offset from UTC
396
            $timezone = new DateTimeZone(Symphony::Configuration()->get('timezone', 'region'));
397
            $datetime = new DateTime('now', $timezone);
398
            $timezoneOffset = intval($timezone->getOffset($datetime)) / 60;
399
400
            // Add scripts
401
            $environment = array(
402
403
                'root' => URL,
404
                'symphony' => SYMPHONY_URL,
405
                'path' => '/' . Symphony::Configuration()->get('admin-path', 'symphony'),
406
                'route' => getCurrentPage(),
407
                'version' => Symphony::Configuration()->get('version', 'symphony'),
408
                'lang' => Lang::get(),
409
                'user' => array(
410
411
                    'fullname' => Symphony::Author()->getFullName(),
412
                    'name' => Symphony::Author()->get('first_name'),
413
                    'type' => Symphony::Author()->get('user_type'),
414
                    'id' => Symphony::Author()->get('id')
415
                ),
416
                'datetime' => array(
417
418
                    'formats' => DateTimeObj::getDateFormatMappings(),
419
                    'timezone-offset' => $timezoneOffset
420
                ),
421
                'env' => array_merge(
422
423
                    array('page-namespace' => Symphony::getPageNamespace()),
424
                    $this->_context
425
                )
426
            );
427
428
            $this->addElementToHead(
429
                new XMLElement('script', json_encode($environment), array(
430
                    'type' => 'application/json',
431
                    'id' => 'environment'
432
                )),
433
                4
434
            );
435
436
            $this->addScriptToHead(ASSETS_URL . '/js/symphony.min.js', 6, false);
437
438
            // Initialise page containers
439
            $this->Wrapper = new XMLElement('div', null, array('id' => 'wrapper'));
440
            $this->Header = new XMLElement('header', null, array('id' => 'header'));
441
            $this->Context = new XMLElement('div', null, array('id' => 'context'));
442
            $this->Breadcrumbs = new XMLElement('div', null, array('id' => 'breadcrumbs'));
443
            $this->Contents = new XMLElement('div', null, array('id' => 'contents', 'role' => 'main'));
444
            $this->Form = Widget::Form(Administration::instance()->getCurrentPageURL(), 'post', null, null,
445
                array('role' => 'form'));
446
447
            /**
448
             * Allows developers to insert items into the page HEAD. Use
449
             * `Administration::instance()->Page` for access to the page object.
450
             *
451
             * @since In Symphony 2.3.2 this delegate was renamed from
452
             *  `InitaliseAdminPageHead` to the correct spelling of
453
             *  `InitialiseAdminPageHead`. The old delegate is supported
454
             *  until Symphony 3.0.0
455
             *
456
             * @delegate InitialiseAdminPageHead
457
             * @param string $context
458
             *  '/backend/'
459
             */
460
            Symphony::ExtensionManager()->notifyMembers('InitialiseAdminPageHead', '/backend/');
461
            Symphony::ExtensionManager()->notifyMembers('InitaliseAdminPageHead', '/backend/');
462
463
            $this->addHeaderToPage('Content-Type', 'text/html; charset=UTF-8');
464
            $this->addHeaderToPage('Cache-Control', 'no-cache, must-revalidate, max-age=0');
465
            $this->addHeaderToPage('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT');
466
467
            // If not set by another extension, lock down the backend
468
            if (!array_key_exists('x-frame-options', $this->headers())) {
469
                $this->addHeaderToPage('X-Frame-Options', 'SAMEORIGIN');
470
            }
471
472
            if (!array_key_exists('access-control-allow-origin', $this->headers())) {
473
                $this->addHeaderToPage('Access-Control-Allow-Origin', URL);
474
            }
475
476
            if (isset($_REQUEST['action'])) {
477
                $this->action();
478
                Symphony::Profiler()->sample('Page action run', PROFILE_LAP);
479
            }
480
481
            $h1 = new XMLElement('h1');
482
            $h1->appendChild(Widget::Anchor(Symphony::Configuration()->get('sitename', 'general'),
483
                rtrim(URL, '/') . '/'));
484
            $this->Header->appendChild($h1);
485
486
            $this->appendUserLinks();
487
            $this->appendNavigation();
488
489
            // Add Breadcrumbs
490
            $this->Context->prependChild($this->Breadcrumbs);
491
            $this->Contents->appendChild($this->Form);
492
493
            $this->view();
494
495
            $this->appendAlert();
496
497
            Symphony::Profiler()->sample('Page content created', PROFILE_LAP);
498
        }
499
500
        /**
501
         * Checks the current Symphony Author can access the current page.
502
         * This check uses the `ASSETS . /xml/navigation.xml` file to determine
503
         * if the current page (or the current page namespace) can be viewed
504
         * by the currently logged in Author.
505
         *
506
         * @link http://github.com/symphonycms/symphony-2/blob/master/symphony/assets/xml/navigation.xml
507
         * @return boolean
508
         *  True if the Author can access the current page, false otherwise
509
         */
510
        public function canAccessPage()
511
        {
512
            $nav = $this->getNavigationArray();
513
            $page = '/' . trim(getCurrentPage(), '/') . '/';
514
515
            $page_limit = 'author';
516
517
            foreach ($nav as $item) {
518
                if (
519
                    // If page directly matches one of the children
520
                    General::in_array_multi($page, $item['children'])
521
                    // If the page namespace matches one of the children (this will usually drop query
522
                    // string parameters such as /edit/1/)
523
                    || General::in_array_multi(Symphony::getPageNamespace() . '/', $item['children'])
524
                ) {
525
                    if (is_array($item['children'])) {
526
                        foreach ($item['children'] as $c) {
527 View Code Duplication
                            if ($c['link'] === $page && isset($c['limit'])) {
528
                                $page_limit = $c['limit'];
529
                            }
530
                        }
531
                    }
532
533
                    if (isset($item['limit']) && $page_limit !== 'primary') {
534
                        if ($page_limit === 'author' && $item['limit'] === 'developer') {
535
                            $page_limit = 'developer';
536
                        }
537
                    }
538 View Code Duplication
                } elseif (isset($item['link']) && $page === $item['link'] && isset($item['limit'])) {
539
                    $page_limit = $item['limit'];
540
                }
541
            }
542
543
            return $this->doesAuthorHaveAccess($page_limit);
544
        }
545
546
        /**
547
         * Returns the `$_navigation` variable of this Page. If it is empty,
548
         * it will be built by `__buildNavigation`
549
         *
550
         * @see __buildNavigation()
551
         * @return array
552
         */
553
        public function getNavigationArray()
554
        {
555
            if (empty($this->_navigation)) {
556
                $this->__buildNavigation();
557
            }
558
559
            return $this->_navigation;
560
        }
561
562
        /**
563
         * This function populates the `$_navigation` array with an associative array
564
         * of all the navigation groups and their links. Symphony only supports one
565
         * level of navigation, so children links cannot have children links. The default
566
         * Symphony navigation is found in the `ASSETS/xml/navigation.xml` folder. This is
567
         * loaded first, and then the Section navigation is built, followed by the Extension
568
         * navigation. Additionally, this function will set the active group of the navigation
569
         * by checking the current page against the array of links.
570
         *
571
         * @link https://github.com/symphonycms/symphony-2/blob/master/symphony/assets/xml/navigation.xml
572
         * @link https://github.com/symphonycms/symphony-2/blob/master/symphony/lib/toolkit/class.extension.php
573
         */
574
        public function __buildNavigation()
575
        {
576
            $nav = array();
577
578
            $this->buildXmlNavigation($nav);
579
            $this->buildSectionNavigation($nav);
580
            $this->buildExtensionsNavigation($nav);
581
582
            $pageCallback = Administration::instance()->getPageCallback();
583
584
            $pageRoot = $pageCallback['pageroot'] . (isset($pageCallback['context'][0]) ? $pageCallback['context'][0] . '/' : '');
585
            $found = self::__findActiveNavigationGroup($nav, $pageRoot);
586
587
            // Normal searches failed. Use a regular expression using the page root. This is less
588
            // efficient and should never really get invoked unless something weird is going on
589
            if (!$found) {
590
                self::__findActiveNavigationGroup($nav, '/^' . str_replace('/', '\/', $pageCallback['pageroot']) . '/i',
591
                    true);
592
            }
593
594
            ksort($nav);
595
            $this->_navigation = $nav;
596
        }
597
598
        /**
599
         * This method fills the `$nav` array with value
600
         * from the `ASSETS/xml/navigation.xml` file
601
         *
602
         * @link http://github.com/symphonycms/symphony-2/blob/master/symphony/assets/xml/navigation.xml
603
         *
604
         * @since Symphony 2.3.2
605
         *
606
         * @param array $nav
607
         *  The navigation array that will receive nav nodes
608
         */
609
        private function buildXmlNavigation(&$nav)
610
        {
611
            $xml = simplexml_load_file(ASSETS . '/xml/navigation.xml');
612
613
            // Loop over the default Symphony navigation file, converting
614
            // it into an associative array representation
615
            foreach ($xml->xpath('/navigation/group') as $n) {
616
                $index = (string)$n->attributes()->index;
617
                $children = $n->xpath('children/item');
618
                $content = $n->attributes();
619
620
                // If the index is already set, increment the index and check again.
621
                // Rinse and repeat until the index is not set.
622
                if (isset($nav[$index])) {
623
                    do {
624
                        $index++;
625
                    } while (isset($nav[$index]));
626
                }
627
628
                $nav[$index] = array(
629
                    'name' => __(strval($content->name)),
630
                    'type' => 'structure',
631
                    'index' => $index,
632
                    'children' => array()
633
                );
634
635
                if (strlen(trim((string)$content->limit)) > 0) {
636
                    $nav[$index]['limit'] = (string)$content->limit;
637
                }
638
639
                if (count($children) > 0) {
640
                    foreach ($children as $child) {
641
                        $item = array(
642
                            'link' => (string)$child->attributes()->link,
643
                            'name' => __(strval($child->attributes()->name)),
644
                            'visible' => ((string)$child->attributes()->visible === 'no' ? 'no' : 'yes'),
645
                        );
646
647
                        $limit = (string)$child->attributes()->limit;
648
649
                        if (strlen(trim($limit)) > 0) {
650
                            $item['limit'] = $limit;
651
                        }
652
653
                        $nav[$index]['children'][] = $item;
654
                    }
655
                }
656
            }
657
        }
658
659
        /**
660
         * This method fills the `$nav` array with value
661
         * from each Section
662
         *
663
         * @since Symphony 2.3.2
664
         *
665
         * @param array $nav
666
         *  The navigation array that will receive nav nodes
667
         */
668
        private function buildSectionNavigation(&$nav)
669
        {
670
            // Build the section navigation, grouped by their navigation groups
671
            $sections = SectionManager::fetch(null, 'asc', 'sortorder');
672
673
            if (is_array($sections) && !empty($sections)) {
674
                foreach ($sections as $s) {
675
                    $group_index = self::__navigationFindGroupIndex($nav, $s->get('navigation_group'));
676
677
                    if ($group_index === false) {
678
                        $group_index = General::array_find_available_index($nav, 0);
679
680
                        $nav[$group_index] = array(
681
                            'name' => $s->get('navigation_group'),
682
                            'type' => 'content',
683
                            'index' => $group_index,
684
                            'children' => array()
685
                        );
686
                    }
687
688
                    $nav[$group_index]['children'][] = array(
689
                        'link' => '/publish/' . $s->get('handle') . '/',
690
                        'name' => $s->get('name'),
691
                        'type' => 'section',
692
                        'section' => array('id' => $s->get('id'), 'handle' => $s->get('handle')),
693
                        'visible' => ($s->get('hidden') === 'no' ? 'yes' : 'no')
694
                    );
695
                }
696
            }
697
        }
698
699
        /**
700
         * Given an associative array representing the navigation, and a group,
701
         * this function will attempt to return the index of the group in the navigation
702
         * array. If it is found, it will return the index, otherwise it will return false.
703
         *
704
         * @param array $nav
705
         *  An associative array of the navigation where the key is the group
706
         *  index, and the value is an associative array of 'name', 'index' and
707
         *  'children'. Name is the name of the this group, index is the same as
708
         *  the key and children is an associative array of navigation items containing
709
         *  the keys 'link', 'name' and 'visible'. The 'haystack'.
710
         * @param string $group
711
         *  The group name to find, the 'needle'.
712
         * @return integer|boolean
713
         *  If the group is found, the index will be returned, otherwise false.
714
         */
715
        private static function __navigationFindGroupIndex(array $nav, $group)
716
        {
717
            foreach ($nav as $index => $item) {
718
                if ($item['name'] === $group) {
719
                    return $index;
720
                }
721
            }
722
723
            return false;
724
        }
725
726
        /**
727
         * This method fills the `$nav` array with value
728
         * from each Extension's `fetchNavigation` method
729
         *
730
         * @since Symphony 2.3.2
731
         *
732
         * @param array $nav
733
         *  The navigation array that will receive nav nodes
734
         * @throws Exception
735
         * @throws SymphonyErrorPage
736
         */
737
        private function buildExtensionsNavigation(&$nav)
738
        {
739
            // Loop over all the installed extensions to add in other navigation items
740
            $extensions = Symphony::ExtensionManager()->listInstalledHandles();
741
742
            foreach ($extensions as $e) {
743
                $extension = Symphony::ExtensionManager()->getInstance($e);
744
                $extension_navigation = $extension->fetchNavigation();
745
746
                if (is_array($extension_navigation) && !empty($extension_navigation)) {
747
                    foreach ($extension_navigation as $item) {
748
                        $type = isset($item['children']) ? Extension::NAV_GROUP : Extension::NAV_CHILD;
749
750
                        switch ($type) {
751
                            case Extension::NAV_GROUP:
752
                                $index = General::array_find_available_index($nav, $item['location']);
753
754
                                // Actual group
755
                                $nav[$index] = self::createParentNavItem($index, $item);
756
757
                                // Render its children
758
                                foreach ($item['children'] as $child) {
759
                                    $nav[$index]['children'][] = self::createChildNavItem($child, $e);
760
                                }
761
762
                                break;
763
764
                            case Extension::NAV_CHILD:
765
                                if (!is_numeric($item['location'])) {
766
                                    // is a navigation group
767
                                    $group_name = $item['location'];
768
                                    $group_index = self::__navigationFindGroupIndex($nav, $item['location']);
769
                                } else {
770
                                    // is a legacy numeric index
771
                                    $group_index = $item['location'];
772
                                }
773
774
                                $child = self::createChildNavItem($item, $e);
775
776
                                if ($group_index === false) {
777
                                    $group_index = General::array_find_available_index($nav, 0);
778
779
                                    $nav_parent = self::createParentNavItem($group_index, $item);
780
                                    $nav_parent['name'] = $group_name;
0 ignored issues
show
Bug introduced by
The variable $group_name does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
781
                                    $nav_parent['children'] = array($child);
782
783
                                    // add new navigation group
784
                                    $nav[$group_index] = $nav_parent;
785
                                } else {
786
                                    // add new location by index
787
                                    $nav[$group_index]['children'][] = $child;
788
                                }
789
790
                                break;
791
                        }
792
                    }
793
                }
794
            }
795
        }
796
797
        /**
798
         * This function builds out a navigation menu item for parents. Parents display
799
         * in the top level navigation of the backend and may have children (dropdown menus)
800
         *
801
         * @since Symphony 2.5.1
802
         * @param integer $index
803
         * @param array $item
804
         * @return array
805
         */
806
        private static function createParentNavItem($index, $item)
807
        {
808
            $nav_item = array(
809
                'name' => $item['name'],
810
                'type' => isset($item['type']) ? $item['type'] : 'structure',
811
                'index' => $index,
812
                'children' => array(),
813
                'limit' => isset($item['limit']) ? $item['limit'] : null
814
            );
815
816
            return $nav_item;
817
        }
818
819
        /**
820
         * This function builds out a navigation menu item for children. Children
821
         * live under a parent navigation item and are shown on hover.
822
         *
823
         * @since Symphony 2.5.1
824
         * @param array $item
825
         * @param string $extension_handle
826
         * @return array
827
         */
828
        private static function createChildNavItem($item, $extension_handle)
829
        {
830
            if (!isset($item['relative']) || $item['relative'] === true) {
831
                $link = '/extension/' . $extension_handle . '/' . ltrim($item['link'], '/');
832
            } else {
833
                $link = '/' . ltrim($item['link'], '/');
834
            }
835
836
            $nav_item = array(
837
                'link' => $link,
838
                'name' => $item['name'],
839
                'visible' => (isset($item['visible']) && $item['visible'] === 'no') ? 'no' : 'yes',
840
                'limit' => isset($item['limit']) ? $item['limit'] : null,
841
                'target' => isset($item['target']) ? $item['target'] : null
842
            );
843
844
            return $nav_item;
845
        }
846
847
        // Errors first, success next, then notices.
848
849
        /**
850
         * Given the navigation array, this function will loop over all the items
851
         * to determine which is the 'active' navigation group, or in other words,
852
         * what group best represents the current page `$this->Author` is viewing.
853
         * This is done by checking the current page's link against all the links
854
         * provided in the `$nav`, and then flagging the group of the found link
855
         * with an 'active' CSS class. The current page's link omits any flags or
856
         * URL parameters and just uses the root page URL.
857
         *
858
         * @param array $nav
859
         *  An associative array of the navigation where the key is the group
860
         *  index, and the value is an associative array of 'name', 'index' and
861
         *  'children'. Name is the name of the this group, index is the same as
862
         *  the key and children is an associative array of navigation items containing
863
         *  the keys 'link', 'name' and 'visible'. The 'haystack'. This parameter is passed
864
         *  by reference to this function.
865
         * @param string $pageroot
866
         *  The current page the Author is the viewing, minus any flags or URL
867
         *  parameters such as a Symphony object ID. eg. Section ID, Entry ID. This
868
         *  parameter is also be a regular expression, but this is highly unlikely.
869
         * @param boolean $pattern
870
         *  If set to true, the `$pageroot` represents a regular expression which will
871
         *  determine if the active navigation item
872
         * @return boolean
873
         *  Returns true if an active link was found, false otherwise. If true, the
874
         *  navigation group of the active link will be given the CSS class 'active'
875
         */
876
        private static function __findActiveNavigationGroup(array &$nav, $pageroot, $pattern = false)
877
        {
878
            foreach ($nav as $index => $contents) {
879
                if (is_array($contents['children']) && !empty($contents['children'])) {
880
                    foreach ($contents['children'] as $item) {
881
                        if ($pattern && preg_match($pageroot, $item['link'])) {
882
                            $nav[$index]['class'] = 'active';
883
884
                            return true;
885
                        } elseif ($item['link'] === $pageroot) {
886
                            $nav[$index]['class'] = 'active';
887
888
                            return true;
889
                        }
890
                    }
891
                }
892
            }
893
894
            return false;
895
        }
896
897
        /**
898
         * Given the limit of the current navigation item or page, this function
899
         * returns if the current Author can access that item or not.
900
         *
901
         * @since Symphony 2.5.1
902
         * @param string $item_limit
903
         * @return boolean
904
         */
905
        public function doesAuthorHaveAccess($item_limit = null)
906
        {
907
            $can_access = false;
908
909
            if (!isset($item_limit) || $item_limit === 'author') {
910
                $can_access = true;
911
            } elseif ($item_limit === 'developer' && Symphony::Author()->isDeveloper()) {
912
                $can_access = true;
913
            } elseif ($item_limit === 'manager' && (Symphony::Author()->isManager() || Symphony::Author()->isDeveloper())) {
914
                $can_access = true;
915
            } elseif ($item_limit === 'primary' && Symphony::Author()->isPrimaryAccount()) {
916
                $can_access = true;
917
            }
918
919
            return $can_access;
920
        }
921
922
        /**
923
         * This function is called when `$_REQUEST` contains a key of 'action'.
924
         * Any logic that needs to occur immediately for the action to complete
925
         * should be contained within this function. By default this calls the
926
         * `__switchboard` with the type set to 'action'.
927
         *
928
         * @see __switchboard()
929
         */
930
        public function action()
931
        {
932
            $this->__switchboard('action');
933
        }
934
935
        /**
936
         * The `__switchboard` function acts as a controller to display content
937
         * based off the $type. By default, the `$type` is 'view' but it can be set
938
         * also set to 'action'. The `$type` is prepended by __ and the context is
939
         * append to the $type to create the name of the function that will provide
940
         * that logic. For example, if the $type was action and the context of the
941
         * current page was new, the resulting function to be called would be named
942
         * `__actionNew()`. If an action function is not provided by the Page, this function
943
         * returns nothing, however if a view function is not provided, a 404 page
944
         * will be returned.
945
         *
946
         * @param string $type
947
         *  Either 'view' or 'action', by default this will be 'view'
948
         * @throws SymphonyErrorPage
949
         */
950
        public function __switchboard($type = 'view')
951
        {
952
            if (empty($this->_context)) {
953
                $context = 'index';
954
            } else {
955
                $context = current($this->_context);
956
            }
957
958
            $function = ($type === 'action' ? '__action' : '__view') . ucfirst($context);
959
960
            if (!method_exists($this, $function)) {
961
                // If there is no action function, just return without doing anything
962
                if ($type === 'action') {
963
                    return;
964
                }
965
966
                Administration::instance()->errorPageNotFound();
967
            }
968
969
            $this->$function(null);
970
        }
971
972
        /**
973
         * Creates the Symphony footer for an Administration page. By default
974
         * this includes the installed Symphony version and the currently logged
975
         * in Author. A delegate is provided to allow extensions to manipulate the
976
         * footer HTML, which is an XMLElement of a `<ul>` element.
977
         * Since Symphony 2.3, it no longer uses the `AddElementToFooter` delegate.
978
         */
979
        public function appendUserLinks()
980
        {
981
            $ul = new XMLElement('ul', null, array('id' => 'session'));
982
983
            $li = new XMLElement('li');
984
            $li->appendChild(
985
                Widget::Anchor(
986
                    Symphony::Author()->getFullName(),
987
                    SYMPHONY_URL . '/system/authors/edit/' . Symphony::Author()->get('id') . '/'
988
                )
989
            );
990
            $ul->appendChild($li);
991
992
            $li = new XMLElement('li');
993
            $li->appendChild(Widget::Anchor(__('Log out'), SYMPHONY_URL . '/logout/', null, null, null,
994
                array('accesskey' => 'l')));
995
            $ul->appendChild($li);
996
997
            $this->Header->appendChild($ul);
998
        }
999
1000
        /**
1001
         * This function will append the Navigation to the AdministrationPage.
1002
         * It fires a delegate, NavigationPreRender, to allow extensions to manipulate
1003
         * the navigation. Extensions should not use this to add their own navigation,
1004
         * they should provide the navigation through their fetchNavigation function.
1005
         * Note with the Section navigation groups, if there is only one section in a group
1006
         * and that section is set to visible, the group will not appear in the navigation.
1007
         *
1008
         * @uses NavigationPreRender
1009
         * @see  getNavigationArray()
1010
         * @see  toolkit.Extension#fetchNavigation()
1011
         */
1012
        public function appendNavigation()
1013
        {
1014
            $nav = $this->getNavigationArray();
1015
1016
            /**
1017
             * Immediately before displaying the admin navigation. Provided with the
1018
             * navigation array. Manipulating it will alter the navigation for all pages.
1019
             *
1020
             * @delegate NavigationPreRender
1021
             * @param string $context
1022
             *  '/backend/'
1023
             * @param array $nav
1024
             *  An associative array of the current navigation, passed by reference
1025
             */
1026
            Symphony::ExtensionManager()->notifyMembers('NavigationPreRender', '/backend/',
1027
                array('navigation' => &$nav));
1028
1029
            $navElement = new XMLElement('nav', null, array('id' => 'nav', 'role' => 'navigation'));
1030
            $contentNav = new XMLElement('ul', null, array('class' => 'content', 'role' => 'menubar'));
1031
            $structureNav = new XMLElement('ul', null, array('class' => 'structure', 'role' => 'menubar'));
1032
1033
            foreach ($nav as $n) {
1034
                if (isset($n['visible']) && $n['visible'] === 'no') {
1035
                    continue;
1036
                }
1037
1038
                if ($this->doesAuthorHaveAccess($n['limit'])) {
1039
                    $xGroup = new XMLElement('li', $n['name'], array('role' => 'presentation'));
1040
1041
                    if (isset($n['class']) && trim($n['name']) !== '') {
1042
                        $xGroup->setAttribute('class', $n['class']);
1043
                    }
1044
1045
                    $hasChildren = false;
1046
                    $xChildren = new XMLElement('ul', null, array('role' => 'menu'));
1047
1048
                    if (is_array($n['children']) && !empty($n['children'])) {
1049
                        foreach ($n['children'] as $c) {
1050
                            // adapt for Yes and yes
1051
                            if (strtolower($c['visible']) !== 'yes') {
1052
                                continue;
1053
                            }
1054
1055
                            if ($this->doesAuthorHaveAccess($c['limit'])) {
1056
                                $xChild = new XMLElement('li');
1057
                                $xChild->setAttribute('role', 'menuitem');
1058
                                $linkChild = Widget::Anchor($c['name'], SYMPHONY_URL . $c['link']);
1059
                                if (isset($c['target'])) {
1060
                                    $linkChild->setAttribute('target', $c['target']);
1061
                                }
1062
                                $xChild->appendChild($linkChild);
1063
                                $xChildren->appendChild($xChild);
1064
                                $hasChildren = true;
1065
                            }
1066
                        }
1067
1068
                        if ($hasChildren) {
1069
                            $xGroup->setAttribute('aria-haspopup', 'true');
1070
                            $xGroup->appendChild($xChildren);
1071
1072
                            if ($n['type'] === 'content') {
1073
                                $contentNav->appendChild($xGroup);
1074
                            } elseif ($n['type'] === 'structure') {
1075
                                $structureNav->prependChild($xGroup);
1076
                            }
1077
                        }
1078
                    }
1079
                }
1080
            }
1081
1082
            $navElement->appendChild($contentNav);
1083
            $navElement->appendChild($structureNav);
1084
            $this->Header->appendChild($navElement);
1085
            Symphony::Profiler()->sample('Navigation Built', PROFILE_LAP);
1086
        }
1087
1088
        /**
1089
         * Called to build the content for the page. This function immediately calls
1090
         * `__switchboard()` which acts a bit of a controller to show content based on
1091
         * off a type, such as 'view' or 'action'. `AdministrationPages` can override this
1092
         * function to just display content if they do not need the switchboard functionality
1093
         *
1094
         * @see __switchboard()
1095
         */
1096
        public function view()
1097
        {
1098
            $this->__switchboard();
1099
        }
1100
1101
        /**
1102
         * If `$this->Alert` is set, it will be added to this page. The
1103
         * `AppendPageAlert` delegate is fired to allow extensions to provide their
1104
         * their own Alert messages for this page. Since Symphony 2.3, there may be
1105
         * more than one `Alert` per page. Alerts are displayed in the order of
1106
         * severity, with Errors first, then Success alerts followed by Notices.
1107
         *
1108
         * @uses AppendPageAlert
1109
         */
1110
        public function appendAlert()
1111
        {
1112
            /**
1113
             * Allows for appending of alerts. Administration::instance()->Page->Alert is way to tell what
1114
             * is currently in the system
1115
             *
1116
             * @delegate AppendPageAlert
1117
             * @param string $context
1118
             *  '/backend/'
1119
             */
1120
            Symphony::ExtensionManager()->notifyMembers('AppendPageAlert', '/backend/');
1121
1122
1123
            if (!is_array($this->Alert) || empty($this->Alert)) {
1124
                return;
1125
            }
1126
1127
            usort($this->Alert, array($this, 'sortAlerts'));
1128
1129
            // Using prependChild ruins our order (it's backwards, but with most
1130
            // recent notices coming after oldest notices), so reversing the array
1131
            // fixes this. We need to prepend so that without Javascript the notices
1132
            // are at the top of the markup. See #1312
1133
            $this->Alert = array_reverse($this->Alert);
1134
1135
            foreach ($this->Alert as $alert) {
1136
                $this->Header->prependChild($alert->asXML());
1137
            }
1138
        }
1139
1140
        /**
1141
         * Appends the `$this->Header`, `$this->Context` and `$this->Contents`
1142
         * to `$this->Wrapper` before adding the ID and class attributes for
1143
         * the `<body>` element. This function will also place any Drawer elements
1144
         * in their relevant positions in the page. After this has completed the
1145
         * parent `generate()` is called which will convert the `XMLElement`'s
1146
         * into strings ready for output.
1147
         *
1148
         * @see core.HTMLPage#generate()
1149
         * @param null $page
1150
         * @return string
1151
         */
1152
        public function generate($page = null)
1153
        {
1154
            $this->Wrapper->appendChild($this->Header);
1155
1156
            // Add horizontal drawers (inside #context)
1157
            if (isset($this->Drawer['horizontal'])) {
1158
                $this->Context->appendChildArray($this->Drawer['horizontal']);
1159
            }
1160
1161
            $this->Wrapper->appendChild($this->Context);
1162
1163
            // Add vertical-left drawers (between #context and #contents)
1164
            if (isset($this->Drawer['vertical-left'])) {
1165
                $this->Contents->appendChildArray($this->Drawer['vertical-left']);
1166
            }
1167
1168
            // Add vertical-right drawers (after #contents)
1169
            if (isset($this->Drawer['vertical-right'])) {
1170
                $this->Contents->appendChildArray($this->Drawer['vertical-right']);
1171
            }
1172
1173
            $this->Wrapper->appendChild($this->Contents);
1174
1175
            $this->Body->appendChild($this->Wrapper);
1176
1177
            $this->__appendBodyId();
1178
            $this->__appendBodyAttributes($this->_context);
1179
1180
            return parent::generate($page);
1181
        }
1182
1183
        /**
1184
         * Uses this pages PHP classname as the `<body>` ID attribute.
1185
         * This function removes 'content' from the start of the classname
1186
         * and converts all uppercase letters to lowercase and prefixes them
1187
         * with a hyphen.
1188
         */
1189
        private function __appendBodyId()
1190
        {
1191
            // trim "content" from beginning of class name
1192
            $body_id = preg_replace("/^content/", '', get_class($this));
1193
1194
            // lowercase any uppercase letters and prefix with a hyphen
1195
            $body_id = trim(
1196
                preg_replace_callback(
1197
                    "/([A-Z])/",
1198
                    function ($id) {
1199
                        return "-" . strtolower($id[0]);
1200
                    },
1201
                    $body_id
1202
                ),
1203
                '-'
1204
            );
1205
1206
            if (!empty($body_id)) {
1207
                $this->Body->setAttribute('id', $body_id);
1208
            }
1209
        }
1210
1211
        /**
1212
         * Given the context of the current page, which is an associative
1213
         * array, this function will append the values to the page's body as
1214
         * data attributes. If an context value is numeric it will be given
1215
         * the key 'id' otherwise all attributes will be prefixed by the context key.
1216
         *
1217
         * If the context value is an array, it will be JSON encoded.
1218
         *
1219
         * @param array $context
1220
         */
1221
        private function __appendBodyAttributes(array $context = array())
1222
        {
1223
            foreach ($context as $key => $value) {
1224
                if (is_numeric($value)) {
1225
                    $key = 'id';
1226
1227
                    // Add prefixes to all context values by making the
1228
                    // class be {key}-{value}. #1397 ^BA
1229
                } elseif (!is_numeric($key) && isset($value)) {
1230
                    $key = str_replace('_', '-', $key);
1231
                }
1232
1233
                // JSON encode any array values
1234
                if (is_array($value)) {
1235
                    $value = json_encode($value);
1236
                }
1237
1238
                $this->Body->setAttribute('data-' . $key, $value);
1239
            }
1240
        }
1241
1242
        public function sortAlerts($a, $b)
1243
        {
1244
            if ($a->{'type'} === $b->{'type'}) {
1245
                return 0;
1246
            }
1247
1248
            if (
1249
                ($a->{'type'} === Alert::ERROR && $a->{'type'} !== $b->{'type'})
1250
                || ($a->{'type'} === Alert::SUCCESS && $b->{'type'} === Alert::NOTICE)
1251
            ) {
1252
                return -1;
1253
            }
1254
1255
            return 1;
1256
        }
1257
    }
1258