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 — master ( 4ccb09...2f9dc2 )
by Nicolas
03:16
created

addTimestampValidationPageAlert()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 40
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 29
nc 8
nop 3
dl 0
loc 40
rs 8.5806
c 0
b 0
f 0
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 == 'single' ? 'single' : '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 "index" classes
139
        if (!isset($this->_context['page']) || $this->_context['page'] !== 'index' || $class !== 'index') {
140
            $this->_body_class .= $class;
141
        }
142
    }
143
144
    /**
145
     * Accessor for `$this->_context` which includes contextual information
146
     * about the current page such as the class, file location or page root.
147
     * This information varies depending on if the page is provided by an
148
     * extension, is for the publish area, is the login page or any other page
149
     *
150
     * @since Symphony 2.3
151
     * @return array
152
     */
153
    public function getContext()
154
    {
155
        return $this->_context;
156
    }
157
158
    /**
159
     * Given a `$message` and an optional `$type`, this function will
160
     * add an Alert instance into this page's `$this->Alert` property.
161
     * Since Symphony 2.3, there may be more than one `Alert` per page.
162
     * Unless the Alert is an Error, it is required the `$message` be
163
     * passed to this function.
164
     *
165
     * @param string $message
166
     *  The message to display to users
167
     * @param string $type
168
     *  An Alert constant, being `Alert::NOTICE`, `Alert::ERROR` or
169
     *  `Alert::SUCCESS`. The differing types will show the error
170
     *  in a different style in the backend. If omitted, this defaults
171
     *  to `Alert::NOTICE`.
172
     * @throws Exception
173
     */
174
    public function pageAlert($message = null, $type = Alert::NOTICE)
175
    {
176
        if (is_null($message) && $type == Alert::ERROR) {
177
            $message = __('There was a problem rendering this page. Please check the activity log for more details.');
178
        } else {
179
            $message = __($message);
180
        }
181
182
        if (strlen(trim($message)) == 0) {
183
            throw new Exception(__('A message must be supplied unless the alert is of type Alert::ERROR'));
184
        }
185
186
        $this->Alert[] = new Alert($message, $type);
187
    }
188
189
    /**
190
     * Appends the heading of this Symphony page to the Context element.
191
     * Action buttons can be provided (e.g. "Create new") as second parameter.
192
     *
193
     * @since Symphony 2.3
194
     * @param string $value
195
     *  The heading text
196
     * @param array|XMLElement|string $actions
197
     *  Some contextual actions to append to the heading, they can be provided as
198
     *  an array of XMLElements or strings. Traditionally Symphony uses this to append
199
     *  a "Create new" link to the Context div.
200
     */
201
    public function appendSubheading($value, $actions = null)
202
    {
203
        if (!is_array($actions) && $actions) { // Backward compatibility
204
            $actions = array($actions);
205
        }
206
207
        if (!empty($actions)) {
208
            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...
209
                $this->insertAction($a);
210
            }
211
        }
212
213
        $this->Breadcrumbs->appendChild(new XMLElement('h2', $value, array('role' => 'heading', 'id' => 'symphony-subheading')));
214
    }
215
216
    /**
217
     * This function allows a user to insert an Action button to the page.
218
     * It accepts an `XMLElement` (which should be of the `Anchor` type),
219
     * an optional parameter `$prepend`, which when `true` will add this
220
     * action before any existing actions.
221
     *
222
     * @since Symphony 2.3
223
     * @see core.Widget#Anchor
224
     * @param XMLElement $action
225
     *  An Anchor element to add to the top of the page.
226
     * @param boolean $append
227
     *  If true, this will add the `$action` after existing actions, otherwise
228
     *  it will be added before existing actions. By default this is `true`,
229
     *  which will add the `$action` after current actions.
230
     */
231
    public function insertAction(XMLElement $action, $append = true)
232
    {
233
        $actions = $this->Context->getChildrenByName('ul');
234
235
        // Actions haven't be added yet, create the element
236
        if (empty($actions)) {
237
            $ul = new XMLElement('ul', null, array('class' => 'actions'));
238
            $this->Context->appendChild($ul);
239
        } else {
240
            $ul = current($actions);
241
            $this->Context->replaceChildAt(1, $ul);
242
        }
243
244
        $li = new XMLElement('li', $action);
245
246
        if ($append) {
247
            $ul->prependChild($li);
248
        } else {
249
            $ul->appendChild($li);
250
        }
251
    }
252
253
    /**
254
     * Allows developers to specify a list of nav items that build the
255
     * path to the current page or, in jargon, "breadcrumbs".
256
     *
257
     * @since Symphony 2.3
258
     * @param array $values
259
     *  An array of `XMLElement`'s or strings that compose the path. If breadcrumbs
260
     *  already exist, any new item will be appended to the rightmost part of the
261
     *  path.
262
     */
263
    public function insertBreadcrumbs(array $values)
264
    {
265
        if (empty($values)) {
266
            return;
267
        }
268
269
        if ($this->Breadcrumbs instanceof XMLELement && count($this->Breadcrumbs->getChildrenByName('nav')) === 1) {
270
            $nav = $this->Breadcrumbs->getChildrenByName('nav');
271
            $nav = $nav[0];
272
273
            $p = $nav->getChild(0);
274
        } else {
275
            $p = new XMLElement('p');
276
            $nav = new XMLElement('nav');
277
            $nav->appendChild($p);
278
279
            $this->Breadcrumbs->prependChild($nav);
280
        }
281
282
        foreach ($values as $v) {
283
            $p->appendChild($v);
284
            $p->appendChild(new XMLElement('span', '&#8250;', array('class' => 'sep')));
285
        }
286
    }
287
288
    /**
289
     * Allows a Drawer element to added to the backend page in one of three
290
     * positions, `horizontal`, `vertical-left` or `vertical-right`. The button
291
     * to trigger the visibility of the drawer will be added after existing
292
     * actions by default.
293
     *
294
     * @since Symphony 2.3
295
     * @see core.Widget#Drawer
296
     * @param XMLElement $drawer
297
     *  An XMLElement representing the drawer, use `Widget::Drawer` to construct
298
     * @param string $position
299
     *  Where `$position` can be `horizontal`, `vertical-left` or
300
     *  `vertical-right`. Defaults to `horizontal`.
301
     * @param string $button
302
     *  If not passed, a button to open/close the drawer will not be added
303
     *  to the interface. Accepts 'prepend' or 'append' values, which will
304
     *  add the button before or after existing buttons. Defaults to `prepend`.
305
     *  If any other value is passed, no button will be added.
306
     * @throws InvalidArgumentException
307
     */
308
    public function insertDrawer(XMLElement $drawer, $position = 'horizontal', $button = 'append')
309
    {
310
        $drawer->addClass($position);
311
        $drawer->setAttribute('data-position', $position);
312
        $drawer->setAttribute('role', 'complementary');
313
        $this->Drawer[$position][] = $drawer;
314
315
        if (in_array($button, array('prepend', 'append'))) {
316
            $this->insertAction(
317
                Widget::Anchor(
318
                    $drawer->getAttribute('data-label'),
319
                    '#' . $drawer->getAttribute('id'),
320
                    null,
321
                    'button drawer ' . $position
322
                ),
323
                ($button === 'append' ? true : false)
324
            );
325
        }
326
    }
327
328
    /**
329
     * This function initialises a lot of the basic elements that make up a Symphony
330
     * backend page such as the default stylesheets and scripts, the navigation and
331
     * the footer. Any alerts are also appended by this function. `view()` is called to
332
     * build the actual content of the page. The `InitialiseAdminPageHead` delegate
333
     * allows extensions to add elements to the `<head>`. The `CanAccessPage` delegate
334
     * allows extensions to restrict access to pages.
335
     *
336
     * @see view()
337
     * @uses InitialiseAdminPageHead
338
     * @uses CanAccessPage
339
     * @param array $context
340
     *  An associative array describing this pages context. This
341
     *  can include the section handle, the current entry_id, the page
342
     *  name and any flags such as 'saved' or 'created'. This list is not exhaustive
343
     *  and extensions can add their own keys to the array.
344
     * @throws InvalidArgumentException
345
     * @throws SymphonyErrorPage
346
     */
347
    public function build(array $context = array())
348
    {
349
        $this->_context = $context;
350
351
        if (!$this->canAccessPage()) {
352
            Administration::instance()->throwCustomError(
353
                __('You are not authorised to access this page.'),
354
                __('Access Denied'),
355
                Page::HTTP_STATUS_UNAUTHORIZED
356
            );
357
        }
358
359
        $this->Html->setDTD('<!DOCTYPE html>');
360
        $this->Html->setAttribute('lang', Lang::get());
361
        $this->addElementToHead(new XMLElement('meta', null, array('charset' => 'UTF-8')), 0);
362
        $this->addElementToHead(new XMLElement('meta', null, array('http-equiv' => 'X-UA-Compatible', 'content' => 'IE=edge,chrome=1')), 1);
363
        $this->addElementToHead(new XMLElement('meta', null, array('name' => 'viewport', 'content' => 'width=device-width, initial-scale=1')), 2);
364
365
        // Add styles
366
        $this->addStylesheetToHead(ASSETS_URL . '/css/symphony.min.css', 'screen', 2, false);
367
368
        // Calculate timezone offset from UTC
369
        $timezone = new DateTimeZone(DateTimeObj::getSetting('timezone'));
370
        $datetime = new DateTime('now', $timezone);
371
        $timezoneOffset = intval($timezone->getOffset($datetime)) / 60;
372
373
        // Add scripts
374
        $environment = array(
375
376
            'root'     => URL,
377
            'symphony' => SYMPHONY_URL,
378
            'path'     => '/' . Symphony::Configuration()->get('admin-path', 'symphony'),
379
            'route'    => getCurrentPage(),
380
            'version'  => Symphony::Configuration()->get('version', 'symphony'),
381
            'lang'     => Lang::get(),
382
            'user'     => array(
383
384
                'fullname' => Symphony::Author()->getFullName(),
385
                'name'     => Symphony::Author()->get('first_name'),
386
                'type'     => Symphony::Author()->get('user_type'),
387
                'id'       => Symphony::Author()->get('id')
388
            ),
389
            'datetime' => array(
390
391
                'formats'         => DateTimeObj::getDateFormatMappings(),
392
                'timezone-offset' => $timezoneOffset
393
            ),
394
            'env' => array_merge(
395
396
                array('page-namespace' => Symphony::getPageNamespace()),
397
                $this->_context
398
            )
399
        );
400
401
        $this->addElementToHead(
402
            new XMLElement('script', json_encode($environment), array(
403
                'type' => 'application/json',
404
                'id' => 'environment'
405
            )),
406
            4
407
        );
408
409
        $this->addScriptToHead(ASSETS_URL . '/js/symphony.min.js', 6, false);
410
411
        // Initialise page containers
412
        $this->Wrapper = new XMLElement('div', null, array('id' => 'wrapper'));
413
        $this->Header = new XMLElement('header', null, array('id' => 'header'));
414
        $this->Context = new XMLElement('div', null, array('id' => 'context'));
415
        $this->Breadcrumbs = new XMLElement('div', null, array('id' => 'breadcrumbs'));
416
        $this->Contents = new XMLElement('div', null, array('id' => 'contents', 'role' => 'main'));
417
        $this->Form = Widget::Form(Administration::instance()->getCurrentPageURL(), 'post', null, null, array('role' => 'form'));
418
419
        /**
420
         * Allows developers to insert items into the page HEAD. Use
421
         * `Administration::instance()->Page` for access to the page object.
422
         *
423
         * @since In Symphony 2.3.2 this delegate was renamed from
424
         *  `InitaliseAdminPageHead` to the correct spelling of
425
         *  `InitialiseAdminPageHead`. The old delegate is supported
426
         *  until Symphony 3.0
427
         *
428
         * @delegate InitialiseAdminPageHead
429
         * @param string $context
430
         *  '/backend/'
431
         */
432
        Symphony::ExtensionManager()->notifyMembers('InitialiseAdminPageHead', '/backend/');
433
        Symphony::ExtensionManager()->notifyMembers('InitaliseAdminPageHead', '/backend/');
434
435
        $this->addHeaderToPage('Content-Type', 'text/html; charset=UTF-8');
436
        $this->addHeaderToPage('Cache-Control', 'no-cache, must-revalidate, max-age=0');
437
        $this->addHeaderToPage('Expires', 'Mon, 12 Dec 1982 06:14:00 GMT');
438
        $this->addHeaderToPage('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT');
439
        $this->addHeaderToPage('Pragma', 'no-cache');
440
441
        // If not set by another extension, lock down the backend
442
        if (!array_key_exists('x-frame-options', $this->headers())) {
443
            $this->addHeaderToPage('X-Frame-Options', 'SAMEORIGIN');
444
        }
445
446
        if (!array_key_exists('x-content-type-options', $this->headers())) {
447
            $this->addHeaderToPage('X-Content-Type-Options', 'nosniff');
448
        }
449
450
        if (!array_key_exists('x-xss-protection', $this->headers())) {
451
            $this->addHeaderToPage('X-XSS-Protection', '1; mode=block');
452
        }
453
454
        if (!array_key_exists('referrer-policy', $this->headers())) {
455
            $this->addHeaderToPage('Referrer-Policy', 'same-origin');
456
        }
457
458
        if (isset($_REQUEST['action'])) {
459
            $this->action();
460
            Symphony::Profiler()->sample('Page action run', PROFILE_LAP);
461
        }
462
463
        $h1 = new XMLElement('h1');
464
        $h1->appendChild(Widget::Anchor(Symphony::Configuration()->get('sitename', 'general'), rtrim(URL, '/') . '/'));
465
        $this->Header->appendChild($h1);
466
467
        $this->appendUserLinks();
468
        $this->appendNavigation();
469
470
        // Add Breadcrumbs
471
        $this->Context->prependChild($this->Breadcrumbs);
472
        $this->Contents->appendChild($this->Form);
473
474
        // Validate date time config
475 View Code Duplication
        if (empty(__SYM_DATE_FORMAT__)) {
476
            $this->pageAlert(
477
                __('Your <code>%s</code> file does not define a date format', array(basename(CONFIG))),
478
                Alert::NOTICE
479
            );
480
        }
481 View Code Duplication
        if (empty(__SYM_TIME_FORMAT__)) {
482
            $this->pageAlert(
483
                __('Your <code>%s</code> file does not define a time format.', array(basename(CONFIG))),
484
                Alert::NOTICE
485
            );
486
        }
487
488
        $this->view();
489
490
        $this->appendAlert();
491
492
        Symphony::Profiler()->sample('Page content created', PROFILE_LAP);
493
    }
494
495
    /**
496
     * Checks the current Symphony Author can access the current page.
497
     * This check uses the `ASSETS . /xml/navigation.xml` file to determine
498
     * if the current page (or the current page namespace) can be viewed
499
     * by the currently logged in Author.
500
     *
501
     * @since Symphony 2.7.0
502
     * It fires a delegate, CanAccessPage, to allow extensions to restrict access
503
     * to the current page
504
     *
505
     * @uses CanAccessPage
506
     *
507
     * @link http://github.com/symphonycms/symphony-2/blob/master/symphony/assets/xml/navigation.xml
508
     * @return boolean
509
     *  true if the Author can access the current page, false otherwise
510
     */
511
    public function canAccessPage()
512
    {
513
        $nav = $this->getNavigationArray();
514
        $page = '/' . trim(getCurrentPage(), '/') . '/';
515
516
        $page_limit = 'author';
517
518
        foreach ($nav as $item) {
519
            if (
520
                // If page directly matches one of the children
521
                General::in_array_multi($page, $item['children'])
522
                // If the page namespace matches one of the children (this will usually drop query
523
                // string parameters such as /edit/1/)
524
                || General::in_array_multi(Symphony::getPageNamespace() . '/', $item['children'])
525
            ) {
526
                if (is_array($item['children'])) {
527
                    foreach ($item['children'] as $c) {
528 View Code Duplication
                        if ($c['link'] === $page && isset($c['limit'])) {
529
                            $page_limit = $c['limit'];
530
                            // TODO: break out of the loop here in Symphony 3.0.0
531
                        }
532
                    }
533
                }
534
535
                if (isset($item['limit']) && $page_limit !== 'primary') {
536
                    if ($page_limit === 'author' && $item['limit'] === 'developer') {
537
                        $page_limit = 'developer';
538
                    }
539
                }
540 View Code Duplication
            } elseif (isset($item['link']) && $page === $item['link'] && isset($item['limit'])) {
541
                $page_limit = $item['limit'];
542
            }
543
        }
544
545
        $hasAccess = $this->doesAuthorHaveAccess($page_limit);
546
547
        if ($hasAccess) {
548
            $page_context = $this->getContext();
549
            $section_handle = !isset($page_context['section_handle']) ? null : $page_context['section_handle'];
550
            /**
551
             * Immediately after the core access rules allowed access to this page
552
             * (i.e. not called if the core rules denied it).
553
             * Extension developers must only further restrict access to it.
554
             * Extension developers must also take care of checking the current value
555
             * of the allowed parameter in order to prevent conflicts with other extensions.
556
             * `$context['allowed'] = $context['allowed'] && customLogic();`
557
             *
558
             * @delegate CanAccessPage
559
             * @since Symphony 2.7.0
560
             * @see doesAuthorHaveAccess()
561
             * @param string $context
562
             *  '/backend/'
563
             * @param bool $allowed
564
             *  A flag to further restrict access to the page, passed by reference
565
             * @param string $page_limit
566
             *  The computed page limit for the current page
567
             * @param string $page_url
568
             *  The computed page url for the current page
569
             * @param int $section.id
570
             *  The id of the section for this url
571
             * @param string $section.handle
572
             *  The handle of the section for this url
573
             */
574
            Symphony::ExtensionManager()->notifyMembers('CanAccessPage', '/backend/', array(
575
                'allowed' => &$hasAccess,
576
                'page_limit' => $page_limit,
577
                'page_url' => $page,
578
                'section' => array(
579
                    'id' => !$section_handle ? 0 : SectionManager::fetchIDFromHandle($section_handle),
580
                    'handle' => $section_handle
581
                ),
582
            ));
583
        }
584
585
        return $hasAccess;
586
    }
587
588
    /**
589
     * Given the limit of the current navigation item or page, this function
590
     * returns if the current Author can access that item or not.
591
     *
592
     * @since Symphony 2.5.1
593
     * @param string $item_limit
594
     * @return boolean
595
     */
596
    public function doesAuthorHaveAccess($item_limit = null)
597
    {
598
        $can_access = false;
599
600
        if (!isset($item_limit) || $item_limit === 'author') {
601
            $can_access = true;
602
        } elseif ($item_limit === 'developer' && Symphony::Author()->isDeveloper()) {
603
            $can_access = true;
604
        } elseif ($item_limit === 'manager' && (Symphony::Author()->isManager() || Symphony::Author()->isDeveloper())) {
605
            $can_access = true;
606
        } elseif ($item_limit === 'primary' && Symphony::Author()->isPrimaryAccount()) {
607
            $can_access = true;
608
        }
609
610
        return $can_access;
611
    }
612
613
    /**
614
     * Appends the `$this->Header`, `$this->Context` and `$this->Contents`
615
     * to `$this->Wrapper` before adding the ID and class attributes for
616
     * the `<body>` element. This function will also place any Drawer elements
617
     * in their relevant positions in the page. After this has completed the
618
     * parent `generate()` is called which will convert the `XMLElement`'s
619
     * into strings ready for output.
620
     *
621
     * @see core.HTMLPage#generate()
622
     * @param null $page
623
     * @return string
624
     */
625
    public function generate($page = null)
626
    {
627
        $this->Wrapper->appendChild($this->Header);
628
629
        // Add horizontal drawers (inside #context)
630
        if (isset($this->Drawer['horizontal'])) {
631
            $this->Context->appendChildArray($this->Drawer['horizontal']);
632
        }
633
634
        $this->Wrapper->appendChild($this->Context);
635
636
        // Add vertical-left drawers (between #context and #contents)
637
        if (isset($this->Drawer['vertical-left'])) {
638
            $this->Contents->appendChildArray($this->Drawer['vertical-left']);
639
        }
640
641
        // Add vertical-right drawers (after #contents)
642
        if (isset($this->Drawer['vertical-right'])) {
643
            $this->Contents->appendChildArray($this->Drawer['vertical-right']);
644
        }
645
646
        $this->Wrapper->appendChild($this->Contents);
647
648
        $this->Body->appendChild($this->Wrapper);
649
650
        $this->__appendBodyId();
651
        $this->__appendBodyClass($this->_context);
652
653
        /**
654
         * This is just prior to the page headers being rendered, and is suitable for changing them
655
         * @delegate PreRenderHeaders
656
         * @since Symphony 2.7.0
657
         * @param string $context
658
         * '/backend/'
659
         */
660
        Symphony::ExtensionManager()->notifyMembers('PreRenderHeaders', '/backend/');
661
662
        return parent::generate($page);
663
    }
664
665
    /**
666
     * Uses this pages PHP classname as the `<body>` ID attribute.
667
     * This function removes 'content' from the start of the classname
668
     * and converts all uppercase letters to lowercase and prefixes them
669
     * with a hyphen.
670
     */
671
    private function __appendBodyId()
672
    {
673
        // trim "content" from beginning of class name
674
        $body_id = preg_replace("/^content/", '', get_class($this));
675
676
        // lowercase any uppercase letters and prefix with a hyphen
677
        $body_id = trim(
678
            preg_replace_callback(
679
                "/([A-Z])/",
680
                function($id) {
681
                    return "-" . strtolower($id[0]);
682
                },
683
                $body_id
684
            ),
685
            '-'
686
        );
687
688
        if (!empty($body_id)) {
689
            $this->Body->setAttribute('id', trim($body_id));
690
        }
691
    }
692
693
    /**
694
     * Given the context of the current page, which is an associative
695
     * array, this function will append the values to the page's body as
696
     * classes. If an context value is numeric it will be prepended by 'id-',
697
     * otherwise all classes will be prefixed by the context key.
698
     *
699
     * @param array $context
700
     */
701
    private function __appendBodyClass(array $context = array())
702
    {
703
        $body_class = '';
704
705
        foreach ($context as $key => $value) {
706
            if (is_numeric($value)) {
707
                $value = 'id-' . $value;
708
709
                // Add prefixes to all context values by making the
710
                // class be {key}-{value}. #1397 ^BA
711
            } elseif (!is_numeric($key) && isset($value)) {
712
                // Skip arrays
713
                if (is_array($value)) {
714
                    $value = null;
715
                } else {
716
                    $value = str_replace('_', '-', $key) . '-'. $value;
717
                }
718
            }
719
720
            if ($value !== null) {
721
                $body_class .= trim($value) . ' ';
722
            }
723
        }
724
725
        $classes = array_merge(explode(' ', trim($body_class)), explode(' ', trim($this->_body_class)));
726
        $body_class = trim(implode(' ', $classes));
727
728
        if (!empty($body_class)) {
729
            $this->Body->setAttribute('class', $body_class);
730
        }
731
    }
732
733
    /**
734
     * Called to build the content for the page. This function immediately calls
735
     * `__switchboard()` which acts a bit of a controller to show content based on
736
     * off a type, such as 'view' or 'action'. `AdministrationPages` can override this
737
     * function to just display content if they do not need the switchboard functionality
738
     *
739
     * @see __switchboard()
740
     */
741
    public function view()
742
    {
743
        $this->__switchboard();
744
    }
745
746
    /**
747
     * This function is called when `$_REQUEST` contains a key of 'action'.
748
     * Any logic that needs to occur immediately for the action to complete
749
     * should be contained within this function. By default this calls the
750
     * `__switchboard` with the type set to 'action'.
751
     *
752
     * @see __switchboard()
753
     */
754
    public function action()
755
    {
756
        $this->__switchboard('action');
757
    }
758
759
    /**
760
     * The `__switchboard` function acts as a controller to display content
761
     * based off the $type. By default, the `$type` is 'view' but it can be set
762
     * also set to 'action'. The `$type` is prepended by __ and the context is
763
     * append to the $type to create the name of the function that will provide
764
     * that logic. For example, if the $type was action and the context of the
765
     * current page was new, the resulting function to be called would be named
766
     * `__actionNew()`. If an action function is not provided by the Page, this function
767
     * returns nothing, however if a view function is not provided, a 404 page
768
     * will be returned.
769
     *
770
     * @param string $type
771
     *  Either 'view' or 'action', by default this will be 'view'
772
     * @throws SymphonyErrorPage
773
     */
774
    public function __switchboard($type = 'view')
775
    {
776
        if (!isset($this->_context[0]) || trim($this->_context[0]) === '') {
777
            $context = 'index';
778
        } else {
779
            $context = $this->_context[0];
780
        }
781
782
        $function = ($type == 'action' ? '__action' : '__view') . ucfirst($context);
783
784
        if (!method_exists($this, $function)) {
785
            // If there is no action function, just return without doing anything
786
            if ($type == 'action') {
787
                return;
788
            }
789
790
            Administration::instance()->errorPageNotFound();
791
        }
792
793
        $this->$function(null);
794
    }
795
796
    /**
797
     * If `$this->Alert` is set, it will be added to this page. The
798
     * `AppendPageAlert` delegate is fired to allow extensions to provide their
799
     * their own Alert messages for this page. Since Symphony 2.3, there may be
800
     * more than one `Alert` per page. Alerts are displayed in the order of
801
     * severity, with Errors first, then Success alerts followed by Notices.
802
     *
803
     * @uses AppendPageAlert
804
     */
805
    public function appendAlert()
806
    {
807
        /**
808
         * Allows for appending of alerts. Administration::instance()->Page->Alert is way to tell what
809
         * is currently in the system
810
         *
811
         * @delegate AppendPageAlert
812
         * @param string $context
813
         *  '/backend/'
814
         */
815
        Symphony::ExtensionManager()->notifyMembers('AppendPageAlert', '/backend/');
816
817
818
        if (!is_array($this->Alert) || empty($this->Alert)) {
819
            return;
820
        }
821
822
        usort($this->Alert, array($this, 'sortAlerts'));
823
824
        // Using prependChild ruins our order (it's backwards, but with most
825
        // recent notices coming after oldest notices), so reversing the array
826
        // fixes this. We need to prepend so that without Javascript the notices
827
        // are at the top of the markup. See #1312
828
        $this->Alert = array_reverse($this->Alert);
829
830
        foreach ($this->Alert as $alert) {
831
            $this->Header->prependChild($alert->asXML());
832
        }
833
    }
834
835
    // Errors first, success next, then notices.
836
    public function sortAlerts($a, $b)
837
    {
838
        if ($a->{'type'} === $b->{'type'}) {
839
            return 0;
840
        }
841
842
        if (
843
            ($a->{'type'} === Alert::ERROR && $a->{'type'} !== $b->{'type'})
844
            || ($a->{'type'} === Alert::SUCCESS && $b->{'type'} === Alert::NOTICE)
845
        ) {
846
            return -1;
847
        }
848
849
        return 1;
850
    }
851
852
    /**
853
     * This function will append the Navigation to the AdministrationPage.
854
     * It fires a delegate, NavigationPreRender, to allow extensions to manipulate
855
     * the navigation. Extensions should not use this to add their own navigation,
856
     * they should provide the navigation through their fetchNavigation function.
857
     * Note with the Section navigation groups, if there is only one section in a group
858
     * and that section is set to visible, the group will not appear in the navigation.
859
     *
860
     * @uses NavigationPreRender
861
     * @see getNavigationArray()
862
     * @see toolkit.Extension#fetchNavigation()
863
     */
864
    public function appendNavigation()
865
    {
866
        $nav = $this->getNavigationArray();
867
868
        /**
869
         * Immediately before displaying the admin navigation. Provided with the
870
         * navigation array. Manipulating it will alter the navigation for all pages.
871
         *
872
         * @delegate NavigationPreRender
873
         * @param string $context
874
         *  '/backend/'
875
         * @param array $nav
876
         *  An associative array of the current navigation, passed by reference
877
         */
878
        Symphony::ExtensionManager()->notifyMembers('NavigationPreRender', '/backend/', array(
879
            'navigation' => &$nav,
880
        ));
881
882
        $navElement = new XMLElement('nav', null, array('id' => 'nav', 'role' => 'navigation'));
883
        $contentNav = new XMLElement('ul', null, array('class' => 'content', 'role' => 'menubar'));
884
        $structureNav = new XMLElement('ul', null, array('class' => 'structure', 'role' => 'menubar'));
885
886
        foreach ($nav as $n) {
887
            if (isset($n['visible']) && $n['visible'] === 'no') {
888
                continue;
889
            }
890
891
            $item_limit = isset($n['limit']) ? $n['limit'] : null;
892
893
            if ($this->doesAuthorHaveAccess($item_limit)) {
894
                $xGroup = new XMLElement('li', General::sanitize($n['name']), array('role' => 'presentation'));
895
896
                if (isset($n['class']) && trim($n['name']) !== '') {
897
                    $xGroup->setAttribute('class', $n['class']);
898
                }
899
900
                $hasChildren = false;
901
                $xChildren = new XMLElement('ul', null, array('role' => 'menu'));
902
903
                if (is_array($n['children']) && !empty($n['children'])) {
904
                    foreach ($n['children'] as $c) {
905
                        // adapt for Yes and yes
906
                        if (strtolower($c['visible']) !== 'yes') {
907
                            continue;
908
                        }
909
910
                        $child_item_limit = isset($c['limit']) ? $c['limit'] : null;
911
912
                        if ($this->doesAuthorHaveAccess($child_item_limit)) {
913
                            $xChild = new XMLElement('li');
914
                            $xChild->setAttribute('role', 'menuitem');
915
                            $linkChild = Widget::Anchor(General::sanitize($c['name']), SYMPHONY_URL . $c['link']);
916
                            if (isset($c['target'])) {
917
                                $linkChild->setAttribute('target', $c['target']);
918
                            }
919
                            $xChild->appendChild($linkChild);
920
                            $xChildren->appendChild($xChild);
921
                            $hasChildren = true;
922
                        }
923
                    }
924
925
                    if ($hasChildren) {
926
                        $xGroup->setAttribute('aria-haspopup', 'true');
927
                        $xGroup->appendChild($xChildren);
928
929
                        if ($n['type'] === 'content') {
930
                            $contentNav->appendChild($xGroup);
931
                        } elseif ($n['type'] === 'structure') {
932
                            $structureNav->prependChild($xGroup);
933
                        }
934
                    }
935
                }
936
            }
937
        }
938
939
        $navElement->appendChild($contentNav);
940
        $navElement->appendChild($structureNav);
941
        $this->Header->appendChild($navElement);
942
        Symphony::Profiler()->sample('Navigation Built', PROFILE_LAP);
943
    }
944
945
    /**
946
     * Returns the `$_navigation` variable of this Page. If it is empty,
947
     * it will be built by `__buildNavigation`
948
     *
949
     * When it calls `__buildNavigation`, it fires a delegate, NavigationPostBuild,
950
     * to allow extensions to manipulate the navigation.
951
     *
952
     * @uses NavigationPostBuild
953
     * @see __buildNavigation()
954
     * @return array
955
     */
956
    public function getNavigationArray()
957
    {
958
        if (empty($this->_navigation)) {
959
            $this->__buildNavigation();
960
        }
961
962
        return $this->_navigation;
963
    }
964
965
    /**
966
     * This method fills the `$nav` array with value
967
     * from the `ASSETS/xml/navigation.xml` file
968
     *
969
     * @link http://github.com/symphonycms/symphony-2/blob/master/symphony/assets/xml/navigation.xml
970
     *
971
     * @since Symphony 2.3.2
972
     *
973
     * @param array $nav
974
     *  The navigation array that will receive nav nodes
975
     */
976
    private function buildXmlNavigation(&$nav)
977
    {
978
        $xml = simplexml_load_file(ASSETS . '/xml/navigation.xml');
979
980
        // Loop over the default Symphony navigation file, converting
981
        // it into an associative array representation
982
        foreach ($xml->xpath('/navigation/group') as $n) {
983
            $index = (string)$n->attributes()->index;
984
            $children = $n->xpath('children/item');
985
            $content = $n->attributes();
986
987
            // If the index is already set, increment the index and check again.
988
            // Rinse and repeat until the index is not set.
989
            if (isset($nav[$index])) {
990
                do {
991
                    $index++;
992
                } while (isset($nav[$index]));
993
            }
994
995
            $nav[$index] = array(
996
                'name' => __(strval($content->name)),
997
                'type' => 'structure',
998
                'index' => $index,
999
                'children' => array()
1000
            );
1001
1002
            if (strlen(trim((string)$content->limit)) > 0) {
1003
                $nav[$index]['limit'] = (string)$content->limit;
1004
            }
1005
1006
            if (count($children) > 0) {
1007
                foreach ($children as $child) {
1008
                    $item = array(
1009
                        'link' => (string)$child->attributes()->link,
1010
                        'name' => __(strval($child->attributes()->name)),
1011
                        'visible' => ((string)$child->attributes()->visible == 'no' ? 'no' : 'yes'),
1012
                    );
1013
1014
                    $limit = (string)$child->attributes()->limit;
1015
1016
                    if (strlen(trim($limit)) > 0) {
1017
                        $item['limit'] = $limit;
1018
                    }
1019
1020
                    $nav[$index]['children'][] = $item;
1021
                }
1022
            }
1023
        }
1024
    }
1025
1026
    /**
1027
     * This method fills the `$nav` array with value
1028
     * from each Section
1029
     *
1030
     * @since Symphony 2.3.2
1031
     *
1032
     * @param array $nav
1033
     *  The navigation array that will receive nav nodes
1034
     */
1035
    private function buildSectionNavigation(&$nav)
1036
    {
1037
        // Build the section navigation, grouped by their navigation groups
1038
        $sections = SectionManager::fetch(null, 'asc', 'sortorder');
1039
1040
        if (is_array($sections) && !empty($sections)) {
1041
            foreach ($sections as $s) {
1042
                $group_index = self::__navigationFindGroupIndex($nav, $s->get('navigation_group'));
1043
1044
                if ($group_index === false) {
1045
                    $group_index = General::array_find_available_index($nav, 0);
1046
1047
                    $nav[$group_index] = array(
1048
                        'name' => $s->get('navigation_group'),
1049
                        'type' => 'content',
1050
                        'index' => $group_index,
1051
                        'children' => array()
1052
                    );
1053
                }
1054
1055
                $hasAccess = true;
1056
                $url = '/publish/' . $s->get('handle') . '/';
1057
                /**
1058
                 * Immediately after the core access rules allowed access to this page
1059
                 * (i.e. not called if the core rules denied it).
1060
                 * Extension developers must only further restrict access to it.
1061
                 * Extension developers must also take care of checking the current value
1062
                 * of the allowed parameter in order to prevent conflicts with other extensions.
1063
                 * `$context['allowed'] = $context['allowed'] && customLogic();`
1064
                 *
1065
                 * @delegate CanAccessPage
1066
                 * @since Symphony 2.7.0
1067
                 * @see doesAuthorHaveAccess()
1068
                 * @param string $context
1069
                 *  '/backend/'
1070
                 * @param bool $allowed
1071
                 *  A flag to further restrict access to the page, passed by reference
1072
                 * @param string $page_limit
1073
                 *  The computed page limit for the current page
1074
                 * @param string $page_url
1075
                 *  The computed page url for the current page
1076
                 * @param int $section.id
1077
                 *  The id of the section for this url
1078
                 * @param string $section.handle
1079
                 *  The handle of the section for this url
1080
                 */
1081
                Symphony::ExtensionManager()->notifyMembers('CanAccessPage', '/backend/', array(
1082
                    'allowed' => &$hasAccess,
1083
                    'page_limit' => 'author',
1084
                    'page_url' => $url,
1085
                    'section' => array(
1086
                        'id' => $s->get('id'),
1087
                        'handle' => $s->get('handle')
1088
                    ),
1089
                ));
1090
1091
                if ($hasAccess) {
1092
                    $nav[$group_index]['children'][] = array(
1093
                        'link' => $url,
1094
                        'name' => $s->get('name'),
1095
                        'type' => 'section',
1096
                        'section' => array(
1097
                            'id' => $s->get('id'),
1098
                            'handle' => $s->get('handle')
1099
                        ),
1100
                        'visible' => ($s->get('hidden') == 'no' ? 'yes' : 'no')
1101
                    );
1102
                }
1103
            }
1104
        }
1105
    }
1106
1107
    /**
1108
     * This method fills the `$nav` array with value
1109
     * from each Extension's `fetchNavigation` method
1110
     *
1111
     * @since Symphony 2.3.2
1112
     *
1113
     * @param array $nav
1114
     *  The navigation array that will receive nav nodes
1115
     * @throws Exception
1116
     * @throws SymphonyErrorPage
1117
     */
1118
    private function buildExtensionsNavigation(&$nav)
1119
    {
1120
        // Loop over all the installed extensions to add in other navigation items
1121
        $extensions = Symphony::ExtensionManager()->listInstalledHandles();
1122
1123
        foreach ($extensions as $e) {
1124
            $extension = Symphony::ExtensionManager()->getInstance($e);
1125
            $extension_navigation = $extension->fetchNavigation();
1126
1127
            if (is_array($extension_navigation) && !empty($extension_navigation)) {
1128
                foreach ($extension_navigation as $item) {
1129
                    $type = isset($item['children']) ? Extension::NAV_GROUP : Extension::NAV_CHILD;
1130
1131
                    switch ($type) {
1132
                        case Extension::NAV_GROUP:
1133
                            $index = General::array_find_available_index($nav, $item['location']);
1134
1135
                            // Actual group
1136
                            $nav[$index] = self::createParentNavItem($index, $item);
1137
1138
                            // Render its children
1139
                            foreach ($item['children'] as $child) {
1140
                                $nav[$index]['children'][] = self::createChildNavItem($child, $e);
1141
                            }
1142
1143
                            break;
1144
1145
                        case Extension::NAV_CHILD:
1146
                            if (!is_numeric($item['location'])) {
1147
                                // is a navigation group
1148
                                $group_name = $item['location'];
1149
                                $group_index = self::__navigationFindGroupIndex($nav, $item['location']);
1150
                            } else {
1151
                                // is a legacy numeric index
1152
                                $group_index = $item['location'];
1153
                            }
1154
1155
                            $child = self::createChildNavItem($item, $e);
1156
1157
                            if ($group_index === false) {
1158
                                $group_index = General::array_find_available_index($nav, 0);
1159
1160
                                $nav_parent = self::createParentNavItem($group_index, $item);
1161
                                $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...
1162
                                $nav_parent['children'] = array($child);
1163
1164
                                // add new navigation group
1165
                                $nav[$group_index] = $nav_parent;
1166
                            } else {
1167
                                // add new location by index
1168
                                $nav[$group_index]['children'][] = $child;
1169
                            }
1170
1171
                            break;
1172
                    }
1173
                }
1174
            }
1175
        }
1176
    }
1177
1178
    /**
1179
     * This function builds out a navigation menu item for parents. Parents display
1180
     * in the top level navigation of the backend and may have children (dropdown menus)
1181
     *
1182
     * @since Symphony 2.5.1
1183
     * @param integer $index
1184
     * @param array $item
1185
     * @return array
1186
     */
1187
    private static function createParentNavItem($index, $item)
1188
    {
1189
        $nav_item = array(
1190
            'name' => $item['name'],
1191
            'type' => isset($item['type']) ? $item['type'] : 'structure',
1192
            'index' => $index,
1193
            'children' => array(),
1194
            'limit' => isset($item['limit']) ? $item['limit'] : null
1195
        );
1196
1197
        return $nav_item;
1198
    }
1199
1200
    /**
1201
     * This function builds out a navigation menu item for children. Children
1202
     * live under a parent navigation item and are shown on hover.
1203
     *
1204
     * @since Symphony 2.5.1
1205
     * @param array $item
1206
     * @param string $extension_handle
1207
     * @return array
1208
     */
1209
    private static function createChildNavItem($item, $extension_handle)
1210
    {
1211
        if (!isset($item['relative']) || $item['relative'] === true) {
1212
            $link = '/extension/' . $extension_handle . '/' . ltrim($item['link'], '/');
1213
        } else {
1214
            $link = '/' . ltrim($item['link'], '/');
1215
        }
1216
1217
        $nav_item = array(
1218
            'link' => $link,
1219
            'name' => $item['name'],
1220
            'visible' => (isset($item['visible']) && $item['visible'] == 'no') ? 'no' : 'yes',
1221
            'limit' => isset($item['limit']) ? $item['limit'] : null,
1222
            'target' => isset($item['target']) ? $item['target'] : null
1223
        );
1224
1225
        return $nav_item;
1226
    }
1227
1228
    /**
1229
     * This function populates the `$_navigation` array with an associative array
1230
     * of all the navigation groups and their links. Symphony only supports one
1231
     * level of navigation, so children links cannot have children links. The default
1232
     * Symphony navigation is found in the `ASSETS/xml/navigation.xml` folder. This is
1233
     * loaded first, and then the Section navigation is built, followed by the Extension
1234
     * navigation. Additionally, this function will set the active group of the navigation
1235
     * by checking the current page against the array of links.
1236
     *
1237
     * It fires a delegate, NavigationPostBuild, to allow extensions to manipulate
1238
     * the navigation.
1239
     *
1240
     * @uses NavigationPostBuild
1241
     * @link https://github.com/symphonycms/symphony-2/blob/master/symphony/assets/xml/navigation.xml
1242
     * @link https://github.com/symphonycms/symphony-2/blob/master/symphony/lib/toolkit/class.extension.php
1243
     */
1244
    public function __buildNavigation()
1245
    {
1246
        $nav = array();
1247
1248
        $this->buildXmlNavigation($nav);
1249
        $this->buildSectionNavigation($nav);
1250
        $this->buildExtensionsNavigation($nav);
1251
1252
        $pageCallback = Administration::instance()->getPageCallback();
1253
1254
        $pageRoot = $pageCallback['pageroot'] . (isset($pageCallback['context'][0]) ? $pageCallback['context'][0] . '/' : '');
1255
        $found = self::__findActiveNavigationGroup($nav, $pageRoot);
1256
1257
        // Normal searches failed. Use a regular expression using the page root. This is less
1258
        // efficient and should never really get invoked unless something weird is going on
1259
        if (!$found) {
1260
            self::__findActiveNavigationGroup($nav, '/^' . str_replace('/', '\/', $pageCallback['pageroot']) . '/i', true);
1261
        }
1262
1263
        ksort($nav);
1264
        $this->_navigation = $nav;
1265
1266
        /**
1267
         * Immediately after the navigation array as been built. Provided with the
1268
         * navigation array. Manipulating it will alter the navigation for all pages.
1269
         * Developers can also alter the 'limit' property of each page to allow more
1270
         * or less access to them.
1271
         * Preventing a user from accessing the page affects both the navigation and the
1272
         * page access rights: user will get a 403 Forbidden error if not authorized.
1273
         *
1274
         * @delegate NavigationPostBuild
1275
         * @since Symphony 2.7.0
1276
         * @param string $context
1277
         *  '/backend/'
1278
         * @param array $nav
1279
         *  An associative array of the current navigation, passed by reference
1280
         */
1281
        Symphony::ExtensionManager()->notifyMembers('NavigationPostBuild', '/backend/', array(
1282
            'navigation' => &$this->_navigation,
1283
        ));
1284
    }
1285
1286
    /**
1287
     * Given an associative array representing the navigation, and a group,
1288
     * this function will attempt to return the index of the group in the navigation
1289
     * array. If it is found, it will return the index, otherwise it will return false.
1290
     *
1291
     * @param array $nav
1292
     *  An associative array of the navigation where the key is the group
1293
     *  index, and the value is an associative array of 'name', 'index' and
1294
     *  'children'. Name is the name of the this group, index is the same as
1295
     *  the key and children is an associative array of navigation items containing
1296
     *  the keys 'link', 'name' and 'visible'. The 'haystack'.
1297
     * @param string $group
1298
     *  The group name to find, the 'needle'.
1299
     * @return integer|boolean
1300
     *  If the group is found, the index will be returned, otherwise false.
1301
     */
1302
    private static function __navigationFindGroupIndex(array $nav, $group)
1303
    {
1304
        foreach ($nav as $index => $item) {
1305
            if ($item['name'] === $group) {
1306
                return $index;
1307
            }
1308
        }
1309
1310
        return false;
1311
    }
1312
1313
    /**
1314
     * Given the navigation array, this function will loop over all the items
1315
     * to determine which is the 'active' navigation group, or in other words,
1316
     * what group best represents the current page `$this->Author` is viewing.
1317
     * This is done by checking the current page's link against all the links
1318
     * provided in the `$nav`, and then flagging the group of the found link
1319
     * with an 'active' CSS class. The current page's link omits any flags or
1320
     * URL parameters and just uses the root page URL.
1321
     *
1322
     * @param array $nav
1323
     *  An associative array of the navigation where the key is the group
1324
     *  index, and the value is an associative array of 'name', 'index' and
1325
     *  'children'. Name is the name of the this group, index is the same as
1326
     *  the key and children is an associative array of navigation items containing
1327
     *  the keys 'link', 'name' and 'visible'. The 'haystack'. This parameter is passed
1328
     *  by reference to this function.
1329
     * @param string $pageroot
1330
     *  The current page the Author is the viewing, minus any flags or URL
1331
     *  parameters such as a Symphony object ID. eg. Section ID, Entry ID. This
1332
     *  parameter is also be a regular expression, but this is highly unlikely.
1333
     * @param boolean $pattern
1334
     *  If set to true, the `$pageroot` represents a regular expression which will
1335
     *  determine if the active navigation item
1336
     * @return boolean
1337
     *  Returns true if an active link was found, false otherwise. If true, the
1338
     *  navigation group of the active link will be given the CSS class 'active'
1339
     */
1340
    private static function __findActiveNavigationGroup(array &$nav, $pageroot, $pattern = false)
1341
    {
1342
        foreach ($nav as $index => $contents) {
1343
            if (is_array($contents['children']) && !empty($contents['children'])) {
1344
                foreach ($contents['children'] as $item) {
1345
                    if ($pattern && preg_match($pageroot, $item['link'])) {
1346
                        $nav[$index]['class'] = 'active';
1347
                        return true;
1348
                    } elseif ($item['link'] == $pageroot) {
1349
                        $nav[$index]['class'] = 'active';
1350
                        return true;
1351
                    }
1352
                }
1353
            }
1354
        }
1355
1356
        return false;
1357
    }
1358
1359
    /**
1360
     * Creates the Symphony footer for an Administration page. By default
1361
     * this includes the installed Symphony version and the currently logged
1362
     * in Author. A delegate is provided to allow extensions to manipulate the
1363
     * footer HTML, which is an XMLElement of a `<ul>` element.
1364
     * Since Symphony 2.3, it no longer uses the `AddElementToFooter` delegate.
1365
     */
1366
    public function appendUserLinks()
1367
    {
1368
        $ul = new XMLElement('ul', null, array('id' => 'session'));
1369
1370
        $li = new XMLElement('li');
1371
        $li->appendChild(
1372
            Widget::Anchor(
1373
                Symphony::Author()->getFullName(),
1374
                SYMPHONY_URL . '/system/authors/edit/' . Symphony::Author()->get('id') . '/'
1375
            )
1376
        );
1377
        $ul->appendChild($li);
1378
1379
        $li = new XMLElement('li');
1380
        $li->appendChild(Widget::Anchor(__('Log out'), SYMPHONY_URL . '/logout/', null, null, null, array('accesskey' => 'l')));
1381
        $ul->appendChild($li);
1382
1383
        $this->Header->appendChild($ul);
1384
    }
1385
1386
    /**
1387
     * Adds a localized Alert message for failed timestamp validations.
1388
     * It also adds meta information about the last author and timestamp.
1389
     *
1390
     * @since Symphony 2.7.0
1391
     * @param string $errorMessage
1392
     *  The error message to display.
1393
     * @param Entry|Section $existingObject
1394
     *  The Entry or section object that failed validation.
1395
     * @param string $action
1396
     *  The requested action.
1397
     */
1398
    public function addTimestampValidationPageAlert($errorMessage, $existingObject, $action)
0 ignored issues
show
Unused Code introduced by
The parameter $errorMessage is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1399
    {
1400
        $authorId = $existingObject->get('modification_author_id');
1401
        if (!$authorId) {
1402
            $authorId = $existingObject->get('author_id');
1403
        }
1404
        $author = AuthorManager::fetchByID($authorId);
1405
        $formatteAuthorName = $authorId === Symphony::Author()->get('id')
1406
            ? __('yourself')
1407
            : (!$author
1408
                ? __('an unknown user')
1409
                : $author->get('first_name') . ' ' . $author->get('last_name'));
1410
1411
        $msg = $this->_errors['timestamp'] . ' ' . __(
0 ignored issues
show
Bug introduced by
The property _errors does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
1412
            'made by %s at %s.', array(
1413
                $formatteAuthorName,
1414
                Widget::Time($existingObject->get('modification_date'))->generate(),
1415
            )
1416
        );
1417
1418
        $currentUrl = Administration::instance()->getCurrentPageURL();
1419
        $overwritelink = Widget::Anchor(
1420
            __('Replace changes?'),
1421
            $currentUrl,
1422
            __('Overwrite'),
1423
            'js-tv-overwrite',
1424
            null,
1425
            array(
1426
                'data-action' => General::sanitize($action)
1427
            )
1428
        );
1429
        $ignorelink = Widget::Anchor(
1430
            __('View changes.'),
1431
            $currentUrl,
1432
            __('View the updated entry')
1433
        );
1434
        $actions = $overwritelink->generate() . ' ' . $ignorelink->generate();
1435
        
1436
        $this->pageAlert("$msg $actions", Alert::ERROR);
1437
    }
1438
}
1439