Completed
Push — master ( fbe370...a8ec29 )
by Chauncey
08:11
created

AdminTemplate::navContainerCssClasses()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 8
nc 8
nop 0
dl 0
loc 18
rs 9.2
c 0
b 0
f 0
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
     * The cache of parsed template names.
135
     *
136
     * @var array
137
     */
138
    protected static $templateNameCache = [];
139
140
    /**
141
     * Template's init method is called automatically from `charcoal-app`'s Template Route.
142
     *
143
     * For admin templates, initializations is:
144
     *
145
     * - to start a session, if necessary
146
     * - to authenticate
147
     * - to initialize the template data with the PSR Request object
148
     *
149
     * @param RequestInterface $request The request to initialize.
150
     * @return boolean
151
     * @see \Charcoal\App\Route\TemplateRoute::__invoke()
152
     */
153
    public function init(RequestInterface $request)
154
    {
155
        if (!session_id()) {
156
            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

156
            session_cache_limiter(/** @scrutinizer ignore-type */ false);
Loading history...
157
            session_start();
158
        }
159
160
        $this->setDataFromRequest($request);
161
        $this->authRedirect($request);
162
163
        return parent::init($request);
164
    }
165
166
    /**
167
     * Determine if the current user is authenticated, if not redirect them to the login page.
168
     *
169
     * @todo   Move auth-check and redirection to a middleware or dedicated admin route.
170
     * @param  RequestInterface $request The request to initialize.
171
     * @return void
172
     */
173
    protected function authRedirect(RequestInterface $request)
174
    {
175
        // Test if authentication is required.
176
        if ($this->authRequired() === false) {
0 ignored issues
show
introduced by
The condition $this->authRequired() === false is always false.
Loading history...
177
            return;
178
        }
179
180
        // Test if user is authorized to access this controller
181
        if ($this->isAuthorized() === true) {
182
            return;
183
        }
184
185
        $redirectTo = urlencode($request->getRequestTarget());
186
187
        header('HTTP/1.0 403 Forbidden');
188
        header('Location: '.$this->adminUrl('login?redirect_to='.$redirectTo));
189
        exit;
190
    }
191
192
    /**
193
     * Sets the template data from a PSR Request object.
194
     *
195
     * @param  RequestInterface $request A PSR-7 compatible Request instance.
196
     * @return self
197
     */
198
    protected function setDataFromRequest(RequestInterface $request)
199
    {
200
        $keys = $this->validDataFromRequest();
201
        if (!empty($keys)) {
202
            $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

202
            $this->setData($request->/** @scrutinizer ignore-call */ getParams($keys));
Loading history...
203
        }
204
205
        return $this;
206
    }
207
208
    /**
209
     * Retrieve the list of parameters to extract from the HTTP request.
210
     *
211
     * @return string[]
212
     */
213
    protected function validDataFromRequest()
214
    {
215
        return [
216
            // HTTP Handling
217
            'next_url',
218
            // Navigation Menusa
219
            'header_menu_item', 'side_menu_item', 'system_menu_item',
220
        ];
221
    }
222
223
    /**
224
     * @param mixed $ident Template identifier.
225
     * @return AdminTemplate Chainable
226
     */
227
    public function setIdent($ident)
228
    {
229
        $this->ident = $ident;
230
        return $this;
231
    }
232
233
    /**
234
     * @return string
235
     */
236
    public function ident()
237
    {
238
        return $this->ident;
239
    }
240
241
    /**
242
     * @param mixed $label Template label.
243
     * @return AdminTemplate Chainable
244
     */
245
    public function setLabel($label)
246
    {
247
        $this->label = $this->translator()->translation($label);
248
249
        return $this;
250
    }
251
252
    /**
253
     * @return Translation|string|null
254
     */
255
    public function label()
256
    {
257
        return $this->label;
258
    }
259
260
    /**
261
     * Set the title of the page.
262
     *
263
     * @param  mixed $title Template title.
264
     * @return AdminTemplate Chainable
265
     */
266
    public function setTitle($title)
267
    {
268
        $this->title = $this->translator()->translation($title);
269
270
        return $this;
271
    }
272
273
    /**
274
     * Retrieve the title of the page.
275
     *
276
     * @return Translation|string|null
277
     */
278
    public function title()
279
    {
280
        if ($this->title === null) {
281
            return $this->siteName();
282
        }
283
284
        return $this->title;
285
    }
286
287
    /**
288
     * Set the page's sub-title.
289
     *
290
     * @param mixed $subtitle Template subtitle.
291
     * @return AdminTemplate Chainable
292
     */
293
    public function setSubtitle($subtitle)
294
    {
295
        $this->subtitle = $this->translator()->translation($subtitle);
296
297
        return $this;
298
    }
299
300
    /**
301
     * Retrieve the page's sub-title.
302
     *
303
     * @return Translation|string|null
304
     */
305
    public function subtitle()
306
    {
307
        return $this->subtitle;
308
    }
309
310
    /**
311
     * Display or not the top right header menu.
312
     * @todo This is NOT used yet.
313
     * @param boolean $bool Display or not.
314
     * @return AdminTemplate Chainable.
315
     */
316
    public function setShowTopHeaderMenu($bool)
317
    {
318
        $this->showTopHeaderMenu = $bool;
319
        return $this;
320
    }
321
322
    /**
323
     * @todo Don't take the admin configuration that way...
324
     * @return boolean Show the top header menu or not.
325
     */
326
    public function showTopHeaderMenu()
327
    {
328
        $showTopHeaderMenu = $this->adminConfig('show_top_header_menu');
329
        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...
330
    }
331
332
    /**
333
     * Sets the top right header menu.
334
     * @param array $menu Menu as link and labels.
335
     * @return AdminTemplate Chainable.
336
     */
337
    public function setTopHeaderMenu(array $menu)
338
    {
339
        $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...
340
        return $this;
341
    }
342
343
    /**
344
     * Header menu links and labels.
345
     * @todo To Do.
346
     * @return array The menu.
347
     */
348
    public function topHeaderMenu()
349
    {
350
        return [];
351
    }
352
353
    /**
354
     * @param boolean $show The show header menu flag.
355
     * @return AdminTemplate Chainable
356
     */
357
    public function setShowHeaderMenu($show)
358
    {
359
        $this->showHeaderMenu = !!$show;
360
        return $this;
361
    }
362
363
    /**
364
     * @return boolean
365
     */
366
    public function showHeaderMenu()
367
    {
368
        return ($this->isAuthorized() && $this->showHeaderMenu);
369
    }
370
371
    /**
372
     * Yield the header menu.
373
     *
374
     * @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...
375
     */
376
    public function headerMenu()
377
    {
378
        if ($this->headerMenu === null) {
379
            $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...
380
        }
381
382
        foreach ($this->headerMenu as $menuIdent => $menuItem) {
383
            yield $menuIdent => $menuItem;
384
        }
385
    }
386
387
    /**
388
     * @param boolean $show The show footer menu flag.
389
     * @return AdminTemplate Chainable
390
     */
391
    public function setShowSystemMenu($show)
392
    {
393
        $this->showSystemMenu = !!$show;
394
        return $this;
395
    }
396
397
    /**
398
     * @return boolean
399
     */
400
    public function showSystemMenu()
401
    {
402
        return ($this->isAuthorized() && $this->showSystemMenu && (count($this->systemMenu()) > 0));
403
    }
404
405
    /**
406
     * @return array
407
     */
408
    public function systemMenu()
409
    {
410
        if ($this->systemMenu === null) {
411
            $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...
412
        }
413
414
        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...
415
    }
416
417
    /**
418
     * @param  boolean $show The show sidemenu flag.
419
     * @return AdminTemplate Chainable
420
     */
421
    public function setShowSidemenu($show)
422
    {
423
        $this->showSidemenu = !!$show;
424
        return $this;
425
    }
426
427
    /**
428
     * @return boolean
429
     */
430
    public function showSidemenu()
431
    {
432
        return ($this->isAuthorized() && $this->showSidemenu);
433
    }
434
435
    /**
436
     * Retrieve the sidemenu.
437
     *
438
     * @return \Charcoal\Admin\Widget\SidemenuWidgetInterface|null
439
     */
440
    public function sidemenu()
441
    {
442
        return $this->sidemenu;
443
    }
444
445
    /**
446
     * @return string
447
     */
448
    public function headerMenuLogo()
449
    {
450
        $logo = $this->adminConfig('menu_logo');
451
        if (!empty($logo)) {
452
            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...
453
        }
454
455
        return 'assets/admin/images/identicon.png';
456
    }
457
458
    /**
459
     * @return string
460
     */
461
    public function navContainerCssClasses()
462
    {
463
        $classes = [ 'has-nav-logo' ];
464
465
        if ($this->showHeaderMenu()) {
466
            $classes[] = 'has-nav-main';
467
        }
468
469
        if ($this->showSidemenu()) {
470
            $classes[] = 'has-nav-sub';
471
        }
472
473
        /** @see ::showSystemMenu() */
474
        if ($this->isAuthenticated()) {
475
            $classes[] = 'has-nav-system';
476
        }
477
478
        return implode(' ', $classes);
479
    }
480
481
    /**
482
     * Get the "Visit website" label.
483
     *
484
     * @return string|boolean The button's label,
485
     *     TRUE to use the default label,
486
     *     or FALSE to disable the link.
487
     */
488
    public function visitSiteLabel()
489
    {
490
        $label = $this->adminConfig('header_menu.visit_site');
491
        if ($label === false) {
492
            return false;
493
        }
494
495
        if (empty($label) || $label === true) {
496
            $label = $this->translator()->translate('Visit Site');
497
        } else {
498
            $label = $this->translator()->translate($label);
499
        }
500
501
        return $label;
502
    }
503
504
    /**
505
     * Retrieve the name of the project.
506
     *
507
     * @return Translation|string|null
508
     */
509
    public function siteName()
510
    {
511
        return $this->siteName;
512
    }
513
514
    /**
515
     * Retrieve the document title.
516
     *
517
     * @return Translation|string|null
518
     */
519
    public function documentTitle()
520
    {
521
        $siteName  = $this->siteName();
522
        $pageTitle = strip_tags($this->title());
523
524
        if ($pageTitle) {
525
            if ($pageTitle === $siteName) {
526
                return sprintf('%1$s &#8212; Charcoal', $pageTitle);
527
            } else {
528
                return sprintf('%1$s &lsaquo; %2$s &#8212; Charcoal', $pageTitle, $siteName);
529
            }
530
        }
531
532
        return $siteName;
533
    }
534
535
    /**
536
     * Retrieve the current language.
537
     *
538
     * @return string
539
     */
540
    public function lang()
541
    {
542
        return $this->translator()->getLocale();
543
    }
544
545
    /**
546
     * Retrieve the current language.
547
     *
548
     * @return string
549
     */
550
    public function locale()
551
    {
552
        $lang    = $this->lang();
553
        $locales = $this->translator()->locales();
554
555
        if (isset($locales[$lang]['locale'])) {
556
            $locale = $locales[$lang]['locale'];
557
            if (is_array($locale)) {
558
                $locale = implode(' ', $locale);
559
            }
560
        } else {
561
            $locale = 'en-US';
562
        }
563
564
        return $locale;
565
    }
566
567
    /**
568
     * Determine if a CAPTCHA test is available.
569
     *
570
     * For example, the "Login", "Lost Password", and "Reset Password" templates
571
     * can render the CAPTCHA test.
572
     *
573
     * @see    AdminAction::recaptchaEnabled() Duplicate
574
     * @return boolean
575
     */
576
    public function recaptchaEnabled()
577
    {
578
        $recaptcha = $this->apiConfig('google.recaptcha');
579
580
        if (empty($recaptcha) || (isset($recaptcha['active']) && $recaptcha['active'] === false)) {
581
            return false;
582
        }
583
584
        return (!empty($recaptcha['public_key'])  || !empty($recaptcha['key'])) &&
585
               (!empty($recaptcha['private_key']) || !empty($recaptcha['secret']));
586
    }
587
588
    /**
589
     * Determine if the CAPTCHA test is invisible.
590
     *
591
     * Note: Charcoal's implementation of Google reCAPTCHA defaults to "invisible".
592
     *
593
     * @return boolean
594
     */
595
    public function recaptchaInvisible()
596
    {
597
        $recaptcha = $this->apiConfig('google.recaptcha');
598
599
        $hasInvisible = isset($recaptcha['invisible']);
600
        if ($hasInvisible && $recaptcha['invisible'] === true) {
601
            return true;
602
        }
603
604
        $hasSize = isset($recaptcha['size']);
605
        if ($hasSize && $recaptcha['size'] === 'invisible') {
606
            return true;
607
        }
608
609
        if (!$hasInvisible && !$hasSize) {
610
            return true;
611
        }
612
613
        return false;
614
    }
615
616
    /**
617
     * Alias of {@see self::recaptchaSiteKey()}.
618
     *
619
     * @deprecated
620
     * @return string|null
621
     */
622
    public function recaptchaKey()
623
    {
624
        return $this->recaptchaSiteKey();
625
    }
626
627
    /**
628
     * Retrieve the Google reCAPTCHA public (site) key.
629
     *
630
     * @throws RuntimeException If Google reCAPTCHA is required but not configured.
631
     * @return string|null
632
     */
633
    public function recaptchaSiteKey()
634
    {
635
        $recaptcha = $this->apiConfig('google.recaptcha');
636
637
        if (!empty($recaptcha['public_key'])) {
638
            return $recaptcha['public_key'];
0 ignored issues
show
Bug Best Practice introduced by
The expression return $recaptcha['public_key'] also could return the type mixed|Charcoal\Config\AbstractConfig which is incompatible with the documented return type null|string.
Loading history...
639
        } elseif (!empty($recaptcha['key'])) {
640
            return $recaptcha['key'];
641
        }
642
643
        return null;
644
    }
645
646
    /**
647
     * Retrieve the parameters for the Google reCAPTCHA widget.
648
     *
649
     * @return string[]
650
     */
651
    public function recaptchaParameters()
652
    {
653
        $apiConfig = $this->apiConfig('google.recaptcha');
654
        $tplConfig = $this->get('recaptcha_options') ?: [];
655
656
        $params = [
657
            'sitekey'  => $this->recaptchaSiteKey(),
658
            'badge'    => null,
659
            'type'     => null,
660
            'size'     => 'invisible',
661
            'tabindex' => null,
662
            'callback' => null,
663
        ];
664
665
        if ($this->recaptchaInvisible() === false) {
666
            $params['size'] = null;
667
        }
668
669
        foreach ($params as $key => $val) {
670
            if ($val === null || $val === '') {
671
                if (isset($tplConfig[$key])) {
672
                    $val = $tplConfig[$key];
673
                } elseif (isset($apiConfig[$key])) {
674
                    $val = $apiConfig[$key];
675
                }
676
677
                $params[$key] = $val;
678
            }
679
        }
680
681
        return $params;
682
    }
683
684
    /**
685
     * Generate a string representation of HTML attributes for the Google reCAPTCHA tag.
686
     *
687
     * @return string
688
     */
689
    public function recaptchaHtmlAttr()
690
    {
691
        $params = $this->recaptchaParameters();
692
693
        $attributes = [];
694
        foreach ($params as $key => $val) {
695
            if ($val !== null) {
696
                $attributes[] = sprintf('data-%s="%s"', $key, htmlspecialchars($val, ENT_QUOTES));
697
            }
698
        }
699
700
        return implode(' ', $attributes);
701
    }
702
703
    /**
704
     * Set common dependencies (services) used in all admin templates.
705
     *
706
     * @param Container $container DI Container.
707
     * @return void
708
     */
709
    protected function setDependencies(Container $container)
710
    {
711
        parent::setDependencies($container);
712
713
        // Satisfies TranslatorAwareTrait dependencies
714
        $this->setTranslator($container['translator']);
715
716
        // Satisfies AuthAwareInterface + SecurityTrait dependencies
717
        $this->setAuthenticator($container['admin/authenticator']);
718
        $this->setAuthorizer($container['admin/authorizer']);
719
720
        // Satisfies AdminTrait dependencies
721
        $this->setDebug($container['config']);
722
        $this->setAppConfig($container['config']);
723
        $this->setAdminConfig($container['admin/config']);
724
725
        // Satisfies BaseUrlTrait dependencies
726
        $this->setBaseUrl($container['base-url']);
727
        $this->setAdminUrl($container['admin/base-url']);
728
729
        // Satisfies AdminTemplate dependencies
730
        $this->setSiteName($container['config']['project_name']);
731
732
        $this->setModelFactory($container['model/factory']);
733
        $this->setWidgetFactory($container['widget/factory']);
734
735
        $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...
736
        $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...
737
    }
738
739
    /**
740
     * @throws Exception If the factory is not set.
741
     * @return FactoryInterface The model factory.
742
     */
743
    protected function modelFactory()
744
    {
745
        if (!$this->modelFactory) {
746
            throw new Exception(
747
                sprintf('Model factory is not set for template "%s".', get_class($this))
748
            );
749
        }
750
        return $this->modelFactory;
751
    }
752
753
    /**
754
     * @throws Exception If the widget factory dependency was not previously set / injected.
755
     * @return FactoryInterface
756
     */
757
    protected function widgetFactory()
758
    {
759
        if ($this->widgetFactory === null) {
760
            throw new Exception(
761
                'Widget factory was not set.'
762
            );
763
        }
764
        return $this->widgetFactory;
765
    }
766
767
    /**
768
     * Set the name of the project.
769
     *
770
     * @param  string $name Name of the project.
771
     * @return AdminTemplate Chainable
772
     */
773
    protected function setSiteName($name)
774
    {
775
        $this->siteName = $this->translator()->translation($name);
776
        return $this;
777
    }
778
779
    /**
780
     * @param  mixed $options The sidemenu widget ID or config.
781
     * @throws InvalidArgumentException If the menu is missing, invalid, or malformed.
782
     * @return array|Generator
783
     */
784
    protected function createHeaderMenu($options = null)
785
    {
786
        $headerMenu = $this->adminConfig('header_menu');
787
788
        if (!isset($headerMenu['items'])) {
789
            throw new InvalidArgumentException(
790
                'Missing "admin.header_menu.items"'
791
            );
792
        }
793
794
        $mainMenu = null;
795
        if (isset($this['header_menu_item'])) {
796
            $mainMenu = $this['header_menu_item'];
797
        }
798
799
        if (!(empty($options) && !is_numeric($options))) {
800
            if (is_string($options)) {
801
                $mainMenu = $options;
802
            } elseif (is_array($options)) {
803
                if (isset($options['widget_options']['ident'])) {
804
                    $mainMenu = $options['widget_options']['ident'];
805
                }
806
            }
807
        }
808
809
        $mainMenuFromRequest = filter_input(INPUT_GET, 'main_menu', FILTER_SANITIZE_STRING);
810
        if ($mainMenuFromRequest) {
811
            $mainMenu = $mainMenuFromRequest;
812
        }
813
814
        $menu  = $this->menuBuilder->build([]);
815
        foreach ($headerMenu['items'] as $menuIdent => $menuItem) {
816
            $menuItem['menu'] = $menu;
817
            $test = $this->menuItemBuilder->build($menuItem);
818
            if ($test->isAuthorized() === false) {
819
                continue;
820
            }
821
            unset($menuItem['menu']);
822
823
            if (isset($menuItem['active']) && $menuItem['active'] === false) {
824
                continue;
825
            }
826
827
            $menuItem  = $this->parseHeaderMenuItem($menuItem, $menuIdent, $mainMenu);
828
            $menuIdent = $menuItem['ident'];
829
830
            yield $menuIdent => $menuItem;
831
        }
832
    }
833
834
    /**
835
     * @param  mixed $options The sidemenu widget ID or config.
836
     * @throws InvalidArgumentException If the sidemenu widget is invalid.
837
     * @return \Charcoal\Admin\Widget\SidemenuWidgetInterface|null
838
     */
839
    protected function createSidemenu($options = null)
840
    {
841
        if (!is_array($options)) {
842
            $options = [
843
                'widget_options' => [
844
                    'ident' => $options
845
                ]
846
            ];
847
        } elseif (!isset($options['widget_options']['ident'])) {
848
            $options['widget_options']['ident'] = null;
849
        }
850
851
        if (isset($this['side_menu_item'])) {
852
            $options['widget_options']['current_item'] = $this['side_menu_item'];
853
        }
854
855
        if (isset($this['header_menu_item'])) {
856
            $options['widget_options']['ident'] = $this['header_menu_item'];
857
        }
858
859
        $sidemenuFromRequest = filter_input(INPUT_GET, 'side_menu', FILTER_SANITIZE_STRING);
860
        $mainMenuFromRequest = filter_input(INPUT_GET, 'main_menu', FILTER_SANITIZE_STRING);
861
        if ($sidemenuFromRequest) {
862
            $options['widget_options']['ident'] = $sidemenuFromRequest;
863
        } elseif ($mainMenuFromRequest) {
864
            $options['widget_options']['ident'] = $mainMenuFromRequest;
865
        }
866
867
        if (!is_string($options['widget_options']['ident'])) {
868
            return null;
869
        }
870
871
        $GLOBALS['widget_template'] = 'charcoal/admin/widget/sidemenu';
872
873
        if (isset($options['widget_type'])) {
874
            $widgetType = $options['widget_type'];
875
        } else {
876
            $widgetType = 'charcoal/admin/widget/sidemenu';
877
        }
878
879
        $sidemenu = $this->widgetFactory()->create($widgetType);
880
881
        if (isset($options['widget_options'])) {
882
            $sidemenu->setData($options['widget_options']);
883
        }
884
885
        return $sidemenu;
886
    }
887
888
    /**
889
     * @param  mixed $options The sidemenu widget ID or config.
890
     * @throws InvalidArgumentException If the menu is missing, invalid, or malformed.
891
     * @return array|Generator
892
     */
893
    protected function createSystemMenu($options = null)
894
    {
895
        $menuConfig = $this->adminConfig('system_menu');
896
897
        if (!isset($menuConfig['items'])) {
898
            return [];
899
        }
900
901
        $currentIdent = null;
902
        if (isset($this['system_menu_item'])) {
903
            $currentIdent = $this['system_menu_item'];
904
        }
905
906
        if (!(empty($options) && !is_numeric($options))) {
907
            if (is_string($options)) {
908
                $currentIdent = $options;
909
            } elseif (is_array($options)) {
910
                $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

910
                $menuConfig = array_replace_recursive(/** @scrutinizer ignore-type */ $menuConfig, $options);
Loading history...
911
            }
912
        }
913
914
        $systemMenu = $this->menuBuilder->build([]);
915
        $menuItems  = [];
916
        foreach ($menuConfig['items'] as $menuIdent => $menuItem) {
917
            $menuItem['menu'] = $systemMenu;
918
            $test = $this->menuItemBuilder->build($menuItem);
919
            if ($test->isAuthorized() === false) {
920
                continue;
921
            }
922
            unset($menuItem['menu']);
923
924
            if (isset($menuItem['active']) && $menuItem['active'] === false) {
925
                continue;
926
            }
927
928
            $menuItem  = $this->parseSystemMenuItem($menuItem, $menuIdent, $currentIdent);
929
            $menuIdent = $menuItem['ident'];
930
931
            $menuItems[$menuIdent] = $menuItem;
932
        }
933
        return $menuItems;
934
    }
935
936
    /**
937
     * As a convenience, all admin templates have a model factory to easily create objects.
938
     *
939
     * @param FactoryInterface $factory The factory used to create models.
940
     * @return void
941
     */
942
    private function setModelFactory(FactoryInterface $factory)
943
    {
944
        $this->modelFactory = $factory;
945
    }
946
947
    /**
948
     * @param FactoryInterface $factory The widget factory, to create the dashboard and sidemenu widgets.
949
     * @return void
950
     */
951
    private function setWidgetFactory(FactoryInterface $factory)
952
    {
953
        $this->widgetFactory = $factory;
954
    }
955
956
    /**
957
     * @param  array       $menuItem     The menu structure.
958
     * @param  string|null $menuIdent    The menu identifier.
959
     * @param  string|null $currentIdent The current menu identifier.
960
     * @return array Finalized menu structure.
961
     */
962
    private function parseHeaderMenuItem(array $menuItem, $menuIdent = null, $currentIdent = null)
963
    {
964
        $svgUri = $this->baseUrl().'assets/admin/images/svgs.svg#icon-';
965
966
        if (isset($menuItem['ident'])) {
967
            $menuIdent = $menuItem['ident'];
0 ignored issues
show
Unused Code introduced by
The assignment to $menuIdent is dead and can be removed.
Loading history...
968
        } else {
969
            $menuItem['ident'] = $menuIdent;
970
        }
971
972
        if (!empty($menuItem['url'])) {
973
            $url = $menuItem['url'];
974
            if ($url && strpos($url, ':') === false && !in_array($url[0], [ '/', '#', '?' ])) {
975
                $url = $this->adminUrl().$url;
976
            }
977
        } else {
978
            $url = '';
979
        }
980
981
        $menuItem['url'] = $url;
982
983
        if (isset($menuItem['icon'])) {
984
            $icon = $menuItem['icon'];
985
            if ($icon && strpos($icon, ':') === false && !in_array($icon[0], [ '/', '#', '?' ])) {
986
                $icon = $svgUri.$icon;
987
            }
988
        } else {
989
            $icon = $svgUri.'contents';
990
        }
991
992
        if (is_string($icon) && strpos($icon, '.svg') > 0) {
993
            unset($menuItem['icon']);
994
            $menuItem['svg'] = $icon;
995
        } else {
996
            unset($menuItem['svg']);
997
            $menuItem['icon'] = $icon;
998
        }
999
1000
        if (isset($menuItem['label'])) {
1001
            $menuItem['label'] = $this->translator()->translation($menuItem['label']);
1002
        }
1003
1004
        $menuItem['show_label'] = (isset($menuItem['show_label']) ? !!$menuItem['show_label'] : true);
1005
1006
        $menuItem['selected'] = ($menuItem['ident'] === $currentIdent);
1007
1008
        return $menuItem;
1009
    }
1010
1011
    /**
1012
     * @param  array       $menuItem     The menu structure.
1013
     * @param  string|null $menuIdent    The menu identifier.
1014
     * @param  string|null $currentIdent The current menu identifier.
1015
     * @return array Finalized menu structure.
1016
     */
1017
    private function parseSystemMenuItem(array $menuItem, $menuIdent = null, $currentIdent = null)
1018
    {
1019
        if (!isset($menuItem['ident'])) {
1020
            $menuItem['ident'] = $menuIdent;
1021
        }
1022
1023
        if (!empty($menuItem['url'])) {
1024
            $url = $menuItem['url'];
1025
            if ($url && strpos($url, ':') === false && !in_array($url[0], [ '/', '#', '?' ])) {
1026
                $url = $this->adminUrl().$url;
1027
            }
1028
        } else {
1029
            $url = '#';
1030
        }
1031
1032
        $menuItem['url'] = $url;
1033
1034
        if ($menuItem['icon_css']) {
1035
            $menuItem['iconCss'] = $menuItem['icon_css'];
1036
        }
1037
1038
        if (isset($menuItem['label'])) {
1039
            $menuItem['label'] = $this->translator()->translation($menuItem['label']);
1040
        }
1041
1042
        $menuItem['selected'] = ($menuItem['ident'] === $currentIdent);
1043
1044
        return $menuItem;
1045
    }
1046
1047
1048
1049
    // Front-end helpers
1050
    // ============================================================
1051
1052
    /**
1053
     * Retrieve the template's identifier.
1054
     *
1055
     * @return string
1056
     */
1057
    public function templateName()
1058
    {
1059
        $key = substr(strrchr('\\'.get_class($this), '\\'), 1);
1060
1061
        if (!isset(static::$templateNameCache[$key])) {
1062
            $value = $key;
1063
1064
            if (!ctype_lower($value)) {
1065
                $value = preg_replace('/\s+/u', '', $value);
1066
                $value = mb_strtolower(preg_replace('/(.)(?=[A-Z])/u', '$1-', $value), 'UTF-8');
1067
            }
1068
1069
            $value = str_replace(
1070
                [ 'abstract', 'trait', 'interface', 'template', '\\' ],
1071
                '',
1072
                $value
1073
            );
1074
1075
            static::$templateNameCache[$key] = trim($value, '-');
1076
        }
1077
1078
        return static::$templateNameCache[$key];
1079
    }
1080
}
1081