Header   F
last analyzed

Complexity

Total Complexity 60

Size/Duplication

Total Lines 530
Duplicated Lines 0 %

Test Coverage

Coverage 50.68%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 175
c 2
b 0
f 0
dl 0
loc 530
ccs 149
cts 294
cp 0.5068
rs 3.6
wmc 60

24 Methods

Rating   Name   Duplication   Size   Complexity  
A addOpenGraphData() 0 3 1
A extractOpenGraphImages() 0 9 3
A addJS() 0 11 3
A addJsData() 0 3 1
A getPageTitle() 0 3 1
A addOpenGraphImage() 0 32 6
A addLink() 0 12 3
A getMetaCustom() 0 3 1
A addMetaDescription() 0 3 1
A __construct() 0 46 2
A addRssLink() 0 3 1
A addMetaKeywords() 0 3 1
A addMetaData() 0 16 3
A addCSS() 0 11 3
B getCanonical() 0 35 8
A setTwitterCard() 0 19 3
A addMetaLink() 0 3 1
A setCanonicalUrl() 0 7 2
A parseSeo() 0 18 4
A getContentTitle() 0 3 1
A setMetaCustom() 0 3 1
A setPageTitle() 0 26 5
A setContentTitle() 0 3 1
A parse() 0 56 4

How to fix   Complexity   

Complex Class

Complex classes like Header often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

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

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

1
<?php
2
3
namespace Frontend\Core\Header;
4
5
use Common\Core\Header\Asset;
6
use Common\Core\Header\AssetCollection;
7
use Common\Core\Header\JsData;
8
use Common\Core\Header\Minifier;
9
use Common\Core\Header\Priority;
10
use ForkCMS\App\KernelLoader;
11
use ForkCMS\Google\TagManager\TagManager;
12
use ForkCMS\Privacy\ConsentDialog;
13
use Frontend\Core\Engine\Model;
14
use Frontend\Core\Engine\Theme;
15
use Frontend\Core\Engine\TwigTemplate;
16
use Frontend\Core\Engine\Url;
17
use Frontend\Core\Language\Locale;
18
use Symfony\Component\HttpKernel\KernelInterface;
19
20
/**
21
 * This class will be used to alter the head-part of the HTML-document that will be created by the frontend
22
 * Therefore it will handle meta-stuff (title, including JS, including CSS, ...)
23
 */
24
class Header extends KernelLoader
25
{
26
    /**
27
     * The canonical URL
28
     *
29
     * @var string
30
     */
31
    private $canonical;
32
33
    /**
34
     * The added css-files
35
     *
36
     * @var AssetCollection
37
     */
38
    private $cssFiles;
39
40
    /**
41
     * Data that will be passed to js
42
     *
43
     * @var JsData
44
     */
45
    private $jsData;
46
47
    /**
48
     * The added js-files
49
     *
50
     * @var AssetCollection
51
     */
52
    private $jsFiles;
53
54
    /**
55
     * Meta data and links
56
     *
57
     * @var MetaCollection
58
     */
59
    private $meta;
60
61
    /**
62
     * The custom meta data
63
     *
64
     * @var string
65
     */
66
    private $metaCustom = '';
67
68
    /**
69
     * Page title
70
     *
71
     * @var string
72
     */
73
    private $pageTitle;
74
75
    /**
76
     * Content title
77
     *
78
     * @var string
79
     */
80
    private $contentTitle;
81
82
    /**
83
     * TwigTemplate instance
84
     *
85
     * @var TwigTemplate
86
     */
87
    protected $template;
88
89
    /**
90
     * URL instance
91
     *
92
     * @var Url
93
     */
94
    protected $url;
95
96 26
    public function __construct(KernelInterface $kernel)
97
    {
98 26
        parent::__construct($kernel);
99
100 26
        $container = $this->getContainer();
101 26
        $container->set('header', $this);
102
103 26
        $this->template = $container->get('templating');
104 26
        $this->url = $container->get('url');
105
106 26
        $this->cssFiles = new AssetCollection(
107 26
            Minifier::css(
108 26
                $container->getParameter('site.path_www'),
109 26
                FRONTEND_CACHE_URL . '/MinifiedCss/',
110 26
                FRONTEND_CACHE_PATH . '/MinifiedCss/'
111
            )
112
        );
113 26
        $this->jsFiles = new AssetCollection(
114 26
            Minifier::js(
115 26
                $container->getParameter('site.path_www'),
116 26
                FRONTEND_CACHE_URL . '/MinifiedJs/',
117 26
                FRONTEND_CACHE_PATH . '/MinifiedJs/'
118
            )
119
        );
120
121
        $jsData = [
122 26
            'LANGUAGE' => Locale::frontendLanguage(),
123 26
            'privacyConsent' => $this->get(ConsentDialog::class)->getJsData(),
124
        ];
125 26
        $this->jsData = new JsData($jsData);
126
127 26
        $this->meta = new MetaCollection();
128
129
        // add some default CSS files
130 26
        $this->addCSS('/src/Frontend/Core/Layout/Css/screen.css');
131
132
        // debug stylesheet
133 26
        if ($container->getParameter('kernel.debug')) {
134 26
            $this->addCSS('/src/Frontend/Core/Layout/Css/debug.css', true, false, Priority::debug());
135
        }
136
137
        // add default javascript-files
138 26
        $this->addJS('/js/vendors/jquery.min.js', false, false, Priority::core());
139 26
        $this->addJS('/src/Frontend/Core/Js/jquery/jquery.frontend.js', true, false, Priority::core());
140 26
        $this->addJS('/src/Frontend/Core/Js/utils.js', true, false, Priority::core());
141 26
        $this->addJS('/src/Frontend/Core/Js/frontend.js', true, false, Priority::core());
142 26
    }
143
144
    /**
145
     * Add a CSS file into the array
146
     *
147
     * @param string $file The path for the CSS-file that should be loaded.
148
     * @param bool $minify Should the CSS be minified?
149
     * @param bool $addTimestamp May we add a timestamp for caching purposes?
150
     * @param Priority|null $priority Provides a way to change the order that things are loaded
151
     */
152 26
    public function addCSS(
153
        string $file,
154
        bool $minify = true,
155
        bool $addTimestamp = false,
156
        Priority $priority = null
157
    ): void {
158 26
        $isExternalUrl = $this->get('fork.validator.url')->isExternalUrl($file);
159 26
        $file = $isExternalUrl ? $file : Theme::getPath($file);
160 26
        $minify = $minify && !$isExternalUrl;
161
162 26
        $this->cssFiles->add(new Asset($file, $addTimestamp, $priority), $minify);
163 26
    }
164
165
    /**
166
     * Add a javascript file into the array
167
     *
168
     * @param string $file The path to the javascript-file that should be loaded.
169
     * @param bool $minify Should the javascript be minified?
170
     * @param bool $addTimestamp May we add a timestamp for caching purposes?
171
     * @param Priority|null $priority Provides a way to change the order that things are loaded
172
     */
173 26
    public function addJS(
174
        string $file,
175
        bool $minify = true,
176
        bool $addTimestamp = false,
177
        Priority $priority = null
178
    ): void {
179 26
        $isExternalUrl = $this->get('fork.validator.url')->isExternalUrl($file);
180 26
        $file = $isExternalUrl ? $file : Theme::getPath($file);
181 26
        $minify = $minify && !$isExternalUrl;
182
183 26
        $this->jsFiles->add(new Asset($file, $addTimestamp, $priority), $minify);
184 26
    }
185
186
    /**
187
     * Add data into the jsData
188
     *
189
     * @param string $module The name of the module.
190
     * @param string $key The key whereunder the value will be stored.
191
     * @param mixed $value The value
192
     */
193
    public function addJsData(string $module, string $key, $value): void
194
    {
195
        $this->jsData->add($module, $key, $value);
196
    }
197
198
    /**
199
     * @param array $attributes The attributes to parse.
200
     * @param bool $overwrite Should we overwrite the current value?
201
     * @param string[] $uniqueAttributeKeys Which keys can we use to decide if an item is unique.
202
     */
203
    public function addLink(
204
        array $attributes,
205
        bool $overwrite = false,
206
        array $uniqueAttributeKeys = ['rel', 'hreflang', 'type', 'title']
207
    ): void {
208
        if (!isset($attributes['href']) || empty($attributes['href'])) {
209
            return;
210
        }
211
        $href = $attributes['href'];
212
        unset($attributes['href']);
213
214
        $this->meta->addMetaLink(new MetaLink($href, $attributes, $uniqueAttributeKeys), $overwrite);
215
    }
216
217
    /**
218
     * @param array $attributes The attributes to parse.
219
     * @param bool $overwrite Should we overwrite the current value?
220
     * @param array $uniqueAttributeKeys Which keys can we use to decide if an item is unique.
221
     * @param string $uniqueKeySuffix This additional key helps you to create your own custom unique keys if required.
222
     */
223 2
    public function addMetaData(
224
        array $attributes,
225
        bool $overwrite = false,
226
        array $uniqueAttributeKeys = ['name'],
227
        string $uniqueKeySuffix = null
228
    ): void {
229 2
        if (!isset($attributes['content']) || $attributes['content'] === '') {
230
            return;
231
        }
232
233 2
        $content = $attributes['content'];
234 2
        unset($attributes['content']);
235
236 2
        $this->meta->addMetaData(
237 2
            new MetaData($content, $attributes, $uniqueAttributeKeys, $uniqueKeySuffix),
238 2
            $overwrite
239
        );
240 2
    }
241
242
    /**
243
     * Add meta-description, somewhat a shortcut for the addMetaData-method
244
     *
245
     * @param string $metaDescription The description.
246
     * @param bool $overwrite Should we overwrite the previous value?
247
     */
248 26
    public function addMetaDescription(string $metaDescription, bool $overwrite = false): void
249
    {
250 26
        $this->meta->addMetaData(MetaData::forName('description', $metaDescription), $overwrite);
251 26
    }
252
253
    /**
254
     * Add meta-keywords, somewhat a shortcut for the addMetaData-method
255
     *
256
     * @param string $metaKeywords The keywords.
257
     * @param bool $overwrite Should we overwrite the previous value?
258
     */
259 26
    public function addMetaKeywords(string $metaKeywords, bool $overwrite = false): void
260
    {
261 26
        $this->meta->addMetaData(MetaData::forName('keywords', $metaKeywords), $overwrite);
262 26
    }
263
264
    /**
265
     * @param string $property The key (without og:).
266
     * @param string $openGraphData The value.
267
     * @param bool $overwrite Should we overwrite the previous value?
268
     */
269 2
    public function addOpenGraphData(string $property, string $openGraphData, bool $overwrite = false): void
270
    {
271 2
        $this->meta->addMetaData(MetaData::forProperty('og:' . $property, $openGraphData), $overwrite);
272 2
    }
273
274
    public function addOpenGraphImage($image, bool $overwrite = false, int $width = 0, int $height = 0): void
275
    {
276
        // remove site url from path
277
        $image = str_replace(SITE_URL, '', $image);
278
279
        // check if it no longer points to an absolute uri
280
        if (!$this->getContainer()->get('fork.validator.url')->isExternalUrl($image)) {
281
            if (!is_file(PATH_WWW . strtok($image, '?'))) {
282
                return;
283
            }
284
            $image = SITE_URL . $image;
285
        }
286
287
        $this->meta->addMetaData(MetaData::forProperty('og:image', $image, ['property', 'content']), $overwrite);
288
        if (SITE_PROTOCOL === 'https') {
289
            $this->meta->addMetaData(
290
                MetaData::forProperty('og:image:secure_url', $image, ['property', 'content']),
291
                $overwrite
292
            );
293
        }
294
295
        if ($width !== 0) {
296
            $this->meta->addMetaData(
297
                MetaData::forProperty('og:image:width', $width, ['property', 'content']),
298
                $overwrite
299
            );
300
        }
301
302
        if ($height !== 0) {
303
            $this->meta->addMetaData(
304
                MetaData::forProperty('og:image:height', $height, ['property', 'content']),
305
                $overwrite
306
            );
307
        }
308
    }
309
310 8
    public function addRssLink(string $title, string $link): void
311
    {
312 8
        $this->meta->addMetaLink(MetaLink::rss($link, $title), true);
313 8
    }
314
315
    /**
316
     * Extract images from content that can be added add Open Graph image
317
     *
318
     * @param string $content The content (where from to extract the images).
319
     */
320 2
    public function extractOpenGraphImages(string $content): void
321
    {
322 2
        $images = [];
323
324
        // check if any img-tags are present in the content
325 2
        if (preg_match_all('/<img.*?src="(.*?)".*?\/>/i', $content, $images)) {
326
            // loop all found images and add to Open Graph metadata
327
            foreach ($images[1] as $image) {
328
                $this->addOpenGraphImage($image);
329
            }
330
        }
331 2
    }
332
333 26
    public function getMetaCustom(): string
334
    {
335 26
        return (string) $this->metaCustom;
336
    }
337
338 26
    public function getPageTitle(): string
339
    {
340 26
        return (string) $this->pageTitle;
341
    }
342
343
    /**
344
     * Parse the header into the template
345
     */
346 26
    public function parse(): void
347
    {
348
        // @deprecated remove this in Fork 6, check if this still should be used.
349 26
        $facebook = new Facebook($this->get('fork.settings'));
0 ignored issues
show
Bug introduced by
It seems like $this->get('fork.settings') can also be of type null; however, parameter $modulesSettings of Frontend\Core\Header\Facebook::__construct() does only seem to accept Common\ModulesSettings, 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

349
        $facebook = new Facebook(/** @scrutinizer ignore-type */ $this->get('fork.settings'));
Loading history...
350 26
        $facebook->addOpenGraphMeta($this);
351 26
        $this->parseSeo();
352
353
        // in debug mode we don't want our pages to be indexed.
354 26
        if ($this->getContainer()->getParameter('kernel.debug')) {
355 26
            $this->meta->addMetaData(MetaData::forName('robots', 'noindex, nofollow'), true);
356
        }
357
358 26
        $this->template->assignGlobal('meta', $this->meta);
359 26
        $this->template->assignGlobal('metaCustom', $this->getMetaCustom());
360 26
        $this->cssFiles->parse($this->template, 'cssFiles');
361 26
        $this->jsFiles->parse($this->template, 'jsFiles');
362
363 26
        $siteHTMLHead = '';
364 26
        $siteHTMLStartOfBody = '';
365
366
        // Add Google Tag Manager code if needed
367 26
        $googleTagManagerContainerId = $this->get('fork.settings')->get('Core', 'google_tracking_google_tag_manager_container_id', '');
368 26
        if ($googleTagManagerContainerId !== '') {
369
            $googleTagManager = $this->get(TagManager::class);
370
            $siteHTMLHead .= $googleTagManager->generateHeadCode() . "\n";
371
372
            $siteHTMLStartOfBody .= $googleTagManager->generateStartOfBodyCode() . "\n";
373
        }
374
375
        // Add Google Analytics code if needed
376 26
        $googleAnalyticsTrackingId = $this->get('fork.settings')->get('Core', 'google_tracking_google_analytics_tracking_id', '');
377 26
        if ($googleAnalyticsTrackingId !== '') {
378
            $siteHTMLHead .= new GoogleAnalytics(
379
                $this->get('fork.settings'),
0 ignored issues
show
Bug introduced by
It seems like $this->get('fork.settings') can also be of type null; however, parameter $modulesSettings of Frontend\Core\Header\Goo...nalytics::__construct() does only seem to accept Common\ModulesSettings, 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

379
                /** @scrutinizer ignore-type */ $this->get('fork.settings'),
Loading history...
380
                $this->get(ConsentDialog::class),
0 ignored issues
show
Bug introduced by
It seems like $this->get(ForkCMS\Privacy\ConsentDialog::class) can also be of type null; however, parameter $consentDialog of Frontend\Core\Header\Goo...nalytics::__construct() does only seem to accept ForkCMS\Privacy\ConsentDialog, 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

380
                /** @scrutinizer ignore-type */ $this->get(ConsentDialog::class),
Loading history...
381
                $this->get('fork.cookie')
0 ignored issues
show
Bug introduced by
It seems like $this->get('fork.cookie') can also be of type null; however, parameter $cookie of Frontend\Core\Header\Goo...nalytics::__construct() does only seem to accept Common\Core\Cookie, 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

381
                /** @scrutinizer ignore-type */ $this->get('fork.cookie')
Loading history...
382
            ) . "\n";
383
        }
384
385
        // @deprecated fallback to site_html_header as this was used in the past
386 26
        $siteHTMLHead .= (string) $this->get('fork.settings')->get('Core', 'site_html_head', $this->get('fork.settings')->get('Core', 'site_html_header', '')) . "\n";
387 26
        $siteHTMLHead .= "\n" . $this->jsData;
388 26
        $this->template->assignGlobal('siteHTMLHead', trim($siteHTMLHead));
389
390
        // @deprecated remove this in Fork 6, use siteHTMLHead
391 26
        $this->template->assignGlobal('siteHTMLHeader', trim($siteHTMLHead));
392
393
        // @deprecated fallback to site_start_of_body_scripts as this was used in the pased
394 26
        $siteHTMLStartOfBody .= $this->get('fork.settings')->get('Core', 'site_html_start_of_body', $this->get('fork.settings')->get('Core', 'site_start_of_body_scripts', ''));
395 26
        $this->template->assignGlobal('siteHTMLStartOfBody', trim($siteHTMLStartOfBody));
396
397 26
        $this->template->assignGlobal('pageTitle', $this->getPageTitle());
398 26
        $this->template->assignGlobal('contentTitle', $this->getContentTitle());
399 26
        $this->template->assignGlobal(
400 26
            'siteTitle',
401 26
            (string) $this->get('fork.settings')->get('Core', 'site_title_' . LANGUAGE, SITE_DEFAULT_TITLE)
402
        );
403 26
    }
404
405 26
    private function getCanonical(): string
406
    {
407 26
        $queryString = trim($this->url->getQueryString(), '/');
408 26
        $language = $this->get('fork.settings')->get('Core', 'default_language', SITE_DEFAULT_LANGUAGE);
409 26
        if ($queryString === $language) {
410
            $this->canonical = rtrim(SITE_URL, '/');
411
412
            if ($this->getContainer()->getParameter('site.multilanguage')) {
413
                $this->canonical .= '/' . $language;
414
            }
415
        }
416
417 26
        if (!empty($this->canonical)) {
418 6
            return $this->canonical;
419
        }
420
421
        // get the chunks of the current url
422 22
        $urlChunks = parse_url($this->url->getQueryString());
423
424
        // a canonical url should contain the domain. So make sure you
425
        // redirect your website to a single url with .htaccess
426 22
        $url = rtrim(SITE_URL, '/');
427 22
        if (isset($urlChunks['port'])) {
428
            $url .= ':' . $urlChunks['port'];
429
        }
430 22
        if (isset($urlChunks['path'])) {
431 22
            $url .= $urlChunks['path'];
432
        }
433
434
        // any items provided through GET?
435 22
        if (!isset($urlChunks['query']) || !Model::getRequest()->query->has('page')) {
436 21
            return $url;
437
        }
438
439 3
        return $url . '?page=' . Model::getRequest()->query->get('page');
440
    }
441
442
    /**
443
     * Parse SEO specific data
444
     */
445 26
    private function parseSeo(): void
446
    {
447 26
        if ($this->get('fork.settings')->get('Core', 'seo_noodp', false)) {
448
            $this->meta->addMetaData(MetaData::forName('robots', 'noodp'));
449
        }
450
451 26
        if ($this->get('fork.settings')->get('Core', 'seo_noydir', false)) {
452
            $this->meta->addMetaData(MetaData::forName('robots', 'noydir'));
453
        }
454
455 26
        $charset = $this->getContainer()->getParameter('kernel.charset');
456 26
        if ($charset === 'utf-8') {
457
            $this->meta->addMetaLink(MetaLink::canonical(\SpoonFilter::htmlspecialchars($this->getCanonical())));
458
459
            return;
460
        }
461
462 26
        $this->meta->addMetaLink(MetaLink::canonical(\SpoonFilter::htmlentities($this->getCanonical())));
463 26
    }
464
465 6
    public function setCanonicalUrl(string $canonicalUrl): void
466
    {
467 6
        if (strpos($canonicalUrl, '/') === 0) {
468 2
            $canonicalUrl = SITE_URL . $canonicalUrl;
469
        }
470
471 6
        $this->canonical = $canonicalUrl;
472 6
    }
473
474 26
    public function setMetaCustom(string $meta = null): void
475
    {
476 26
        $this->metaCustom = $meta;
477 26
    }
478
479 26
    public function setContentTitle(string $contentTitle): void
480
    {
481 26
        $this->contentTitle = $contentTitle;
482 26
    }
483
484 26
    public function getContentTitle(): string
485
    {
486 26
        return $this->contentTitle;
487
    }
488
489
    /**
490
     * @param string $value The page title to be set or to be prepended.
491
     * @param bool $overwrite Should the existing page title be overwritten?
492
     */
493 26
    public function setPageTitle(string $value, bool $overwrite = false): void
494
    {
495 26
        $this->setContentTitle($value);
496
497 26
        $value = trim($value);
498
499 26
        if ($overwrite) {
500
            $this->pageTitle = $value;
501
502
            return;
503
        }
504
505 26
        if (empty($value)) {
506
            $this->pageTitle = $this->get('fork.settings')->get('Core', 'site_title_' . LANGUAGE, SITE_DEFAULT_TITLE);
507
508
            return;
509
        }
510
511 26
        if ($this->pageTitle === null || $this->pageTitle === '') {
512 26
            $this->pageTitle = $this->get('fork.settings')->get('Core', 'site_title_' . LANGUAGE, SITE_DEFAULT_TITLE);
513 26
            $this->pageTitle = $value . ' -  ' . $this->pageTitle;
514
515 26
            return;
516
        }
517
518 18
        $this->pageTitle = $value . ' - ' . $this->pageTitle;
519 18
    }
520
521
    /**
522
     * @param string $title The title (maximum 70 characters)
523
     * @param string $description A brief description of the card (maximum 200 characters)
524
     * @param string $imageUrl The URL of the image (minimum 280x150 and <1MB)
525
     * @param string $cardType The cardtype, possible types: https://dev.twitter.com/cards/types
526
     * @param string $siteHandle (optional)  Twitter handle of the site
527
     * @param string $creatorHandle (optional) Twitter handle of the author
528
     */
529 2
    public function setTwitterCard(
530
        string $title,
531
        string $description,
532
        string $imageUrl,
533
        string $cardType = 'summary',
534
        string $siteHandle = null,
535
        string $creatorHandle = null
536
    ): void {
537 2
        $this->meta->addMetaData(MetaData::forName('twitter:card', $cardType));
538 2
        $this->meta->addMetaData(MetaData::forName('twitter:title', $title));
539 2
        $this->meta->addMetaData(MetaData::forName('twitter:description', $description));
540 2
        $this->meta->addMetaData(MetaData::forName('twitter:image', $imageUrl));
541
542 2
        if ($siteHandle !== null) {
543
            $this->meta->addMetaData(MetaData::forName('twitter:site', $siteHandle));
544
        }
545
546 2
        if ($creatorHandle !== null) {
547
            $this->meta->addMetaData(MetaData::forName('twitter:creator', $creatorHandle));
548
        }
549 2
    }
550
551
    public function addMetaLink(MetaLink $metaLink, bool $overwrite = false): void
552
    {
553
        $this->meta->addMetaLink($metaLink, $overwrite);
554
    }
555
}
556