Passed
Push — master ( d8129c...113a04 )
by Chauncey
07:55
created

AdminTemplate::setSiteName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 4
rs 10
1
<?php
2
3
namespace Charcoal\Admin;
4
5
use Exception;
6
use InvalidArgumentException;
7
8
// From PSR-7
9
use Psr\Http\Message\RequestInterface;
10
11
// From Pimple
12
use Pimple\Container;
13
14
// From 'charcoal-factory'
15
use Charcoal\Factory\FactoryInterface;
16
17
// From 'charcoal-user'
18
use Charcoal\User\AuthAwareInterface;
19
use Charcoal\User\AuthAwareTrait;
20
21
// From 'charcoal-translator'
22
use Charcoal\Translator\Translation;
23
use Charcoal\Translator\TranslatorAwareTrait;
24
25
// From 'charcoal-app'
26
use Charcoal\App\Template\AbstractTemplate;
27
28
// From 'charcoal-ui'
29
use Charcoal\Ui\Menu\GenericMenu;
30
use Charcoal\Ui\MenuItem\GenericMenuItem;
31
32
// From 'charcoal-admin'
33
use Charcoal\Admin\Ui\FeedbackContainerTrait;
34
use Charcoal\Admin\Support\AdminTrait;
35
use Charcoal\Admin\Support\BaseUrlTrait;
36
use Charcoal\Admin\Support\SecurityTrait;
37
38
/**
39
 * Base class for all `admin` Templates.
40
 *
41
 * # Available (mustache) methods
42
 * - `title` (Translation) - The page title
43
 * - `subtitle` (Translation) The page subtitle
44
 * - `showHeaderMenu` (bool) - Display the header menu or not
45
 * - `headerMenu` (iterator) - The header menu data
46
 * - `showSystemMenu` (bool) - Display the footer menu or not
47
 * - `systemMenu` (iterator) - The footer menu data
48
 */
49
class AdminTemplate extends AbstractTemplate implements
50
    AuthAwareInterface
51
{
52
    use AdminTrait;
53
    use AuthAwareTrait;
54
    use BaseUrlTrait;
55
    use FeedbackContainerTrait;
56
    use SecurityTrait;
57
    use TranslatorAwareTrait;
58
59
    const GOOGLE_RECAPTCHA_CLIENT_URL = 'https://www.google.com/recaptcha/api.js';
60
61
    /**
62
     * The name of the project.
63
     *
64
     * @var Translation|string|null
65
     */
66
    private $siteName;
67
68
    /**
69
     * @var string $ident
70
     */
71
    private $ident;
72
73
    /**
74
     * @var Translation|string|null $label
75
     */
76
    protected $label;
77
78
    /**
79
     * @var Translation|string|null $title
80
     */
81
    protected $title;
82
83
    /**
84
     * @var Translation|string|null $subtitle
85
     */
86
    protected $subtitle;
87
88
    /**
89
     * @var boolean
90
     */
91
    private $showSidemenu = true;
92
93
    /**
94
     * @var boolean
95
     */
96
    private $showHeaderMenu = true;
97
98
    /**
99
     * @var boolean
100
     */
101
    private $showSystemMenu = true;
102
103
    /**
104
     * @var boolean
105
     */
106
    private $showTopHeaderMenu;
107
108
    /**
109
     * @var boolean
110
     */
111
    protected $headerMenu;
112
113
    /**
114
     * @var boolean
115
     */
116
    protected $systemMenu;
117
118
    /**
119
     * @var SideMenuWidgetInterface
0 ignored issues
show
Bug introduced by
The type Charcoal\Admin\SideMenuWidgetInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
120
     */
121
    protected $sidemenu;
122
123
    /**
124
     * @var FactoryInterface $modelFactory
125
     */
126
    private $modelFactory;
127
128
    /**
129
     * @var FactoryInterface $widgetFactory
130
     */
131
    private $widgetFactory;
132
133
    /**
134
     * Template's init method is called automatically from `charcoal-app`'s Template Route.
135
     *
136
     * For admin templates, initializations is:
137
     *
138
     * - to start a session, if necessary
139
     * - to authenticate
140
     * - to initialize the template data with the PSR Request object
141
     *
142
     * @param RequestInterface $request The request to initialize.
143
     * @return boolean
144
     * @see \Charcoal\App\Route\TemplateRoute::__invoke()
145
     */
146
    public function init(RequestInterface $request)
147
    {
148
        if (!session_id()) {
149
            session_cache_limiter(false);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type string expected by parameter $cache_limiter of session_cache_limiter(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

149
            session_cache_limiter(/** @scrutinizer ignore-type */ false);
Loading history...
150
            session_start();
151
        }
152
153
        $this->setDataFromRequest($request);
154
        $this->authRedirect($request);
155
156
        return parent::init($request);
157
    }
158
159
    /**
160
     * Determine if the current user is authenticated, if not redirect them to the login page.
161
     *
162
     * @todo   Move auth-check and redirection to a middleware or dedicated admin route.
163
     * @param  RequestInterface $request The request to initialize.
164
     * @return void
165
     */
166
    protected function authRedirect(RequestInterface $request)
167
    {
168
        // Test if authentication is required.
169
        if ($this->authRequired() === false) {
0 ignored issues
show
introduced by
The condition $this->authRequired() === false is always false.
Loading history...
170
            return;
171
        }
172
173
        // Test if user is authorized to access this controller
174
        if ($this->isAuthorized() === true) {
175
            return;
176
        }
177
178
        $redirectTo = urlencode($request->getRequestTarget());
179
180
        header('HTTP/1.0 403 Forbidden');
181
        header('Location: '.$this->adminUrl('login?redirect_to='.$redirectTo));
182
        exit;
183
    }
184
185
    /**
186
     * Sets the template data from a PSR Request object.
187
     *
188
     * @param  RequestInterface $request A PSR-7 compatible Request instance.
189
     * @return self
190
     */
191
    protected function setDataFromRequest(RequestInterface $request)
192
    {
193
        $keys = $this->validDataFromRequest();
194
        if (!empty($keys)) {
195
            $this->setData($request->getParams($keys));
0 ignored issues
show
Bug introduced by
The method getParams() does not exist on Psr\Http\Message\RequestInterface. It seems like you code against a sub-type of Psr\Http\Message\RequestInterface such as Slim\Http\Request. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

195
            $this->setData($request->/** @scrutinizer ignore-call */ getParams($keys));
Loading history...
196
        }
197
198
        return $this;
199
    }
200
201
    /**
202
     * Retrieve the list of parameters to extract from the HTTP request.
203
     *
204
     * @return string[]
205
     */
206
    protected function validDataFromRequest()
207
    {
208
        return [
209
            // HTTP Handling
210
            'next_url',
211
            // Navigation Menusa
212
            'header_menu_item', 'side_menu_item', 'system_menu_item',
213
        ];
214
    }
215
216
    /**
217
     * @param mixed $ident Template identifier.
218
     * @return AdminTemplate Chainable
219
     */
220
    public function setIdent($ident)
221
    {
222
        $this->ident = $ident;
223
        return $this;
224
    }
225
226
    /**
227
     * @return string
228
     */
229
    public function ident()
230
    {
231
        return $this->ident;
232
    }
233
234
    /**
235
     * @param mixed $label Template label.
236
     * @return AdminTemplate Chainable
237
     */
238
    public function setLabel($label)
239
    {
240
        $this->label = $this->translator()->translation($label);
241
242
        return $this;
243
    }
244
245
    /**
246
     * @return Translation|string|null
247
     */
248
    public function label()
249
    {
250
        return $this->label;
251
    }
252
253
    /**
254
     * Set the title of the page.
255
     *
256
     * @param  mixed $title Template title.
257
     * @return AdminTemplate Chainable
258
     */
259
    public function setTitle($title)
260
    {
261
        $this->title = $this->translator()->translation($title);
262
263
        return $this;
264
    }
265
266
    /**
267
     * Retrieve the title of the page.
268
     *
269
     * @return Translation|string|null
270
     */
271
    public function title()
272
    {
273
        if ($this->title === null) {
274
            return $this->siteName();
275
        }
276
277
        return $this->title;
278
    }
279
280
    /**
281
     * Set the page's sub-title.
282
     *
283
     * @param mixed $subtitle Template subtitle.
284
     * @return AdminTemplate Chainable
285
     */
286
    public function setSubtitle($subtitle)
287
    {
288
        $this->subtitle = $this->translator()->translation($subtitle);
289
290
        return $this;
291
    }
292
293
    /**
294
     * Retrieve the page's sub-title.
295
     *
296
     * @return Translation|string|null
297
     */
298
    public function subtitle()
299
    {
300
        return $this->subtitle;
301
    }
302
303
    /**
304
     * Display or not the top right header menu.
305
     * @todo This is NOT used yet.
306
     * @param boolean $bool Display or not.
307
     * @return AdminTemplate Chainable.
308
     */
309
    public function setShowTopHeaderMenu($bool)
310
    {
311
        $this->showTopHeaderMenu = $bool;
312
        return $this;
313
    }
314
315
    /**
316
     * @todo Don't take the admin configuration that way...
317
     * @return boolean Show the top header menu or not.
318
     */
319
    public function showTopHeaderMenu()
320
    {
321
        $showTopHeaderMenu = $this->adminConfig('show_top_header_menu');
322
        return $showTopHeaderMenu;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $showTopHeaderMenu also could return the type Charcoal\Admin\Config which is incompatible with the documented return type boolean.
Loading history...
323
    }
324
325
    /**
326
     * Sets the top right header menu.
327
     * @param array $menu Menu as link and labels.
328
     * @return AdminTemplate Chainable.
329
     */
330
    public function setTopHeaderMenu(array $menu)
331
    {
332
        $this->topHeaderMenu = $menu;
0 ignored issues
show
Bug Best Practice introduced by
The property topHeaderMenu does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
333
        return $this;
334
    }
335
336
    /**
337
     * Header menu links and labels.
338
     * @todo To Do.
339
     * @return array The menu.
340
     */
341
    public function topHeaderMenu()
342
    {
343
        return [];
344
    }
345
346
    /**
347
     * @param boolean $show The show header menu flag.
348
     * @return AdminTemplate Chainable
349
     */
350
    public function setShowHeaderMenu($show)
351
    {
352
        $this->showHeaderMenu = !!$show;
353
        return $this;
354
    }
355
356
    /**
357
     * @return boolean
358
     */
359
    public function showHeaderMenu()
360
    {
361
        return ($this->isAuthorized() && $this->showHeaderMenu);
362
    }
363
364
    /**
365
     * Yield the header menu.
366
     *
367
     * @return array|Generator
0 ignored issues
show
Bug introduced by
The type Charcoal\Admin\Generator was not found. Did you mean Generator? If so, make sure to prefix the type with \.
Loading history...
368
     */
369
    public function headerMenu()
370
    {
371
        if ($this->headerMenu === null) {
372
            $this->headerMenu = $this->createHeaderMenu();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->createHeaderMenu() of type Generator is incompatible with the declared type boolean of property $headerMenu.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
373
        }
374
375
        foreach ($this->headerMenu as $menuIdent => $menuItem) {
376
            yield $menuIdent => $menuItem;
377
        }
378
    }
379
380
    /**
381
     * @param boolean $show The show footer menu flag.
382
     * @return AdminTemplate Chainable
383
     */
384
    public function setShowSystemMenu($show)
385
    {
386
        $this->showSystemMenu = !!$show;
387
        return $this;
388
    }
389
390
    /**
391
     * @return boolean
392
     */
393
    public function showSystemMenu()
394
    {
395
        return ($this->isAuthorized() && $this->showSystemMenu && (count($this->systemMenu()) > 0));
396
    }
397
398
    /**
399
     * @return array
400
     */
401
    public function systemMenu()
402
    {
403
        if ($this->systemMenu === null) {
404
            $this->systemMenu = $this->createSystemMenu();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->createSystemMenu() of type Charcoal\Admin\Generator or array is incompatible with the declared type boolean of property $systemMenu.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
405
        }
406
407
        return new \ArrayIterator($this->systemMenu);
0 ignored issues
show
Bug Best Practice introduced by
The expression return new ArrayIterator($this->systemMenu) returns the type ArrayIterator which is incompatible with the documented return type array.
Loading history...
408
    }
409
410
    /**
411
     * @param  boolean $show The show sidemenu flag.
412
     * @return AdminTemplate Chainable
413
     */
414
    public function setShowSidemenu($show)
415
    {
416
        $this->showSidemenu = !!$show;
417
        return $this;
418
    }
419
420
    /**
421
     * @return boolean
422
     */
423
    public function showSidemenu()
424
    {
425
        return ($this->isAuthorized() && $this->showSidemenu);
426
    }
427
428
    /**
429
     * Retrieve the sidemenu.
430
     *
431
     * @return \Charcoal\Admin\Widget\SidemenuWidgetInterface|null
432
     */
433
    public function sidemenu()
434
    {
435
        return $this->sidemenu;
436
    }
437
438
    /**
439
     * @return string
440
     */
441
    public function headerMenuLogo()
442
    {
443
        $logo = $this->adminConfig('menu_logo');
444
        if (!empty($logo)) {
445
            return $logo;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $logo also could return the type Charcoal\Admin\Config which is incompatible with the documented return type string.
Loading history...
446
        }
447
448
        return 'assets/admin/images/identicon.png';
449
    }
450
451
    /**
452
     * Get the "Visit website" label.
453
     *
454
     * @return string|boolean The button's label,
455
     *     TRUE to use the default label,
456
     *     or FALSE to disable the link.
457
     */
458
    public function visitSiteLabel()
459
    {
460
        $label = $this->adminConfig('header_menu.visit_site');
461
        if ($label === false) {
462
            return false;
463
        }
464
465
        if (empty($label) || $label === true) {
466
            $label = $this->translator()->translate('Visit Site');
467
        } else {
468
            $label = $this->translator()->translate($label);
469
        }
470
471
        return $label;
472
    }
473
474
    /**
475
     * Retrieve the name of the project.
476
     *
477
     * @return Translation|string|null
478
     */
479
    public function siteName()
480
    {
481
        return $this->siteName;
482
    }
483
484
    /**
485
     * Retrieve the document title.
486
     *
487
     * @return Translation|string|null
488
     */
489
    public function documentTitle()
490
    {
491
        $siteName  = $this->siteName();
492
        $pageTitle = strip_tags($this->title());
493
494
        if ($pageTitle) {
495
            if ($pageTitle === $siteName) {
496
                return sprintf('%1$s &#8212; Charcoal', $pageTitle);
497
            } else {
498
                return sprintf('%1$s &lsaquo; %2$s &#8212; Charcoal', $pageTitle, $siteName);
499
            }
500
        }
501
502
        return $siteName;
503
    }
504
505
    /**
506
     * Retrieve the current language.
507
     *
508
     * @return string
509
     */
510
    public function lang()
511
    {
512
        return $this->translator()->getLocale();
513
    }
514
515
    /**
516
     * Retrieve the current language.
517
     *
518
     * @return string
519
     */
520
    public function locale()
521
    {
522
        $lang    = $this->lang();
523
        $locales = $this->translator()->locales();
524
525
        if (isset($locales[$lang]['locale'])) {
526
            $locale = $locales[$lang]['locale'];
527
            if (is_array($locale)) {
528
                $locale = implode(' ', $locale);
529
            }
530
        } else {
531
            $locale = 'en-US';
532
        }
533
534
        return $locale;
535
    }
536
537
    /**
538
     * Determine if a CAPTCHA test is available.
539
     *
540
     * For example, the "Login", "Lost Password", and "Reset Password" templates
541
     * can render the CAPTCHA test.
542
     *
543
     * @see    AdminAction::recaptchaEnabled() Duplicate
544
     * @return boolean
545
     */
546
    public function recaptchaEnabled()
547
    {
548
        $recaptcha = $this->apiConfig('google.recaptcha');
549
550
        if (empty($recaptcha) || (isset($recaptcha['active']) && $recaptcha['active'] === false)) {
551
            return false;
552
        }
553
554
        return (!empty($recaptcha['public_key'])  || !empty($recaptcha['key'])) &&
555
               (!empty($recaptcha['private_key']) || !empty($recaptcha['secret']));
556
    }
557
558
    /**
559
     * Determine if the CAPTCHA test is invisible.
560
     *
561
     * Note: Charcoal's implementation of Google reCAPTCHA defaults to "invisible".
562
     *
563
     * @return boolean
564
     */
565
    public function recaptchaInvisible()
566
    {
567
        $recaptcha = $this->apiConfig('google.recaptcha');
568
569
        $hasInvisible = isset($recaptcha['invisible']);
570
        if ($hasInvisible && $recaptcha['invisible'] === true) {
571
            return true;
572
        }
573
574
        $hasSize = isset($recaptcha['size']);
575
        if ($hasSize && $recaptcha['size'] === 'invisible') {
576
            return true;
577
        }
578
579
        if (!$hasInvisible && !$hasSize) {
580
            return true;
581
        }
582
583
        return false;
584
    }
585
586
    /**
587
     * Alias of {@see self::recaptchaSiteKey()}.
588
     *
589
     * @deprecated
590
     * @return string|null
591
     */
592
    public function recaptchaKey()
593
    {
594
        return $this->recaptchaSiteKey();
595
    }
596
597
    /**
598
     * Retrieve the Google reCAPTCHA public (site) key.
599
     *
600
     * @throws RuntimeException If Google reCAPTCHA is required but not configured.
601
     * @return string|null
602
     */
603
    public function recaptchaSiteKey()
604
    {
605
        $recaptcha = $this->apiConfig('google.recaptcha');
606
607
        if (!empty($recaptcha['public_key'])) {
608
            return $recaptcha['public_key'];
609
        } elseif (!empty($recaptcha['key'])) {
610
            return $recaptcha['key'];
611
        }
612
613
        return null;
614
    }
615
616
    /**
617
     * Retrieve the parameters for the Google reCAPTCHA widget.
618
     *
619
     * @return string[]
620
     */
621
    public function recaptchaParameters()
622
    {
623
        $apiConfig = $this->apiConfig('google.recaptcha');
624
        $tplConfig = $this->get('recaptcha_options') ?: [];
625
626
        $params = [
627
            'sitekey'  => $this->recaptchaSiteKey(),
628
            'badge'    => null,
629
            'type'     => null,
630
            'size'     => 'invisible',
631
            'tabindex' => null,
632
            'callback' => null,
633
        ];
634
635
        if ($this->recaptchaInvisible() === false) {
636
            $params['size'] = null;
637
        }
638
639
        foreach ($params as $key => $val) {
640
            if ($val === null || $val === '') {
641
                if (isset($tplConfig[$key])) {
642
                    $val = $tplConfig[$key];
643
                } elseif (isset($apiConfig[$key])) {
644
                    $val = $apiConfig[$key];
645
                }
646
647
                $params[$key] = $val;
648
            }
649
        }
650
651
        return $params;
652
    }
653
654
    /**
655
     * Generate a string representation of HTML attributes for the Google reCAPTCHA tag.
656
     *
657
     * @return string
658
     */
659
    public function recaptchaHtmlAttr()
660
    {
661
        $params = $this->recaptchaParameters();
662
663
        $attributes = [];
664
        foreach ($params as $key => $val) {
665
            if ($val !== null) {
666
                $attributes[] = sprintf('data-%s="%s"', $key, htmlspecialchars($val, ENT_QUOTES));
667
            }
668
        }
669
670
        return implode(' ', $attributes);
671
    }
672
673
    /**
674
     * Set common dependencies (services) used in all admin templates.
675
     *
676
     * @param Container $container DI Container.
677
     * @return void
678
     */
679
    protected function setDependencies(Container $container)
680
    {
681
        parent::setDependencies($container);
682
683
        // Satisfies TranslatorAwareTrait dependencies
684
        $this->setTranslator($container['translator']);
685
686
        // Satisfies AuthAwareInterface + SecurityTrait dependencies
687
        $this->setAuthenticator($container['admin/authenticator']);
688
        $this->setAuthorizer($container['admin/authorizer']);
689
690
        // Satisfies AdminTrait dependencies
691
        $this->setDebug($container['config']);
692
        $this->setAppConfig($container['config']);
693
        $this->setAdminConfig($container['admin/config']);
694
695
        // Satisfies BaseUrlTrait dependencies
696
        $this->setBaseUrl($container['base-url']);
697
        $this->setAdminUrl($container['admin/base-url']);
698
699
        // Satisfies AdminTemplate dependencies
700
        $this->setSiteName($container['config']['project_name']);
701
702
        $this->setModelFactory($container['model/factory']);
703
        $this->setWidgetFactory($container['widget/factory']);
704
705
        $this->menuBuilder = $container['menu/builder'];
0 ignored issues
show
Bug Best Practice introduced by
The property menuBuilder does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
706
        $this->menuItemBuilder = $container['menu/item/builder'];
0 ignored issues
show
Bug Best Practice introduced by
The property menuItemBuilder does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
707
    }
708
709
    /**
710
     * @throws Exception If the factory is not set.
711
     * @return FactoryInterface The model factory.
712
     */
713
    protected function modelFactory()
714
    {
715
        if (!$this->modelFactory) {
716
            throw new Exception(
717
                sprintf('Model factory is not set for template "%s".', get_class($this))
718
            );
719
        }
720
        return $this->modelFactory;
721
    }
722
723
    /**
724
     * @throws Exception If the widget factory dependency was not previously set / injected.
725
     * @return FactoryInterface
726
     */
727
    protected function widgetFactory()
728
    {
729
        if ($this->widgetFactory === null) {
730
            throw new Exception(
731
                'Widget factory was not set.'
732
            );
733
        }
734
        return $this->widgetFactory;
735
    }
736
737
    /**
738
     * Set the name of the project.
739
     *
740
     * @param  string $name Name of the project.
741
     * @return AdminTemplate Chainable
742
     */
743
    protected function setSiteName($name)
744
    {
745
        $this->siteName = $this->translator()->translation($name);
746
        return $this;
747
    }
748
749
    /**
750
     * @param  mixed $options The sidemenu widget ID or config.
751
     * @throws InvalidArgumentException If the menu is missing, invalid, or malformed.
752
     * @return array|Generator
753
     */
754
    protected function createHeaderMenu($options = null)
755
    {
756
        $headerMenu = $this->adminConfig('header_menu');
757
758
        if (!isset($headerMenu['items'])) {
759
            throw new InvalidArgumentException(
760
                'Missing "admin.header_menu.items"'
761
            );
762
        }
763
764
        $mainMenu = null;
765
        if (isset($this['header_menu_item'])) {
766
            $mainMenu = $this['header_menu_item'];
767
        }
768
769
        if (!(empty($options) && !is_numeric($options))) {
770
            if (is_string($options)) {
771
                $mainMenu = $options;
772
            } elseif (is_array($options)) {
773
                if (isset($options['widget_options']['ident'])) {
774
                    $mainMenu = $options['widget_options']['ident'];
775
                }
776
            }
777
        }
778
779
        $mainMenuFromRequest = filter_input(INPUT_GET, 'main_menu', FILTER_SANITIZE_STRING);
780
        if ($mainMenuFromRequest) {
781
            $mainMenu = $mainMenuFromRequest;
782
        }
783
784
        $menu  = $this->menuBuilder->build([]);
785
        foreach ($headerMenu['items'] as $menuIdent => $menuItem) {
786
            $menuItem['menu'] = $menu;
787
            $test = $this->menuItemBuilder->build($menuItem);
788
            if ($test->isAuthorized() === false) {
789
                continue;
790
            }
791
            unset($menuItem['menu']);
792
793
            if (isset($menuItem['active']) && $menuItem['active'] === false) {
794
                continue;
795
            }
796
797
            $menuItem  = $this->parseHeaderMenuItem($menuItem, $menuIdent, $mainMenu);
798
            $menuIdent = $menuItem['ident'];
799
800
            yield $menuIdent => $menuItem;
801
        }
802
    }
803
804
    /**
805
     * @param  mixed $options The sidemenu widget ID or config.
806
     * @throws InvalidArgumentException If the sidemenu widget is invalid.
807
     * @return \Charcoal\Admin\Widget\SidemenuWidgetInterface|null
808
     */
809
    protected function createSidemenu($options = null)
810
    {
811
        if (!is_array($options)) {
812
            $options = [
813
                'widget_options' => [
814
                    'ident' => $options
815
                ]
816
            ];
817
        } elseif (!isset($options['widget_options']['ident'])) {
818
            $options['widget_options']['ident'] = null;
819
        }
820
821
        if (isset($this['side_menu_item'])) {
822
            $options['widget_options']['current_item'] = $this['side_menu_item'];
823
        }
824
825
        if (isset($this['header_menu_item'])) {
826
            $options['widget_options']['ident'] = $this['header_menu_item'];
827
        }
828
829
        $sidemenuFromRequest = filter_input(INPUT_GET, 'side_menu', FILTER_SANITIZE_STRING);
830
        $mainMenuFromRequest = filter_input(INPUT_GET, 'main_menu', FILTER_SANITIZE_STRING);
831
        if ($sidemenuFromRequest) {
832
            $options['widget_options']['ident'] = $sidemenuFromRequest;
833
        } elseif ($mainMenuFromRequest) {
834
            $options['widget_options']['ident'] = $mainMenuFromRequest;
835
        }
836
837
        if (!is_string($options['widget_options']['ident'])) {
838
            return null;
839
        }
840
841
        $GLOBALS['widget_template'] = 'charcoal/admin/widget/sidemenu';
842
843
        if (isset($options['widget_type'])) {
844
            $widgetType = $options['widget_type'];
845
        } else {
846
            $widgetType = 'charcoal/admin/widget/sidemenu';
847
        }
848
849
        $sidemenu = $this->widgetFactory()->create($widgetType);
850
851
        if (isset($options['widget_options'])) {
852
            $sidemenu->setData($options['widget_options']);
853
        }
854
855
        return $sidemenu;
856
    }
857
858
    /**
859
     * @param  mixed $options The sidemenu widget ID or config.
860
     * @throws InvalidArgumentException If the menu is missing, invalid, or malformed.
861
     * @return array|Generator
862
     */
863
    protected function createSystemMenu($options = null)
864
    {
865
        $menuConfig = $this->adminConfig('system_menu');
866
867
        if (!isset($menuConfig['items'])) {
868
            return [];
869
        }
870
871
        $currentIdent = null;
872
        if (isset($this['system_menu_item'])) {
873
            $currentIdent = $this['system_menu_item'];
874
        }
875
876
        if (!(empty($options) && !is_numeric($options))) {
877
            if (is_string($options)) {
878
                $currentIdent = $options;
879
            } elseif (is_array($options)) {
880
                $menuConfig = array_replace_recursive($menuConfig, $options);
0 ignored issues
show
Bug introduced by
It seems like $menuConfig can also be of type Charcoal\Admin\Config; however, parameter $array of array_replace_recursive() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

880
                $menuConfig = array_replace_recursive(/** @scrutinizer ignore-type */ $menuConfig, $options);
Loading history...
881
            }
882
        }
883
884
        $systemMenu = $this->menuBuilder->build([]);
885
        $menuItems  = [];
886
        foreach ($menuConfig['items'] as $menuIdent => $menuItem) {
887
            $menuItem['menu'] = $systemMenu;
888
            $test = $this->menuItemBuilder->build($menuItem);
889
            if ($test->isAuthorized() === false) {
890
                continue;
891
            }
892
            unset($menuItem['menu']);
893
894
            if (isset($menuItem['active']) && $menuItem['active'] === false) {
895
                continue;
896
            }
897
898
            $menuItem  = $this->parseSystemMenuItem($menuItem, $menuIdent, $currentIdent);
899
            $menuIdent = $menuItem['ident'];
900
901
            $menuItems[$menuIdent] = $menuItem;
902
        }
903
        return $menuItems;
904
    }
905
906
    /**
907
     * As a convenience, all admin templates have a model factory to easily create objects.
908
     *
909
     * @param FactoryInterface $factory The factory used to create models.
910
     * @return void
911
     */
912
    private function setModelFactory(FactoryInterface $factory)
913
    {
914
        $this->modelFactory = $factory;
915
    }
916
917
    /**
918
     * @param FactoryInterface $factory The widget factory, to create the dashboard and sidemenu widgets.
919
     * @return void
920
     */
921
    private function setWidgetFactory(FactoryInterface $factory)
922
    {
923
        $this->widgetFactory = $factory;
924
    }
925
926
    /**
927
     * @param  array       $menuItem     The menu structure.
928
     * @param  string|null $menuIdent    The menu identifier.
929
     * @param  string|null $currentIdent The current menu identifier.
930
     * @return array Finalized menu structure.
931
     */
932
    private function parseHeaderMenuItem(array $menuItem, $menuIdent = null, $currentIdent = null)
933
    {
934
        $svgUri = $this->baseUrl().'assets/admin/images/svgs.svg#icon-';
935
936
        if (isset($menuItem['ident'])) {
937
            $menuIdent = $menuItem['ident'];
0 ignored issues
show
Unused Code introduced by
The assignment to $menuIdent is dead and can be removed.
Loading history...
938
        } else {
939
            $menuItem['ident'] = $menuIdent;
940
        }
941
942
        if (!empty($menuItem['url'])) {
943
            $url = $menuItem['url'];
944
            if ($url && strpos($url, ':') === false && !in_array($url[0], [ '/', '#', '?' ])) {
945
                $url = $this->adminUrl().$url;
946
            }
947
        } else {
948
            $url = '';
949
        }
950
951
        $menuItem['url'] = $url;
952
953
        if (isset($menuItem['icon'])) {
954
            $icon = $menuItem['icon'];
955
            if ($icon && strpos($icon, ':') === false && !in_array($icon[0], [ '/', '#', '?' ])) {
956
                $icon = $svgUri.$icon;
957
            }
958
        } else {
959
            $icon = $svgUri.'contents';
960
        }
961
962
        if (is_string($icon) && strpos($icon, '.svg') > 0) {
963
            unset($menuItem['icon']);
964
            $menuItem['svg'] = $icon;
965
        } else {
966
            unset($menuItem['svg']);
967
            $menuItem['icon'] = $icon;
968
        }
969
970
        if (isset($menuItem['label'])) {
971
            $menuItem['label'] = $this->translator()->translation($menuItem['label']);
972
        }
973
974
        $menuItem['show_label'] = (isset($menuItem['show_label']) ? !!$menuItem['show_label'] : true);
975
976
        $menuItem['selected'] = ($menuItem['ident'] === $currentIdent);
977
978
        return $menuItem;
979
    }
980
981
    /**
982
     * @param  array       $menuItem     The menu structure.
983
     * @param  string|null $menuIdent    The menu identifier.
984
     * @param  string|null $currentIdent The current menu identifier.
985
     * @return array Finalized menu structure.
986
     */
987
    private function parseSystemMenuItem(array $menuItem, $menuIdent = null, $currentIdent = null)
988
    {
989
        if (!isset($menuItem['ident'])) {
990
            $menuItem['ident'] = $menuIdent;
991
        }
992
993
        if (!empty($menuItem['url'])) {
994
            $url = $menuItem['url'];
995
            if ($url && strpos($url, ':') === false && !in_array($url[0], [ '/', '#', '?' ])) {
996
                $url = $this->adminUrl().$url;
997
            }
998
        } else {
999
            $url = '#';
1000
        }
1001
1002
        $menuItem['url'] = $url;
1003
1004
        if ($menuItem['icon_css']) {
1005
            $menuItem['iconCss'] = $menuItem['icon_css'];
1006
        }
1007
1008
        if (isset($menuItem['label'])) {
1009
            $menuItem['label'] = $this->translator()->translation($menuItem['label']);
1010
        }
1011
1012
        $menuItem['selected'] = ($menuItem['ident'] === $currentIdent);
1013
1014
        return $menuItem;
1015
    }
1016
}
1017