Skip to content

Commit

Permalink
[FEATURE] By default generate classes for body tag for nuxt-typo3
Browse files Browse the repository at this point in the history
- By default generate pid-[PageID] class & layout-[layout-name] for body tag

- TYPO3 dev can expand default classes via `page.bodyTagAdd` (attributes, are merged, class attribute is also merged by default), so if dev adds custom class via `page.bodyTagAdd` result will be for example `class="pid-1 layout-layout-0 custom-class"`.

 If BE wants to completely overwrite values of body attributes it should communicate it via `config.headless.overwriteBodyTag = 1`. Flag changes behaviour by overwriting default values by values of `page.bodyTagAdd`
  • Loading branch information
twoldanski committed Jan 24, 2025
1 parent 328d11c commit a6cf34b
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 25 deletions.
44 changes: 36 additions & 8 deletions Classes/Seo/MetaHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
use TYPO3\CMS\Frontend\Event\ModifyHrefLangTagsEvent;

use function array_merge;
use function array_merge_recursive;
use function htmlspecialchars;
use function implode;

class MetaHandler
{
Expand All @@ -31,8 +34,11 @@ public function __construct(
private readonly EventDispatcherInterface $eventDispatcher,
) {}

public function process(ServerRequestInterface $request, TypoScriptFrontendController $controller, array $content): array
{
public function process(
ServerRequestInterface $request,
TypoScriptFrontendController $controller,
array $content
): array {
$_params = ['page' => $controller->page, 'request' => $request, '_seoLinks' => []];
$_ref = null;
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Frontend\Page\PageGenerator']['generateMetaTags'] ?? [] as $_funcRef) {
Expand Down Expand Up @@ -78,19 +84,36 @@ public function process(ServerRequestInterface $request, TypoScriptFrontendContr
$language = $request->getAttribute('language');

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

$defaultBodyAttrs = [
'class' => htmlspecialchars(implode(' ', [
'pid-' . $request->getAttribute('routing')->getPageId(),
'layout-' . $content['appearance']['layout'] ?? '',
])),
];

$rawBodyTagAttrs = GeneralUtility::get_tag_attributes(trim($request->getAttribute('frontend.typoscript')->getSetupArray()['page.']['bodyTagAdd'] ?? ''));
$bodyTagAttrs = $this->normalizeAttr($rawBodyTagAttrs);

if ($overwriteBodyTag) {
$bodyTagAttrs = array_merge($defaultBodyAttrs, $rawBodyTagAttrs);
} else {
$bodyTagAttrs = array_map(static function (string|array $attr) {
if (is_array($attr)) {
return implode(' ', $attr);
}

return $attr;
}, array_merge_recursive($defaultBodyAttrs, $rawBodyTagAttrs));
}

$content['seo']['htmlAttrs'] = array_merge([
'lang' => $language->getLocale()->getLanguageCode(),
'dir' => $language->getLocale()->isRightToLeftLanguageDirection() ? 'rtl' : null,
], $htmlTagAttrs);

if ($bodyTagAttrs !== []) {
$content['seo']['bodyAttrs'] = $bodyTagAttrs;
}
$content['seo']['bodyAttrs'] = $this->normalizeAttr($bodyTagAttrs);

return $content;
}
Expand Down Expand Up @@ -140,8 +163,13 @@ protected function generateMetaTagsFromTyposcript(array $metaTagTypoScript, Cont
/**
* @codeCoverageIgnore
*/
private function setMetaTag(string $type, string $name, string $content, array $subProperties = [], $replace = true): void
{
private function setMetaTag(
string $type,
string $name,
string $content,
array $subProperties = [],
$replace = true
): void {
$type = strtolower($type);
$name = strtolower($name);
if (!in_array($type, ['property', 'name', 'http-equiv'], true)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use TYPO3\CMS\Core\Http\ServerRequest;
use TYPO3\CMS\Core\Http\Uri;
use TYPO3\CMS\Core\MetaTag\MetaTagManagerRegistry;
use TYPO3\CMS\Core\Routing\PageArguments;
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
use TYPO3\CMS\Core\TypoScript\AST\Node\RootNode;
use TYPO3\CMS\Core\TypoScript\FrontendTypoScript;
Expand All @@ -42,13 +43,21 @@
class AfterCacheableContentIsGeneratedListenerTest extends UnitTestCase
{
use ProphecyTrait;

protected bool $resetSingletonInstances = true;

public function testNotModifiedWithInvalidOrDisabledJsonContent(): void
{
$metaHandler = new MetaHandler($this->prophesize(MetaTagManagerRegistry::class)->reveal(), $this->prophesize(EventDispatcherInterface::class)->reveal());
$metaHandler = new MetaHandler(
$this->prophesize(MetaTagManagerRegistry::class)->reveal(),
$this->prophesize(EventDispatcherInterface::class)->reveal()
);

$listener = new AfterCacheableContentIsGeneratedListener(new JsonEncoder(), $metaHandler, new HeadlessUserInt());
$listener = new AfterCacheableContentIsGeneratedListener(
new JsonEncoder(),
$metaHandler,
new HeadlessUserInt()
);

$request = $this->prophesize(ServerRequestInterface::class);
$request->getAttribute(Argument::is('headless'))->willReturn(new Headless(HeadlessMode::NONE));
Expand Down Expand Up @@ -77,9 +86,16 @@ public function testNotModifiedWithInvalidOrDisabledJsonContent(): void

public function testNotModifiedWhileValidJson(): void
{
$metaHandler = new MetaHandler($this->prophesize(MetaTagManagerRegistry::class)->reveal(), $this->prophesize(EventDispatcherInterface::class)->reveal());
$metaHandler = new MetaHandler(
$this->prophesize(MetaTagManagerRegistry::class)->reveal(),
$this->prophesize(EventDispatcherInterface::class)->reveal()
);

$listener = new AfterCacheableContentIsGeneratedListener(new JsonEncoder(), $metaHandler, new HeadlessUserInt());
$listener = new AfterCacheableContentIsGeneratedListener(
new JsonEncoder(),
$metaHandler,
new HeadlessUserInt()
);

$content = json_encode(['someCustomPageWithoutMeta' => ['title' => 'test before event']]);

Expand All @@ -99,9 +115,16 @@ public function testNotModifiedWhileValidJson(): void

public function testNotModifiedWhenUserIntContent(): void
{
$metaHandler = new MetaHandler($this->prophesize(MetaTagManagerRegistry::class)->reveal(), $this->prophesize(EventDispatcherInterface::class)->reveal());
$metaHandler = new MetaHandler(
$this->prophesize(MetaTagManagerRegistry::class)->reveal(),
$this->prophesize(EventDispatcherInterface::class)->reveal()
);

$listener = new AfterCacheableContentIsGeneratedListener(new JsonEncoder(), $metaHandler, new HeadlessUserInt());
$listener = new AfterCacheableContentIsGeneratedListener(
new JsonEncoder(),
$metaHandler,
new HeadlessUserInt()
);

$content = json_encode(['someCustomPageWithoutMeta' => ['title' => HeadlessUserInt::NESTED . '_START<<<!--INT_SCRIPT.d53df2a300e62171a7b4882c4b88a153-->>>' . HeadlessUserInt::NESTED . '_END']]);

Expand All @@ -128,7 +151,11 @@ public function testModifiedPageTitle(): void

$metaHandler = new MetaHandler($this->prophesize(MetaTagManagerRegistry::class)->reveal(), $eventDispatcher);

$listener = new AfterCacheableContentIsGeneratedListener(new JsonEncoder(), $metaHandler, new HeadlessUserInt());
$listener = new AfterCacheableContentIsGeneratedListener(
new JsonEncoder(),
$metaHandler,
new HeadlessUserInt()
);

$request = $this->prophesize(ServerRequestInterface::class);
$request->getAttribute(Argument::is('headless'))->willReturn(new Headless(HeadlessMode::FULL));
Expand All @@ -139,22 +166,36 @@ public function testModifiedPageTitle(): void
[]
));

$request->getAttribute('routing')->willReturn(new PageArguments(1, '0', []));
$frontendTyposcript = new FrontendTypoScript(new RootNode(), [], [], []);
$frontendTyposcript->setSetupTree(new RootNode());
$frontendTyposcript->setSetupArray([]);

$request->getAttribute(Argument::is('frontend.typoscript'))->willReturn($frontendTyposcript);

$controller = $this->prophesize(TypoScriptFrontendController::class);
$controller->content = json_encode(['meta' => ['title' => 'test before event'], 'seo' => ['title' => 'test before event']]);
$controller->content = json_encode([
'meta' => ['title' => 'test before event'],
'seo' => ['title' => 'test before event'],
'appearance' => ['layout' => 'layout-0'],
]);
$controller->cObj = $this->prophesize(ContentObjectRenderer::class)->reveal();
$controller->generatePageTitle($request)->willReturn('Modified title via PageTitleProviderManager');

$event = new AfterCacheableContentIsGeneratedEvent($request->reveal(), $controller->reveal(), 'abc', false);

$listener($event);

self::assertSame(json_encode(['meta' => ['title' => 'test before event'], 'seo' => ['title' => 'Modified title via PageTitleProviderManager', 'meta' => [], 'htmlAttrs' => ['lang' => 'en', 'dir' => null]]]), $event->getController()->content);
self::assertSame(json_encode([
'meta' => ['title' => 'test before event'],
'seo' => [
'title' => 'Modified title via PageTitleProviderManager',
'meta' => [],
'htmlAttrs' => ['lang' => 'en', 'dir' => null],
'bodyAttrs' => ['class' => 'pid-1 layout-layout-0'],
],
'appearance' => ['layout' => 'layout-0'],
]), $event->getController()->content);
}

public function testHreflangs(): void
Expand All @@ -169,9 +210,16 @@ public function testHreflangs(): void
$eventDispatcher = $this->prophesize(EventDispatcher::class);
$eventDispatcher->dispatch(Argument::any())->willReturn($event);

$metaHandler = new MetaHandler($this->prophesize(MetaTagManagerRegistry::class)->reveal(), $eventDispatcher->reveal());
$metaHandler = new MetaHandler(
$this->prophesize(MetaTagManagerRegistry::class)->reveal(),
$eventDispatcher->reveal()
);

$listener = new AfterCacheableContentIsGeneratedListener(new JsonEncoder(), $metaHandler, new HeadlessUserInt());
$listener = new AfterCacheableContentIsGeneratedListener(
new JsonEncoder(),
$metaHandler,
new HeadlessUserInt()
);

$request = $this->prophesize(ServerRequestInterface::class);
$request->getAttribute(Argument::is('headless'))->willReturn(new Headless(HeadlessMode::FULL));
Expand All @@ -181,6 +229,7 @@ public function testHreflangs(): void
new Uri('/en'),
[]
));
$request->getAttribute('routing')->willReturn(new PageArguments(2, '0', []));

$frontendTyposcript = new FrontendTypoScript(new RootNode(), [], [], []);
$frontendTyposcript->setSetupTree(new RootNode());
Expand All @@ -190,7 +239,11 @@ public function testHreflangs(): void

$GLOBALS['TYPO3_REQUEST'] = $request->reveal();
$controller = $this->prophesize(TypoScriptFrontendController::class);
$controller->content = json_encode(['meta' => ['title' => 'test before event'], 'seo' => ['title' => 'test before event']]);
$controller->content = json_encode([
'meta' => ['title' => 'test before event'],
'seo' => ['title' => 'test before event'],
'appearance' => ['layout' => 'custom'],
]);
$controller->cObj = $this->prophesize(ContentObjectRenderer::class)->reveal();
$controller->generatePageTitle($request)->willReturn('Modified title via PageTitleProviderManager');

Expand All @@ -210,10 +263,19 @@ public function handle(): void {}

$listener($event);

self::assertSame(json_encode(['meta' => ['title' => 'test before event'], 'seo' => ['title' => 'Modified title via PageTitleProviderManager', 'meta' => [['name' => 'generator', 'content' => 'TYPO3 CMS x T3Headless']], 'link' => [
['rel' => 'alternate', 'hreflang' => 'pl-PL', 'href' => 'https://example.com/pl'],
['rel' => 'alternate', 'hreflang' => 'en-US', 'href' => 'https://example.com/us'],
['rel' => 'alternate', 'hreflang' => 'en-UK', 'href' => 'https://example.com/uk'],
], 'htmlAttrs' => ['lang' => 'en', 'dir' => null]]]), $event->getController()->content);
self::assertSame(json_encode([
'meta' => ['title' => 'test before event'],
'seo' => [
'title' => 'Modified title via PageTitleProviderManager',
'meta' => [['name' => 'generator', 'content' => 'TYPO3 CMS x T3Headless']],
'link' => [
['rel' => 'alternate', 'hreflang' => 'pl-PL', 'href' => 'https://example.com/pl'],
['rel' => 'alternate', 'hreflang' => 'en-US', 'href' => 'https://example.com/us'],
['rel' => 'alternate', 'hreflang' => 'en-UK', 'href' => 'https://example.com/uk'],
],
'htmlAttrs' => ['lang' => 'en', 'dir' => null],
'bodyAttrs' => ['class' => 'pid-2 layout-custom']],
'appearance' => ['layout' => 'custom'],
]), $event->getController()->content);
}
}

0 comments on commit a6cf34b

Please sign in to comment.