• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TYPO3-Headless / headless / 12948910802

24 Jan 2025 11:39AM UTC coverage: 73.13% (+0.2%) from 72.915%
12948910802

Pull #803

github

web-flow
Merge 5337ffe7c into 328d11c14
Pull Request #803: [FEATURE] By default generate classes for body tag for nuxt-typo3

14 of 15 new or added lines in 1 file covered. (93.33%)

1105 of 1511 relevant lines covered (73.13%)

8.3 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.92
/Classes/Seo/MetaHandler.php
1
<?php
2

3
/*
4
 * This file is part of the "headless" Extension for TYPO3 CMS.
5
 *
6
 * For the full copyright and license information, please read the
7
 * LICENSE.md file that was distributed with this source code.
8
 */
9

10
declare(strict_types=1);
11

12
namespace FriendsOfTYPO3\Headless\Seo;
13

14
use InvalidArgumentException;
15
use Psr\EventDispatcher\EventDispatcherInterface;
16
use Psr\Http\Message\ServerRequestInterface;
17
use TYPO3\CMS\Core\MetaTag\MetaTagManagerRegistry;
18
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
19
use TYPO3\CMS\Core\TypoScript\TypoScriptService;
20
use TYPO3\CMS\Core\Utility\GeneralUtility;
21
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
22
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
23
use TYPO3\CMS\Frontend\Event\ModifyHrefLangTagsEvent;
24

25
use function array_merge;
26
use function array_merge_recursive;
27
use function htmlspecialchars;
28
use function implode;
29

30
class MetaHandler
31
{
32
    public function __construct(
33
        private readonly MetaTagManagerRegistry $metaTagRegistry,
34
        private readonly EventDispatcherInterface $eventDispatcher,
35
    ) {}
30✔
36

37
    public function process(
38
        ServerRequestInterface $request,
39
        TypoScriptFrontendController $controller,
40
        array $content
41
    ): array {
42
        $_params = ['page' => $controller->page, 'request' => $request, '_seoLinks' => []];
2✔
43
        $_ref = null;
2✔
44
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Frontend\Page\PageGenerator']['generateMetaTags'] ?? [] as $_funcRef) {
2✔
45
            GeneralUtility::callUserFunction($_funcRef, $_params, $_ref);
1✔
46
        }
47

48
        $content['seo']['title'] = $controller->generatePageTitle($request);
2✔
49

50
        $this->generateMetaTagsFromTyposcript(
2✔
51
            $controller->pSetup['meta.'] ?? [],
2✔
52
            $controller->cObj
2✔
53
        );
2✔
54

55
        $metaTags = [];
2✔
56
        $metaTagManagers = GeneralUtility::makeInstance(MetaTagManagerRegistry::class)->getAllManagers();
2✔
57

58
        foreach ($metaTagManagers as $manager => $managerObject) {
2✔
59
            $properties = json_decode($managerObject->renderAllProperties(), true);
2✔
60
            if (!empty($properties)) {
2✔
61
                $metaTags = array_merge($metaTags, $properties);
1✔
62
            }
63
        }
64

65
        $content['seo']['meta'] = $metaTags;
2✔
66

67
        $hrefLangs = $this->eventDispatcher->dispatch(new ModifyHrefLangTagsEvent($request))->getHrefLangs();
2✔
68

69
        $seoLinks = $_params['_seoLinks'] ?? [];
2✔
70

71
        if (count($hrefLangs) > 1) {
2✔
72
            foreach ($hrefLangs as $hrefLang => $href) {
1✔
73
                $seoLinks[] = ['rel' => 'alternate', 'hreflang' => $hrefLang, 'href' => $href];
1✔
74
            }
75
        }
76

77
        if ($seoLinks !== []) {
2✔
78
            $content['seo']['link'] = $seoLinks;
1✔
79
        }
80

81
        /**
82
         * @var SiteLanguage $language
83
         */
84
        $language = $request->getAttribute('language');
2✔
85

86
        $rawHtmlTagAttrs = $controller->config['config']['htmlTag.']['attributes.'] ?? [];
2✔
87
        $overwriteBodyTag = (int)($controller->config['config']['headless.']['overwriteBodyTag'] ?? 0);
2✔
88
        $htmlTagAttrs = $this->normalizeAttr($rawHtmlTagAttrs);
2✔
89

90
        $defaultBodyAttrs = [
2✔
91
            'class' => implode(' ', [
2✔
92
                'pid-' . $request->getAttribute('routing')->getPageId(),
2✔
93
                'layout-' . $content['appearance']['layout'] ?? '',
2✔
94
            ]),
2✔
95
        ];
2✔
96

97
        $rawBodyTagAttrs = GeneralUtility::get_tag_attributes(trim($request->getAttribute('frontend.typoscript')->getSetupArray()['page.']['bodyTagAdd'] ?? ''));
2✔
98

99
        if ($overwriteBodyTag) {
2✔
NEW
100
            $bodyTagAttrs = array_merge($defaultBodyAttrs, $rawBodyTagAttrs);
×
101
        } else {
102
            $bodyTagAttrs = array_map(static function (string|array $attr) {
2✔
103
                if (is_array($attr)) {
2✔
104
                    return implode(' ', $attr);
1✔
105
                }
106

107
                return $attr;
1✔
108
            }, array_merge_recursive($defaultBodyAttrs, $rawBodyTagAttrs));
2✔
109
        }
110

111
        $content['seo']['htmlAttrs'] = array_merge([
2✔
112
            'lang' => $language->getLocale()->getLanguageCode(),
2✔
113
            'dir' => $language->getLocale()->isRightToLeftLanguageDirection() ? 'rtl' : null,
2✔
114
        ], $htmlTagAttrs);
2✔
115

116
        $content['seo']['bodyAttrs'] = $this->normalizeAttr($bodyTagAttrs);
2✔
117

118
        return $content;
2✔
119
    }
120

121
    /**
122
     * @codeCoverageIgnore
123
     */
124
    protected function generateMetaTagsFromTyposcript(array $metaTagTypoScript, ContentObjectRenderer $cObj)
125
    {
126
        $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
127
        $conf = $typoScriptService->convertTypoScriptArrayToPlainArray($metaTagTypoScript);
128
        foreach ($conf as $key => $properties) {
129
            $replace = false;
130
            if (is_array($properties)) {
131
                $nodeValue = $properties['_typoScriptNodeValue'] ?? '';
132
                $value = trim((string)$cObj->stdWrap($nodeValue, $metaTagTypoScript[$key . '.']));
133
                if ($value === '' && !empty($properties['value'])) {
134
                    $value = $properties['value'];
135
                    $replace = false;
136
                }
137
            } else {
138
                $value = $properties;
139
            }
140

141
            $attribute = 'name';
142
            if ((is_array($properties) && !empty($properties['httpEquivalent'])) || strtolower($key) === 'refresh') {
143
                $attribute = 'http-equiv';
144
            }
145
            if (is_array($properties) && !empty($properties['attribute'])) {
146
                $attribute = $properties['attribute'];
147
            }
148
            if (is_array($properties) && !empty($properties['replace'])) {
149
                $replace = true;
150
            }
151

152
            if (!is_array($value)) {
153
                $value = (array)$value;
154
            }
155
            foreach ($value as $subValue) {
156
                if (trim($subValue ?? '') !== '') {
157
                    $this->setMetaTag($attribute, $key, $subValue, [], $replace);
158
                }
159
            }
160
        }
161
    }
162

163
    /**
164
     * @codeCoverageIgnore
165
     */
166
    private function setMetaTag(
167
        string $type,
168
        string $name,
169
        string $content,
170
        array $subProperties = [],
171
        $replace = true
172
    ): void {
173
        $type = strtolower($type);
174
        $name = strtolower($name);
175
        if (!in_array($type, ['property', 'name', 'http-equiv'], true)) {
176
            throw new InvalidArgumentException(
177
                'When setting a meta tag the only types allowed are property, name or http-equiv. "' . $type . '" given.',
178
                1496402460
179
            );
180
        }
181
        $manager = $this->metaTagRegistry->getManagerForProperty($name);
182
        $manager->addProperty($name, $content, $subProperties, $replace, $type);
183
    }
184

185
    /**
186
     * @codeCoverageIgnore
187
     */
188
    private function normalizeAttr(array $rawHtmlAttrs): array
189
    {
190
        $htmlAttrs = [];
191

192
        foreach ($rawHtmlAttrs as $attr => $value) {
193
            $htmlAttrs[htmlspecialchars((string)$attr)] = htmlspecialchars((string)$value);
194
        }
195
        return $htmlAttrs;
196
    }
197
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc