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
Push — integration ( 45cc9f...98bc42 )
by Brendan
05:52
created

AdministrationPage   F

Complexity

Total Complexity 157

Size/Duplication

Total Lines 1222
Duplicated Lines 0.49 %

Coupling/Cohesion

Components 2
Dependencies 15

Importance

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

32 Methods

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

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
 * The AdministrationPage class represents a Symphony backend page.
8
 * It extends the HTMLPage class and unlike the Frontend, is generated
9
 * using a number XMLElement objects. Instances of this class override
10
 * the view, switchboard and action functions to construct the page. These
11
 * functions act as pseudo MVC, with the switchboard being controller,
12
 * and the view/action being the view.
13
 */
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
     * @var array
22
     */
23
    public $Alert = array();
24
25
    /**
26
     * As the name suggests, a `<div>` that holds the following `$Header`,
27
     * `$Contents` and `$Footer`.
28
     * @var XMLElement
29
     */
30
    public $Wrapper = null;
31
32
    /**
33
     * A `<div>` that contains the header of a Symphony backend page, which
34
     * typically contains the Site title and the navigation.
35
     * @var XMLElement
36
     */
37
    public $Header = null;
38
39
    /**
40
     * A `<div>` that contains the breadcrumbs, the page title and some contextual
41
     * actions (e.g. "Create new").
42
     * @since Symphony 2.3
43
     * @var XMLElement
44
     */
45
    public $Context = null;
46
47
    /**
48
     * An object that stores the markup for the breadcrumbs and is only used
49
     * internally.
50
     * @since Symphony 2.3
51
     * @var XMLElement
52
     */
53
    public $Breadcrumbs = null;
54
55
    /**
56
     * An array of Drawer widgets for the current page
57
     * @since Symphony 2.3
58
     * @var array
59
     */
60
    public $Drawer = array();
61
62
    /**
63
     * A `<div>` that contains the content of a Symphony backend page.
64
     * @var XMLElement
65
     */
66
    public $Contents = null;
67
68
    /**
69
     * An associative array of the navigation where the key is the group
70
     * index, and the value is an associative array of 'name', 'index' and
71
     * 'children'. Name is the name of the this group, index is the same as
72
     * the key and children is an associative array of navigation items containing
73
     * the keys 'link', 'name' and 'visible'. In Symphony, all navigation items
74
     * are contained within a group, and the group has no 'default' link, therefore
75
     * it is up to the children to provide the link to pages. This link should be
76
     * relative to the Symphony path, although it is possible to provide an
77
     * absolute link by providing a key, 'relative' with the value false.
78
     * @var array
79
     */
80
    public $_navigation = array();
81
82
    /**
83
     *  An associative array describing this pages context. This
84
     *  can include the section handle, the current entry_id, the page
85
     *  name and any flags such as 'saved' or 'created'. This variable
86
     *  often provided in delegates so extensions can manipulate based
87
     *  off the current context or add new keys.
88
     * @var array
89
     */
90
    public $_context = null;
91
92
    /**
93
     * The class attribute of the `<body>` element for this page. Defaults
94
     * to an empty string
95
     * @var string
96
     */
97
    private $_body_class = '';
98
99
    /**
100
     * Constructor calls the parent constructor to set up
101
     * the basic HTML, Head and Body `XMLElement`'s. This function
102
     * also sets the `XMLElement` element style to be HTML, instead of XML
103
     */
104
    public function __construct()
105
    {
106
        parent::__construct();
107
108
        $this->Html->setElementStyle('html');
109
    }
110
111
    /**
112
     * Specifies the type of page that being created. This is used to
113
     * trigger various styling hooks. If your page is mainly a form,
114
     * pass 'form' as the parameter, if it's displaying a single entry,
115
     * pass 'single'. If any other parameter is passed, the 'index'
116
     * styling will be applied.
117
     *
118
     * @param string $type
119
     *  Accepts 'form' or 'single', any other `$type` will trigger 'index'
120
     *  styling.
121
     */
122
    public function setPageType($type = 'form')
123
    {
124
        $this->setBodyClass($type === 'form' || $type === 'page-single' ? 'page-single' : 'page-index');
125
    }
126
127
    /**
128
     * Setter function to set the class attribute on the `<body>` element.
129
     * This function will respect any previous classes that have been added
130
     * to this `<body>`
131
     *
132
     * @param string $class
133
     *  The string of the classname, multiple classes can be specified by
134
     *  uses a space separator
135
     */
136
    public function setBodyClass($class)
137
    {
138
        // Prevents duplicate "page-index" classes
139
        if (!isset($this->_context['page']) || !in_array('page-index', array($this->_context['page'], $class))) {
140
            $this->_body_class .= $class;
141
        }
142
143
        $this->Body->setAttribute('class', $this->_body_class);
144
    }
145
146
    /**
147
     * Given the current page `$context` and the URL path parts, parse the context for
148
     * the current page. This happens prior to the AdminPagePostCallback delegate
149
     * being fired. The `$context` is passed by reference
150
     *
151
     * @since Symphony 3.0.0
152
     * @param array $context
153
     * @param array $parts
154
     * @return void
155
     */
156
    public function parseContext(array &$context, array $parts)
157
    {
158
    }
159
160
    /**
161
     * Accessor for `$this->_context` which includes contextual information
162
     * about the current page such as the class, file location or page root.
163
     * This information varies depending on if the page is provided by an
164
     * extension, is for the publish area, is the login page or any other page
165
     *
166
     * @since Symphony 2.3
167
     * @return array
168
     */
169
    public function getContext()
170
    {
171
        return $this->_context;
172
    }
173
174
    /**
175
     * Given a `$message` and an optional `$type`, this function will
176
     * add an Alert instance into this page's `$this->Alert` property.
177
     * Since Symphony 2.3, there may be more than one `Alert` per page.
178
     * Unless the Alert is an Error, it is required the `$message` be
179
     * passed to this function.
180
     *
181
     * @param string $message
182
     *  The message to display to users
183
     * @param string $type
184
     *  An Alert constant, being `Alert::NOTICE`, `Alert::ERROR` or
185
     *  `Alert::SUCCESS`. The differing types will show the error
186
     *  in a different style in the backend. If omitted, this defaults
187
     *  to `Alert::NOTICE`.
188
     * @throws Exception
189
     */
190
    public function pageAlert($message = null, $type = Alert::NOTICE)
191
    {
192
        if (is_null($message) && $type === Alert::ERROR) {
193
            $message = __('There was a problem rendering this page. Please check the activity log for more details.');
194
        } else {
195
            $message = __($message);
196
        }
197
198
        if (strlen(trim($message)) === 0) {
199
            throw new Exception(__('A message must be supplied unless the alert is of type Alert::ERROR'));
200
        }
201
202
        $this->Alert[] = new Alert($message, $type);
203
    }
204
205
    /**
206
     * Appends the heading of this Symphony page to the Context element.
207
     * Action buttons can be provided (e.g. "Create new") as second parameter.
208
     *
209
     * @since Symphony 2.3
210
     * @param string $value
211
     *  The heading text
212
     * @param array|XMLElement|string $actions
213
     *  Some contextual actions to append to the heading, they can be provided as
214
     *  an array of XMLElements or strings. Traditionally Symphony uses this to append
215
     *  a "Create new" link to the Context div.
216
     */
217
    public function appendSubheading($value, $actions = null)
218
    {
219
        if (!is_array($actions) && $actions) { // Backward compatibility
220
            $actions = array($actions);
221
        }
222
223
        if (!empty($actions)) {
224
            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...
225
                $this->insertAction($a);
226
            }
227
        }
228
229
        $this->Breadcrumbs->appendChild(new XMLElement('h2', $value, array('role' => 'heading', 'id' => 'symphony-subheading')));
230
    }
231
232
    /**
233
     * This function allows a user to insert an Action button to the page.
234
     * It accepts an `XMLElement` (which should be of the `Anchor` type),
235
     * an optional parameter `$prepend`, which when `true` will add this
236
     * action before any existing actions.
237
     *
238
     * @since Symphony 2.3
239
     * @see core.Widget#Anchor
240
     * @param XMLElement $action
241
     *  An Anchor element to add to the top of the page.
242
     * @param boolean $append
243
     *  If true, this will add the `$action` after existing actions, otherwise
244
     *  it will be added before existing actions. By default this is `true`,
245
     *  which will add the `$action` after current actions.
246
     */
247
    public function insertAction(XMLElement $action, $append = true)
248
    {
249
        $actions = $this->Context->getChildrenByName('ul');
250
251
        // Actions haven't be added yet, create the element
252
        if (empty($actions)) {
253
            $ul = new XMLElement('ul', null, array('class' => 'actions'));
254
            $this->Context->appendChild($ul);
255
        } else {
256
            $ul = current($actions);
257
            $this->Context->replaceChildAt(1, $ul);
258
        }
259
260
        $li = new XMLElement('li', $action);
261
262
        if ($append) {
263
            $ul->prependChild($li);
264
        } else {
265
            $ul->appendChild($li);
266
        }
267
    }
268
269
    /**
270
     * Allows developers to specify a list of nav items that build the
271
     * path to the current page or, in jargon, "breadcrumbs".
272
     *
273
     * @since Symphony 2.3
274
     * @param array $values
275
     *  An array of `XMLElement`'s or strings that compose the path. If breadcrumbs
276
     *  already exist, any new item will be appended to the rightmost part of the
277
     *  path.
278
     */
279
    public function insertBreadcrumbs(array $values)
280
    {
281
        if (empty($values)) {
282
            return;
283
        }
284
285
        if ($this->Breadcrumbs instanceof XMLElement && count($this->Breadcrumbs->getChildrenByName('nav')) === 1) {
286
            $nav = $this->Breadcrumbs->getChildrenByName('nav');
287
            $nav = $nav[0];
288
289
            $p = $nav->getChild(0);
290
        } else {
291
            $p = new XMLElement('p');
292
            $nav = new XMLElement('nav');
293
            $nav->appendChild($p);
294
295
            $this->Breadcrumbs->prependChild($nav);
296
        }
297
298
        foreach ($values as $v) {
299
            $p->appendChild($v);
300
            $p->appendChild(new XMLElement('span', '&#8250;', array('class' => 'sep')));
301
        }
302
    }
303
304
    /**
305
     * Allows a Drawer element to added to the backend page in one of three
306
     * positions, `horizontal`, `vertical-left` or `vertical-right`. The button
307
     * to trigger the visibility of the drawer will be added after existing
308
     * actions by default.
309
     *
310
     * @since Symphony 2.3
311
     * @see core.Widget#Drawer
312
     * @param XMLElement $drawer
313
     *  An XMLElement representing the drawer, use `Widget::Drawer` to construct
314
     * @param string $position
315
     *  Where `$position` can be `horizontal`, `vertical-left` or
316
     *  `vertical-right`. Defaults to `horizontal`.
317
     * @param string $button
318
     *  If not passed, a button to open/close the drawer will not be added
319
     *  to the interface. Accepts 'prepend' or 'append' values, which will
320
     *  add the button before or after existing buttons. Defaults to `prepend`.
321
     *  If any other value is passed, no button will be added.
322
     * @throws InvalidArgumentException
323
     */
324
    public function insertDrawer(XMLElement $drawer, $position = 'horizontal', $button = 'append')
325
    {
326
        $drawer->addClass($position);
327
        $drawer->setAttribute('data-position', $position);
328
        $drawer->setAttribute('role', 'complementary');
329
        $this->Drawer[$position][] = $drawer;
330
331
        if (in_array($button, array('prepend', 'append'))) {
332
            $this->insertAction(
333
                Widget::Anchor(
334
                    $drawer->getAttribute('data-label'),
335
                    '#' . $drawer->getAttribute('id'),
336
                    null,
337
                    'button drawer ' . $position
338
                ),
339
                ($button === 'append' ? true : false)
340
            );
341
        }
342
    }
343
344
    /**
345
     * This function initialises a lot of the basic elements that make up a Symphony
346
     * backend page such as the default stylesheets and scripts, the navigation and
347
     * the footer. Any alerts are also appended by this function. `view()` is called to
348
     * build the actual content of the page. The `InitialiseAdminPageHead` delegate
349
     * allows extensions to add elements to the `<head>`.
350
     *
351
     * @see view()
352
     * @uses InitialiseAdminPageHead
353
     * @param array $context
354
     *  An associative array describing this pages context. This
355
     *  can include the section handle, the current entry_id, the page
356
     *  name and any flags such as 'saved' or 'created'. This list is not exhaustive
357
     *  and extensions can add their own keys to the array.
358
     * @throws InvalidArgumentException
359
     * @throws SymphonyErrorPage
360
     */
361
    public function build(array $context = array())
362
    {
363
        $this->_context = $context;
364
365
        if (!$this->canAccessPage()) {
366
            Administration::instance()->throwCustomError(
367
                __('You are not authorised to access this page.'),
368
                __('Access Denied'),
369
                Page::HTTP_STATUS_UNAUTHORIZED
370
            );
371
        }
372
373
        $this->Html->setDTD('<!DOCTYPE html>');
374
        $this->Html->setAttribute('lang', Lang::get());
375
        $this->addElementToHead(new XMLElement('meta', null, array('charset' => 'UTF-8')), 0);
376
        $this->addElementToHead(new XMLElement('meta', null, array('http-equiv' => 'X-UA-Compatible', 'content' => 'IE=edge,chrome=1')), 1);
377
        $this->addElementToHead(new XMLElement('meta', null, array('name' => 'viewport', 'content' => 'width=device-width, initial-scale=1')), 2);
378
379
        // Add styles
380
        $this->addStylesheetToHead(ASSETS_URL . '/css/symphony.min.css', 'screen', 2, false);
381
382
        // Calculate timezone offset from UTC
383
        $timezone = new DateTimeZone(Symphony::Configuration()->get('timezone', 'region'));
384
        $datetime = new DateTime('now', $timezone);
385
        $timezoneOffset = intval($timezone->getOffset($datetime)) / 60;
386
387
        // Add scripts
388
        $environment = array(
389
390
            'root'     => URL,
391
            'symphony' => SYMPHONY_URL,
392
            'path'     => '/' . Symphony::Configuration()->get('admin-path', 'symphony'),
393
            'route'    => getCurrentPage(),
394
            'version'  => Symphony::Configuration()->get('version', 'symphony'),
395
            'lang'     => Lang::get(),
396
            'user'     => array(
397
398
                'fullname' => Symphony::Author()->getFullName(),
399
                'name'     => Symphony::Author()->get('first_name'),
400
                'type'     => Symphony::Author()->get('user_type'),
401
                'id'       => Symphony::Author()->get('id')
402
            ),
403
            'datetime' => array(
404
405
                'formats'         => DateTimeObj::getDateFormatMappings(),
406
                'timezone-offset' => $timezoneOffset
407
            ),
408
            'env' => array_merge(
409
410
                array('page-namespace' => Symphony::getPageNamespace()),
411
                $this->_context
412
            )
413
        );
414
415
        $this->addElementToHead(
416
            new XMLElement('script', json_encode($environment), array(
417
                'type' => 'application/json',
418
                'id' => 'environment'
419
            )),
420
            4
421
        );
422
423
        $this->addScriptToHead(ASSETS_URL . '/js/symphony.min.js', 6, false);
424
425
        // Initialise page containers
426
        $this->Wrapper = new XMLElement('div', null, array('id' => 'wrapper'));
427
        $this->Header = new XMLElement('header', null, array('id' => 'header'));
428
        $this->Context = new XMLElement('div', null, array('id' => 'context'));
429
        $this->Breadcrumbs = new XMLElement('div', null, array('id' => 'breadcrumbs'));
430
        $this->Contents = new XMLElement('div', null, array('id' => 'contents', 'role' => 'main'));
431
        $this->Form = Widget::Form(Administration::instance()->getCurrentPageURL(), 'post', null, null, array('role' => 'form'));
432
433
        /**
434
         * Allows developers to insert items into the page HEAD. Use
435
         * `Administration::instance()->Page` for access to the page object.
436
         *
437
         * @since In Symphony 2.3.2 this delegate was renamed from
438
         *  `InitaliseAdminPageHead` to the correct spelling of
439
         *  `InitialiseAdminPageHead`. The old delegate is supported
440
         *  until Symphony 3.0.0
441
         *
442
         * @delegate InitialiseAdminPageHead
443
         * @param string $context
444
         *  '/backend/'
445
         */
446
        Symphony::ExtensionManager()->notifyMembers('InitialiseAdminPageHead', '/backend/');
447
        Symphony::ExtensionManager()->notifyMembers('InitaliseAdminPageHead', '/backend/');
448
449
        $this->addHeaderToPage('Content-Type', 'text/html; charset=UTF-8');
450
        $this->addHeaderToPage('Cache-Control', 'no-cache, must-revalidate, max-age=0');
451
        $this->addHeaderToPage('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT');
452
453
        // If not set by another extension, lock down the backend
454
        if (!array_key_exists('x-frame-options', $this->headers())) {
455
            $this->addHeaderToPage('X-Frame-Options', 'SAMEORIGIN');
456
        }
457
458
        if (!array_key_exists('access-control-allow-origin', $this->headers())) {
459
            $this->addHeaderToPage('Access-Control-Allow-Origin', URL);
460
        }
461
462
        if (isset($_REQUEST['action'])) {
463
            $this->action();
464
            Symphony::Profiler()->sample('Page action run', PROFILE_LAP);
465
        }
466
467
        $h1 = new XMLElement('h1');
468
        $h1->appendChild(Widget::Anchor(Symphony::Configuration()->get('sitename', 'general'), rtrim(URL, '/') . '/'));
0 ignored issues
show
Bug introduced by
It seems like \Symphony::Configuration...('sitename', 'general') targeting Configuration::get() can also be of type array; however, Widget::Anchor() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
469
        $this->Header->appendChild($h1);
470
471
        $this->appendUserLinks();
472
        $this->appendNavigation();
473
474
        // Add Breadcrumbs
475
        $this->Context->prependChild($this->Breadcrumbs);
476
        $this->Contents->appendChild($this->Form);
477
478
        $this->view();
479
480
        $this->appendAlert();
481
482
        Symphony::Profiler()->sample('Page content created', PROFILE_LAP);
483
    }
484
485
    /**
486
     * Checks the current Symphony Author can access the current page.
487
     * This check uses the `ASSETS . /xml/navigation.xml` file to determine
488
     * if the current page (or the current page namespace) can be viewed
489
     * by the currently logged in Author.
490
     *
491
     * @link http://github.com/symphonycms/symphony-2/blob/master/symphony/assets/xml/navigation.xml
492
     * @return boolean
493
     *  True if the Author can access the current page, false otherwise
494
     */
495
    public function canAccessPage()
496
    {
497
        $nav = $this->getNavigationArray();
498
        $page = '/' . trim(getCurrentPage(), '/') . '/';
499
500
        $page_limit = 'author';
501
502
        foreach ($nav as $item) {
503
            if (
504
                // If page directly matches one of the children
505
                General::in_array_multi($page, $item['children'])
506
                // If the page namespace matches one of the children (this will usually drop query
507
                // string parameters such as /edit/1/)
508
                || General::in_array_multi(Symphony::getPageNamespace() . '/', $item['children'])
509
            ) {
510
                if (is_array($item['children'])) {
511
                    foreach ($item['children'] as $c) {
512 View Code Duplication
                        if ($c['link'] === $page && isset($c['limit'])) {
513
                            $page_limit = $c['limit'];
514
                        }
515
                    }
516
                }
517
518
                if (isset($item['limit']) && $page_limit !== 'primary') {
519
                    if ($page_limit === 'author' && $item['limit'] === 'developer') {
520
                        $page_limit = 'developer';
521
                    }
522
                }
523 View Code Duplication
            } elseif (isset($item['link']) && $page === $item['link'] && isset($item['limit'])) {
524
                $page_limit = $item['limit'];
525
            }
526
        }
527
528
        return $this->doesAuthorHaveAccess($page_limit);
529
    }
530
531
    /**
532
     * Given the limit of the current navigation item or page, this function
533
     * returns if the current Author can access that item or not.
534
     *
535
     * @since Symphony 2.5.1
536
     * @param string $item_limit
537
     * @return boolean
538
     */
539
    public function doesAuthorHaveAccess($item_limit = null)
540
    {
541
        $can_access = false;
542
543
        if (!isset($item_limit) || $item_limit === 'author') {
544
            $can_access = true;
545
        } elseif ($item_limit === 'developer' && Symphony::Author()->isDeveloper()) {
546
            $can_access = true;
547
        } elseif ($item_limit === 'manager' && (Symphony::Author()->isManager() || Symphony::Author()->isDeveloper())) {
548
            $can_access = true;
549
        } elseif ($item_limit === 'primary' && Symphony::Author()->isPrimaryAccount()) {
550
            $can_access = true;
551
        }
552
553
        return $can_access;
554
    }
555
556
    /**
557
     * Appends the `$this->Header`, `$this->Context` and `$this->Contents`
558
     * to `$this->Wrapper` before adding the ID and class attributes for
559
     * the `<body>` element. This function will also place any Drawer elements
560
     * in their relevant positions in the page. After this has completed the
561
     * parent `generate()` is called which will convert the `XMLElement`'s
562
     * into strings ready for output.
563
     *
564
     * @see core.HTMLPage#generate()
565
     * @param null $page
566
     * @return string
567
     */
568
    public function generate($page = null)
569
    {
570
        $this->Wrapper->appendChild($this->Header);
571
572
        // Add horizontal drawers (inside #context)
573
        if (isset($this->Drawer['horizontal'])) {
574
            $this->Context->appendChildArray($this->Drawer['horizontal']);
575
        }
576
577
        $this->Wrapper->appendChild($this->Context);
578
579
        // Add vertical-left drawers (between #context and #contents)
580
        if (isset($this->Drawer['vertical-left'])) {
581
            $this->Contents->appendChildArray($this->Drawer['vertical-left']);
582
        }
583
584
        // Add vertical-right drawers (after #contents)
585
        if (isset($this->Drawer['vertical-right'])) {
586
            $this->Contents->appendChildArray($this->Drawer['vertical-right']);
587
        }
588
589
        $this->Wrapper->appendChild($this->Contents);
590
591
        $this->Body->appendChild($this->Wrapper);
592
593
        $this->__appendBodyId();
594
        $this->__appendBodyAttributes($this->_context);
595
596
        return parent::generate($page);
597
    }
598
599
    /**
600
     * Uses this pages PHP classname as the `<body>` ID attribute.
601
     * This function removes 'content' from the start of the classname
602
     * and converts all uppercase letters to lowercase and prefixes them
603
     * with a hyphen.
604
     */
605
    private function __appendBodyId()
606
    {
607
        // trim "content" from beginning of class name
608
        $body_id = preg_replace("/^content/", '', get_class($this));
609
610
        // lowercase any uppercase letters and prefix with a hyphen
611
        $body_id = trim(
612
            preg_replace_callback(
613
                "/([A-Z])/",
614
                function($id) {
615
                    return "-" . strtolower($id[0]);
616
                },
617
                $body_id
618
            ),
619
            '-'
620
        );
621
622
        if (!empty($body_id)) {
623
            $this->Body->setAttribute('id', $body_id);
624
        }
625
    }
626
627
    /**
628
     * Given the context of the current page, which is an associative
629
     * array, this function will append the values to the page's body as
630
     * data attributes. If an context value is numeric it will be given
631
     * the key 'id' otherwise all attributes will be prefixed by the context key.
632
     *
633
     * If the context value is an array, it will be JSON encoded.
634
     *
635
     * @param array $context
636
     */
637
    private function __appendBodyAttributes(array $context = array())
638
    {
639
        foreach ($context as $key => $value) {
640
            if (is_numeric($value)) {
641
                $key = 'id';
642
643
                // Add prefixes to all context values by making the
644
                // class be {key}-{value}. #1397 ^BA
645
            } elseif (!is_numeric($key) && isset($value)) {
646
                $key = str_replace('_', '-', $key);
647
            }
648
649
            // JSON encode any array values
650
            if (is_array($value)) {
651
                $value = json_encode($value);
652
            }
653
654
            $this->Body->setAttribute('data-' . $key, $value);
655
        }
656
    }
657
658
    /**
659
     * Called to build the content for the page. This function immediately calls
660
     * `__switchboard()` which acts a bit of a controller to show content based on
661
     * off a type, such as 'view' or 'action'. `AdministrationPages` can override this
662
     * function to just display content if they do not need the switchboard functionality
663
     *
664
     * @see __switchboard()
665
     */
666
    public function view()
667
    {
668
        $this->__switchboard();
669
    }
670
671
    /**
672
     * This function is called when `$_REQUEST` contains a key of 'action'.
673
     * Any logic that needs to occur immediately for the action to complete
674
     * should be contained within this function. By default this calls the
675
     * `__switchboard` with the type set to 'action'.
676
     *
677
     * @see __switchboard()
678
     */
679
    public function action()
680
    {
681
        $this->__switchboard('action');
682
    }
683
684
    /**
685
     * The `__switchboard` function acts as a controller to display content
686
     * based off the $type. By default, the `$type` is 'view' but it can be set
687
     * also set to 'action'. The `$type` is prepended by __ and the context is
688
     * append to the $type to create the name of the function that will provide
689
     * that logic. For example, if the $type was action and the context of the
690
     * current page was new, the resulting function to be called would be named
691
     * `__actionNew()`. If an action function is not provided by the Page, this function
692
     * returns nothing, however if a view function is not provided, a 404 page
693
     * will be returned.
694
     *
695
     * @param string $type
696
     *  Either 'view' or 'action', by default this will be 'view'
697
     * @throws SymphonyErrorPage
698
     */
699
    public function __switchboard($type = 'view')
700
    {
701
        if (empty($this->_context)) {
702
            $context = 'index';
703
        } else {
704
            $context = current($this->_context);
705
        }
706
707
        $function = ($type === 'action' ? '__action' : '__view') . ucfirst($context);
708
709
        if (!method_exists($this, $function)) {
710
            // If there is no action function, just return without doing anything
711
            if ($type === 'action') {
712
                return;
713
            }
714
715
            Administration::instance()->errorPageNotFound();
716
        }
717
718
        $this->$function(null);
719
    }
720
721
    /**
722
     * If `$this->Alert` is set, it will be added to this page. The
723
     * `AppendPageAlert` delegate is fired to allow extensions to provide their
724
     * their own Alert messages for this page. Since Symphony 2.3, there may be
725
     * more than one `Alert` per page. Alerts are displayed in the order of
726
     * severity, with Errors first, then Success alerts followed by Notices.
727
     *
728
     * @uses AppendPageAlert
729
     */
730
    public function appendAlert()
731
    {
732
        /**
733
         * Allows for appending of alerts. Administration::instance()->Page->Alert is way to tell what
734
         * is currently in the system
735
         *
736
         * @delegate AppendPageAlert
737
         * @param string $context
738
         *  '/backend/'
739
         */
740
        Symphony::ExtensionManager()->notifyMembers('AppendPageAlert', '/backend/');
741
742
743
        if (!is_array($this->Alert) || empty($this->Alert)) {
744
            return;
745
        }
746
747
        usort($this->Alert, array($this, 'sortAlerts'));
748
749
        // Using prependChild ruins our order (it's backwards, but with most
750
        // recent notices coming after oldest notices), so reversing the array
751
        // fixes this. We need to prepend so that without Javascript the notices
752
        // are at the top of the markup. See #1312
753
        $this->Alert = array_reverse($this->Alert);
754
755
        foreach ($this->Alert as $alert) {
756
            $this->Header->prependChild($alert->asXML());
757
        }
758
    }
759
760
    // Errors first, success next, then notices.
761
    public function sortAlerts($a, $b)
762
    {
763
        if ($a->{'type'} === $b->{'type'}) {
764
            return 0;
765
        }
766
767
        if (
768
            ($a->{'type'} === Alert::ERROR && $a->{'type'} !== $b->{'type'})
769
            || ($a->{'type'} === Alert::SUCCESS && $b->{'type'} === Alert::NOTICE)
770
        ) {
771
            return -1;
772
        }
773
774
        return 1;
775
    }
776
777
    /**
778
     * This function will append the Navigation to the AdministrationPage.
779
     * It fires a delegate, NavigationPreRender, to allow extensions to manipulate
780
     * the navigation. Extensions should not use this to add their own navigation,
781
     * they should provide the navigation through their fetchNavigation function.
782
     * Note with the Section navigation groups, if there is only one section in a group
783
     * and that section is set to visible, the group will not appear in the navigation.
784
     *
785
     * @uses NavigationPreRender
786
     * @see getNavigationArray()
787
     * @see toolkit.Extension#fetchNavigation()
788
     */
789
    public function appendNavigation()
790
    {
791
        $nav = $this->getNavigationArray();
792
793
        /**
794
         * Immediately before displaying the admin navigation. Provided with the
795
         * navigation array. Manipulating it will alter the navigation for all pages.
796
         *
797
         * @delegate NavigationPreRender
798
         * @param string $context
799
         *  '/backend/'
800
         * @param array $nav
801
         *  An associative array of the current navigation, passed by reference
802
         */
803
        Symphony::ExtensionManager()->notifyMembers('NavigationPreRender', '/backend/', array('navigation' => &$nav));
804
805
        $navElement = new XMLElement('nav', null, array('id' => 'nav', 'role' => 'navigation'));
806
        $contentNav = new XMLElement('ul', null, array('class' => 'content', 'role' => 'menubar'));
807
        $structureNav = new XMLElement('ul', null, array('class' => 'structure', 'role' => 'menubar'));
808
809
        foreach ($nav as $n) {
810
            if (isset($n['visible']) && $n['visible'] === 'no') {
811
                continue;
812
            }
813
814
            if ($this->doesAuthorHaveAccess($n['limit'])) {
815
                $xGroup = new XMLElement('li', $n['name'], array('role' => 'presentation'));
816
817
                if (isset($n['class']) && trim($n['name']) !== '') {
818
                    $xGroup->setAttribute('class', $n['class']);
819
                }
820
821
                $hasChildren = false;
822
                $xChildren = new XMLElement('ul', null, array('role' => 'menu'));
823
824
                if (is_array($n['children']) && !empty($n['children'])) {
825
                    foreach ($n['children'] as $c) {
826
                        // adapt for Yes and yes
827
                        if (strtolower($c['visible']) !== 'yes') {
828
                            continue;
829
                        }
830
831
                        if ($this->doesAuthorHaveAccess($c['limit'])) {
832
                            $xChild = new XMLElement('li');
833
                            $xChild->setAttribute('role', 'menuitem');
834
                            $linkChild = Widget::Anchor($c['name'], SYMPHONY_URL . $c['link']);
835
                            if (isset($c['target'])) {
836
                                $linkChild->setAttribute('target', $c['target']);
837
                            }
838
                            $xChild->appendChild($linkChild);
839
                            $xChildren->appendChild($xChild);
840
                            $hasChildren = true;
841
                        }
842
                    }
843
844
                    if ($hasChildren) {
845
                        $xGroup->setAttribute('aria-haspopup', 'true');
846
                        $xGroup->appendChild($xChildren);
847
848
                        if ($n['type'] === 'content') {
849
                            $contentNav->appendChild($xGroup);
850
                        } elseif ($n['type'] === 'structure') {
851
                            $structureNav->prependChild($xGroup);
852
                        }
853
                    }
854
                }
855
            }
856
        }
857
858
        $navElement->appendChild($contentNav);
859
        $navElement->appendChild($structureNav);
860
        $this->Header->appendChild($navElement);
861
        Symphony::Profiler()->sample('Navigation Built', PROFILE_LAP);
862
    }
863
864
    /**
865
     * Returns the `$_navigation` variable of this Page. If it is empty,
866
     * it will be built by `__buildNavigation`
867
     *
868
     * @see __buildNavigation()
869
     * @return array
870
     */
871
    public function getNavigationArray()
872
    {
873
        if (empty($this->_navigation)) {
874
            $this->__buildNavigation();
875
        }
876
877
        return $this->_navigation;
878
    }
879
880
    /**
881
     * This method fills the `$nav` array with value
882
     * from the `ASSETS/xml/navigation.xml` file
883
     *
884
     * @link http://github.com/symphonycms/symphony-2/blob/master/symphony/assets/xml/navigation.xml
885
     *
886
     * @since Symphony 2.3.2
887
     *
888
     * @param array $nav
889
     *  The navigation array that will receive nav nodes
890
     */
891
    private function buildXmlNavigation(&$nav)
892
    {
893
        $xml = simplexml_load_file(ASSETS . '/xml/navigation.xml');
894
895
        // Loop over the default Symphony navigation file, converting
896
        // it into an associative array representation
897
        foreach ($xml->xpath('/navigation/group') as $n) {
898
            $index = (string)$n->attributes()->index;
899
            $children = $n->xpath('children/item');
900
            $content = $n->attributes();
901
902
            // If the index is already set, increment the index and check again.
903
            // Rinse and repeat until the index is not set.
904
            if (isset($nav[$index])) {
905
                do {
906
                    $index++;
907
                } while (isset($nav[$index]));
908
            }
909
910
            $nav[$index] = array(
911
                'name' => __(strval($content->name)),
912
                'type' => 'structure',
913
                'index' => $index,
914
                'children' => array()
915
            );
916
917
            if (strlen(trim((string)$content->limit)) > 0) {
918
                $nav[$index]['limit'] = (string)$content->limit;
919
            }
920
921
            if (count($children) > 0) {
922
                foreach ($children as $child) {
923
                    $item = array(
924
                        'link' => (string)$child->attributes()->link,
925
                        'name' => __(strval($child->attributes()->name)),
926
                        'visible' => ((string)$child->attributes()->visible === 'no' ? 'no' : 'yes'),
927
                    );
928
929
                    $limit = (string)$child->attributes()->limit;
930
931
                    if (strlen(trim($limit)) > 0) {
932
                        $item['limit'] = $limit;
933
                    }
934
935
                    $nav[$index]['children'][] = $item;
936
                }
937
            }
938
        }
939
    }
940
941
    /**
942
     * This method fills the `$nav` array with value
943
     * from each Section
944
     *
945
     * @since Symphony 2.3.2
946
     *
947
     * @param array $nav
948
     *  The navigation array that will receive nav nodes
949
     */
950
    private function buildSectionNavigation(&$nav)
951
    {
952
        // Build the section navigation, grouped by their navigation groups
953
        $sections = SectionManager::fetch(null, 'asc', 'sortorder');
954
955
        if (is_array($sections) && !empty($sections)) {
956
            foreach ($sections as $s) {
957
                $group_index = self::__navigationFindGroupIndex($nav, $s->get('navigation_group'));
958
959
                if ($group_index === false) {
960
                    $group_index = General::array_find_available_index($nav, 0);
961
962
                    $nav[$group_index] = array(
963
                        'name' => $s->get('navigation_group'),
964
                        'type' => 'content',
965
                        'index' => $group_index,
966
                        'children' => array()
967
                    );
968
                }
969
970
                $nav[$group_index]['children'][] = array(
971
                    'link' => '/publish/' . $s->get('handle') . '/',
972
                    'name' => $s->get('name'),
973
                    'type' => 'section',
974
                    'section' => array('id' => $s->get('id'), 'handle' => $s->get('handle')),
975
                    'visible' => ($s->get('hidden') === 'no' ? 'yes' : 'no')
976
                );
977
            }
978
        }
979
    }
980
981
    /**
982
     * This method fills the `$nav` array with value
983
     * from each Extension's `fetchNavigation` method
984
     *
985
     * @since Symphony 2.3.2
986
     *
987
     * @param array $nav
988
     *  The navigation array that will receive nav nodes
989
     * @throws Exception
990
     * @throws SymphonyErrorPage
991
     */
992
    private function buildExtensionsNavigation(&$nav)
993
    {
994
        // Loop over all the installed extensions to add in other navigation items
995
        $extensions = Symphony::ExtensionManager()->listInstalledHandles();
996
997
        foreach ($extensions as $e) {
998
            $extension = Symphony::ExtensionManager()->getInstance($e);
999
            $extension_navigation = $extension->fetchNavigation();
1000
1001
            if (is_array($extension_navigation) && !empty($extension_navigation)) {
1002
                foreach ($extension_navigation as $item) {
1003
                    $type = isset($item['children']) ? Extension::NAV_GROUP : Extension::NAV_CHILD;
1004
1005
                    switch ($type) {
1006
                        case Extension::NAV_GROUP:
1007
                            $index = General::array_find_available_index($nav, $item['location']);
1008
1009
                            // Actual group
1010
                            $nav[$index] = self::createParentNavItem($index, $item);
1011
1012
                            // Render its children
1013
                            foreach ($item['children'] as $child) {
1014
                                $nav[$index]['children'][] = self::createChildNavItem($child, $e);
1015
                            }
1016
1017
                            break;
1018
1019
                        case Extension::NAV_CHILD:
1020
                            if (!is_numeric($item['location'])) {
1021
                                // is a navigation group
1022
                                $group_name = $item['location'];
1023
                                $group_index = self::__navigationFindGroupIndex($nav, $item['location']);
1024
                            } else {
1025
                                // is a legacy numeric index
1026
                                $group_index = $item['location'];
1027
                            }
1028
1029
                            $child = self::createChildNavItem($item, $e);
1030
1031
                            if ($group_index === false) {
1032
                                $group_index = General::array_find_available_index($nav, 0);
1033
1034
                                $nav_parent = self::createParentNavItem($group_index, $item);
1035
                                $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...
1036
                                $nav_parent['children'] = array($child);
1037
1038
                                // add new navigation group
1039
                                $nav[$group_index] = $nav_parent;
1040
                            } else {
1041
                                // add new location by index
1042
                                $nav[$group_index]['children'][] = $child;
1043
                            }
1044
1045
                            break;
1046
                    }
1047
                }
1048
            }
1049
        }
1050
    }
1051
1052
    /**
1053
     * This function builds out a navigation menu item for parents. Parents display
1054
     * in the top level navigation of the backend and may have children (dropdown menus)
1055
     *
1056
     * @since Symphony 2.5.1
1057
     * @param integer $index
1058
     * @param array $item
1059
     * @return array
1060
     */
1061
    private static function createParentNavItem($index, $item)
1062
    {
1063
        $nav_item = array(
1064
            'name' => $item['name'],
1065
            'type' => isset($item['type']) ? $item['type'] : 'structure',
1066
            'index' => $index,
1067
            'children' => array(),
1068
            'limit' => isset($item['limit']) ? $item['limit'] : null
1069
        );
1070
1071
        return $nav_item;
1072
    }
1073
1074
    /**
1075
     * This function builds out a navigation menu item for children. Children
1076
     * live under a parent navigation item and are shown on hover.
1077
     *
1078
     * @since Symphony 2.5.1
1079
     * @param array $item
1080
     * @param string $extension_handle
1081
     * @return array
1082
     */
1083
    private static function createChildNavItem($item, $extension_handle)
1084
    {
1085
        if (!isset($item['relative']) || $item['relative'] === true) {
1086
            $link = '/extension/' . $extension_handle . '/' . ltrim($item['link'], '/');
1087
        } else {
1088
            $link = '/' . ltrim($item['link'], '/');
1089
        }
1090
1091
        $nav_item = array(
1092
            'link' => $link,
1093
            'name' => $item['name'],
1094
            'visible' => (isset($item['visible']) && $item['visible'] === 'no') ? 'no' : 'yes',
1095
            'limit' => isset($item['limit']) ? $item['limit'] : null,
1096
            'target' => isset($item['target']) ? $item['target'] : null
1097
        );
1098
1099
        return $nav_item;
1100
    }
1101
1102
    /**
1103
     * This function populates the `$_navigation` array with an associative array
1104
     * of all the navigation groups and their links. Symphony only supports one
1105
     * level of navigation, so children links cannot have children links. The default
1106
     * Symphony navigation is found in the `ASSETS/xml/navigation.xml` folder. This is
1107
     * loaded first, and then the Section navigation is built, followed by the Extension
1108
     * navigation. Additionally, this function will set the active group of the navigation
1109
     * by checking the current page against the array of links.
1110
     *
1111
     * @link https://github.com/symphonycms/symphony-2/blob/master/symphony/assets/xml/navigation.xml
1112
     * @link https://github.com/symphonycms/symphony-2/blob/master/symphony/lib/toolkit/class.extension.php
1113
     */
1114
    public function __buildNavigation()
1115
    {
1116
        $nav = array();
1117
1118
        $this->buildXmlNavigation($nav);
1119
        $this->buildSectionNavigation($nav);
1120
        $this->buildExtensionsNavigation($nav);
1121
1122
        $pageCallback = Administration::instance()->getPageCallback();
1123
1124
        $pageRoot = $pageCallback['pageroot'] . (isset($pageCallback['context'][0]) ? $pageCallback['context'][0] . '/' : '');
1125
        $found = self::__findActiveNavigationGroup($nav, $pageRoot);
1126
1127
        // Normal searches failed. Use a regular expression using the page root. This is less
1128
        // efficient and should never really get invoked unless something weird is going on
1129
        if (!$found) {
1130
            self::__findActiveNavigationGroup($nav, '/^' . str_replace('/', '\/', $pageCallback['pageroot']) . '/i', true);
1131
        }
1132
1133
        ksort($nav);
1134
        $this->_navigation = $nav;
1135
    }
1136
1137
    /**
1138
     * Given an associative array representing the navigation, and a group,
1139
     * this function will attempt to return the index of the group in the navigation
1140
     * array. If it is found, it will return the index, otherwise it will return false.
1141
     *
1142
     * @param array $nav
1143
     *  An associative array of the navigation where the key is the group
1144
     *  index, and the value is an associative array of 'name', 'index' and
1145
     *  'children'. Name is the name of the this group, index is the same as
1146
     *  the key and children is an associative array of navigation items containing
1147
     *  the keys 'link', 'name' and 'visible'. The 'haystack'.
1148
     * @param string $group
1149
     *  The group name to find, the 'needle'.
1150
     * @return integer|boolean
1151
     *  If the group is found, the index will be returned, otherwise false.
1152
     */
1153
    private static function __navigationFindGroupIndex(array $nav, $group)
1154
    {
1155
        foreach ($nav as $index => $item) {
1156
            if ($item['name'] === $group) {
1157
                return $index;
1158
            }
1159
        }
1160
1161
        return false;
1162
    }
1163
1164
    /**
1165
     * Given the navigation array, this function will loop over all the items
1166
     * to determine which is the 'active' navigation group, or in other words,
1167
     * what group best represents the current page `$this->Author` is viewing.
1168
     * This is done by checking the current page's link against all the links
1169
     * provided in the `$nav`, and then flagging the group of the found link
1170
     * with an 'active' CSS class. The current page's link omits any flags or
1171
     * URL parameters and just uses the root page URL.
1172
     *
1173
     * @param array $nav
1174
     *  An associative array of the navigation where the key is the group
1175
     *  index, and the value is an associative array of 'name', 'index' and
1176
     *  'children'. Name is the name of the this group, index is the same as
1177
     *  the key and children is an associative array of navigation items containing
1178
     *  the keys 'link', 'name' and 'visible'. The 'haystack'. This parameter is passed
1179
     *  by reference to this function.
1180
     * @param string $pageroot
1181
     *  The current page the Author is the viewing, minus any flags or URL
1182
     *  parameters such as a Symphony object ID. eg. Section ID, Entry ID. This
1183
     *  parameter is also be a regular expression, but this is highly unlikely.
1184
     * @param boolean $pattern
1185
     *  If set to true, the `$pageroot` represents a regular expression which will
1186
     *  determine if the active navigation item
1187
     * @return boolean
1188
     *  Returns true if an active link was found, false otherwise. If true, the
1189
     *  navigation group of the active link will be given the CSS class 'active'
1190
     */
1191
    private static function __findActiveNavigationGroup(array &$nav, $pageroot, $pattern = false)
1192
    {
1193
        foreach ($nav as $index => $contents) {
1194
            if (is_array($contents['children']) && !empty($contents['children'])) {
1195
                foreach ($contents['children'] as $item) {
1196
                    if ($pattern && preg_match($pageroot, $item['link'])) {
1197
                        $nav[$index]['class'] = 'active';
1198
                        return true;
1199
                    } elseif ($item['link'] === $pageroot) {
1200
                        $nav[$index]['class'] = 'active';
1201
                        return true;
1202
                    }
1203
                }
1204
            }
1205
        }
1206
1207
        return false;
1208
    }
1209
1210
    /**
1211
     * Creates the Symphony footer for an Administration page. By default
1212
     * this includes the installed Symphony version and the currently logged
1213
     * in Author. A delegate is provided to allow extensions to manipulate the
1214
     * footer HTML, which is an XMLElement of a `<ul>` element.
1215
     * Since Symphony 2.3, it no longer uses the `AddElementToFooter` delegate.
1216
     */
1217
    public function appendUserLinks()
1218
    {
1219
        $ul = new XMLElement('ul', null, array('id' => 'session'));
1220
1221
        $li = new XMLElement('li');
1222
        $li->appendChild(
1223
            Widget::Anchor(
1224
                Symphony::Author()->getFullName(),
1225
                SYMPHONY_URL . '/system/authors/edit/' . Symphony::Author()->get('id') . '/'
1226
            )
1227
        );
1228
        $ul->appendChild($li);
1229
1230
        $li = new XMLElement('li');
1231
        $li->appendChild(Widget::Anchor(__('Log out'), SYMPHONY_URL . '/logout/', null, null, null, array('accesskey' => 'l')));
1232
        $ul->appendChild($li);
1233
1234
        $this->Header->appendChild($ul);
1235
    }
1236
}
1237