From 9664371bdc229f039e3cef0a1bc57103f9aae41b Mon Sep 17 00:00:00 2001 From: Riny van Tiggelen Date: Mon, 18 Jul 2022 09:39:48 +0200 Subject: [PATCH 1/6] [FEATURE] Possibility to automatically add Yoast SEO functionality to custom records --- .github/workflows/ci.yml | 7 +- CHANGELOG.md | 7 + Classes/EventListener/AbstractListener.php | 20 ++ .../EventListener/RecordCanonicalListener.php | 51 ++++ .../TableDefinitionsListener.php | 21 ++ Classes/EventListener/TcaBuiltListener.php | 22 ++ .../Exception/UnsupportedVersionException.php | 9 + .../MetaTag/Generator/AbstractGenerator.php | 70 +++++ .../Generator/DescriptionGenerator.php | 21 ++ .../MetaTag/Generator/OpenGraphGenerator.php | 56 ++++ Classes/MetaTag/Generator/RobotsGenerator.php | 26 ++ .../MetaTag/Generator/TwitterGenerator.php | 60 +++++ Classes/MetaTag/RecordMetaTagGenerator.php | 34 +++ Classes/PageTitle/RecordPageTitleProvider.php | 44 ++++ Classes/Record/Builder/AbstractBuilder.php | 24 ++ Classes/Record/Builder/SchemaBuilder.php | 67 +++++ Classes/Record/Builder/TcaBuilder.php | 103 ++++++++ Classes/Record/Record.php | 192 ++++++++++++++ Classes/Record/RecordRegistry.php | 85 ++++++ Classes/Record/RecordService.php | 81 ++++++ Classes/Service/TcaService.php | 245 ++++++++++++++++++ Classes/Utility/RecordUtility.php | 21 ++ Configuration/Services.yaml | 44 ++++ Configuration/TCA/Overrides/pages.php | 226 +--------------- .../TypoScript/Setup/Config.typoscript | 22 +- composer.json | 8 +- ext_emconf.php | 1 - ext_localconf.php | 24 +- 28 files changed, 1342 insertions(+), 249 deletions(-) create mode 100644 Classes/EventListener/AbstractListener.php create mode 100644 Classes/EventListener/RecordCanonicalListener.php create mode 100644 Classes/EventListener/TableDefinitionsListener.php create mode 100644 Classes/EventListener/TcaBuiltListener.php create mode 100644 Classes/Exception/UnsupportedVersionException.php create mode 100644 Classes/MetaTag/Generator/AbstractGenerator.php create mode 100644 Classes/MetaTag/Generator/DescriptionGenerator.php create mode 100644 Classes/MetaTag/Generator/OpenGraphGenerator.php create mode 100644 Classes/MetaTag/Generator/RobotsGenerator.php create mode 100644 Classes/MetaTag/Generator/TwitterGenerator.php create mode 100644 Classes/MetaTag/RecordMetaTagGenerator.php create mode 100644 Classes/PageTitle/RecordPageTitleProvider.php create mode 100644 Classes/Record/Builder/AbstractBuilder.php create mode 100644 Classes/Record/Builder/SchemaBuilder.php create mode 100644 Classes/Record/Builder/TcaBuilder.php create mode 100644 Classes/Record/Record.php create mode 100644 Classes/Record/RecordRegistry.php create mode 100644 Classes/Record/RecordService.php create mode 100644 Classes/Service/TcaService.php create mode 100644 Classes/Utility/RecordUtility.php create mode 100644 Configuration/Services.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index deac6b40..861f7679 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,13 +11,10 @@ jobs: max-parallel: 6 fail-fast: false matrix: - typo3: ['9', '10'] - php: ['php7.2', 'php7.3', 'php7.4'] + typo3: ['9', '10', '11'] + php: ['php7.4'] experimental: [false] include: - - typo3: '11' - php: 'php7.4' - experimental: false - typo3: '11' php: 'php8.0' experimental: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e017e64..fcea3a6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ We will follow [Semantic Versioning](http://semver.org/). ## Yoast SEO Premium for TYPO3 Besides the free version of our plugin, we also have a premium version. The free version enables you to do all necessary optimizations. With the premium version, we make it even easier to do! More information can be found on https://www.maxserv.com/yoast. +## UNRELEASED +### Breaking +- Dropped support for PHP <7.4 + +### Added +- New feature to automatically activate Yoast SEO functionality on custom records + ## 8.2.0 June 23, 2022 ### Added - Support for new TYPO3 composer mode diff --git a/Classes/EventListener/AbstractListener.php b/Classes/EventListener/AbstractListener.php new file mode 100644 index 00000000..2a0dc990 --- /dev/null +++ b/Classes/EventListener/AbstractListener.php @@ -0,0 +1,20 @@ +builder = $builder; + } +} diff --git a/Classes/EventListener/RecordCanonicalListener.php b/Classes/EventListener/RecordCanonicalListener.php new file mode 100644 index 00000000..1036c688 --- /dev/null +++ b/Classes/EventListener/RecordCanonicalListener.php @@ -0,0 +1,51 @@ +typoScriptFrontendController = $typoScriptFrontendController; + $this->recordService = $recordService; + } + + public function setCanonical(ModifyUrlForCanonicalTagEvent $event): void + { + $activeRecord = $this->recordService->getActiveRecord(); + if (!$activeRecord instanceof Record) { + return; + } + + $canonicalLink = $activeRecord->getRecordData()['canonical_link'] ?? ''; + if (empty($canonicalLink)) { + return; + } + + $event->setUrl( + $this->typoScriptFrontendController->cObj->typoLink_URL([ + 'parameter' => $canonicalLink, + 'forceAbsoluteUrl' => true, + ]) + ); + } +} diff --git a/Classes/EventListener/TableDefinitionsListener.php b/Classes/EventListener/TableDefinitionsListener.php new file mode 100644 index 00000000..80562f76 --- /dev/null +++ b/Classes/EventListener/TableDefinitionsListener.php @@ -0,0 +1,21 @@ +getRecords() as $record) { + $this->builder + ->setRecord($record) + ->build(); + } + $event->addSqlData(implode(LF . LF, $this->builder->getResult())); + } +} diff --git a/Classes/EventListener/TcaBuiltListener.php b/Classes/EventListener/TcaBuiltListener.php new file mode 100644 index 00000000..4574a356 --- /dev/null +++ b/Classes/EventListener/TcaBuiltListener.php @@ -0,0 +1,22 @@ +getTca(); + foreach (RecordRegistry::getInstance()->getRecords() as $record) { + $this->builder + ->setRecord($record) + ->build(); + } + $event->setTca($GLOBALS['TCA']); + } +} diff --git a/Classes/Exception/UnsupportedVersionException.php b/Classes/Exception/UnsupportedVersionException.php new file mode 100644 index 00000000..be93f583 --- /dev/null +++ b/Classes/Exception/UnsupportedVersionException.php @@ -0,0 +1,9 @@ +managerRegistry = $managerRegistry; + } + + /** + * @see \TYPO3\CMS\Seo\MetaTag\MetaTagGenerator + * @param array $fileReferences + * @return array + */ + protected function generateSocialImages(array $fileReferences): array + { + $imageService = GeneralUtility::makeInstance(ImageService::class); + + $socialImages = []; + + /** @var FileReference $file */ + foreach ($fileReferences as $file) { + $arguments = $file->getProperties(); + $cropVariantCollection = CropVariantCollection::create((string)$arguments['crop']); + $cropVariant = ($arguments['cropVariant'] ?? false) ?: 'social'; + $cropArea = $cropVariantCollection->getCropArea($cropVariant); + $crop = $cropArea->makeAbsoluteBasedOnFile($file); + + $processingConfiguration = [ + 'crop' => $crop, + 'maxWidth' => 2000, + ]; + + $processedImage = $file->getOriginalFile()->process( + ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, + $processingConfiguration + ); + + $imageUri = $imageService->getImageUri($processedImage, true); + + $socialImages[] = [ + 'url' => $imageUri, + 'width' => floor($processedImage->getProperty('width')), + 'height' => floor($processedImage->getProperty('height')), + 'alternative' => $arguments['alternative'], + ]; + } + + return $socialImages; + } + + abstract public function generate(Record $record): void; +} diff --git a/Classes/MetaTag/Generator/DescriptionGenerator.php b/Classes/MetaTag/Generator/DescriptionGenerator.php new file mode 100644 index 00000000..0f1c3ee8 --- /dev/null +++ b/Classes/MetaTag/Generator/DescriptionGenerator.php @@ -0,0 +1,21 @@ +getRecordData()[$record->getDescriptionField()] ?? ''; + + if (!empty($description)) { + $manager = $this->managerRegistry->getManagerForProperty('description'); + $manager->removeProperty('description'); + $manager->addProperty('description', $description); + } + } +} diff --git a/Classes/MetaTag/Generator/OpenGraphGenerator.php b/Classes/MetaTag/Generator/OpenGraphGenerator.php new file mode 100644 index 00000000..3b079076 --- /dev/null +++ b/Classes/MetaTag/Generator/OpenGraphGenerator.php @@ -0,0 +1,56 @@ +getRecordData()['og_title'] ?? ''; + if (!empty($ogTitle)) { + $manager = $this->managerRegistry->getManagerForProperty('og:title'); + $manager->removeProperty('og:title'); + $manager->addProperty('og:title', $ogTitle); + } + + $ogDescription = $record->getRecordData()['og_description'] ?? ''; + if ($ogDescription) { + $manager = $this->managerRegistry->getManagerForProperty('og:description'); + $manager->removeProperty('og:description'); + $manager->addProperty('og:description', $ogDescription); + } + + if ($record->getRecordData()['og_image']) { + $fileCollector = GeneralUtility::makeInstance(FileCollector::class); + $fileCollector->addFilesFromRelation($record->getTableName(), 'og_image', $record->getRecordData()); + $manager = $this->managerRegistry->getManagerForProperty('og:image'); + + $ogImages = $this->generateSocialImages($fileCollector->getFiles()); + if (count($ogImages) > 0) { + $manager->removeProperty('og:image'); + } + foreach ($ogImages as $ogImage) { + $subProperties = []; + $subProperties['url'] = $ogImage['url']; + $subProperties['width'] = $ogImage['width']; + $subProperties['height'] = $ogImage['height']; + + if (!empty($ogImage['alternative'])) { + $subProperties['alt'] = $ogImage['alternative']; + } + + $manager->addProperty( + 'og:image', + $ogImage['url'], + $subProperties + ); + } + } + } +} diff --git a/Classes/MetaTag/Generator/RobotsGenerator.php b/Classes/MetaTag/Generator/RobotsGenerator.php new file mode 100644 index 00000000..c08f47af --- /dev/null +++ b/Classes/MetaTag/Generator/RobotsGenerator.php @@ -0,0 +1,26 @@ +getRecordData()['no_index'], $record->getRecordData()['no_follow'])) { + return; + } + + $noIndex = ((bool)$record->getRecordData()['no_index']) ? 'noindex' : 'index'; + $noFollow = ((bool)$record->getRecordData()['no_follow']) ? 'nofollow' : 'follow'; + + if ($noIndex === 'noindex' || $noFollow === 'nofollow') { + $manager = $this->managerRegistry->getManagerForProperty('robots'); + $manager->removeProperty('robots'); + $manager->addProperty('robots', implode(',', [$noIndex, $noFollow])); + } + } +} diff --git a/Classes/MetaTag/Generator/TwitterGenerator.php b/Classes/MetaTag/Generator/TwitterGenerator.php new file mode 100644 index 00000000..1e6276d6 --- /dev/null +++ b/Classes/MetaTag/Generator/TwitterGenerator.php @@ -0,0 +1,60 @@ +getRecordData()['twitter_card'] ?? ''; + if (!empty($twitterCard)) { + $manager = $this->managerRegistry->getManagerForProperty('twitter:card'); + $manager->removeProperty('twitter:card'); + $manager->addProperty('twitter:card', $twitterCard); + } + + $twitterTitle = $record->getRecordData()['twitter_title'] ?? ''; + if (!empty($twitterTitle)) { + $manager = $this->managerRegistry->getManagerForProperty('twitter:title'); + $manager->removeProperty('twitter:title'); + $manager->addProperty('twitter:title', $twitterTitle); + } + + $twitterDescription = $record->getRecordData()['twitter_description']; + if (!empty($twitterDescription)) { + $manager = $this->managerRegistry->getManagerForProperty('twitter:description'); + $manager->removeProperty('twitter:description'); + $manager->addProperty('twitter:description', $twitterDescription); + } + + if ($record->getRecordData()['twitter_image']) { + $fileCollector = GeneralUtility::makeInstance(FileCollector::class); + $fileCollector->addFilesFromRelation($record->getTableName(), 'twitter_image', $record->getRecordData()); + $manager = $this->managerRegistry->getManagerForProperty('twitter:image'); + + $twitterImages = $this->generateSocialImages($fileCollector->getFiles()); + if (count($twitterImages) > 0) { + $manager->removeProperty('twitter:image'); + } + foreach ($twitterImages as $twitterImage) { + $subProperties = []; + + if (!empty($twitterImage['alternative'])) { + $subProperties['alt'] = $twitterImage['alternative']; + } + + $manager->addProperty( + 'twitter:image', + $twitterImage['url'], + $subProperties + ); + } + } + } +} diff --git a/Classes/MetaTag/RecordMetaTagGenerator.php b/Classes/MetaTag/RecordMetaTagGenerator.php new file mode 100644 index 00000000..05061c8a --- /dev/null +++ b/Classes/MetaTag/RecordMetaTagGenerator.php @@ -0,0 +1,34 @@ +recordService = $recordService; + } + + public function generate(): void + { + $activeRecord = $this->recordService->getActiveRecord(); + if (!$activeRecord instanceof Record || !$activeRecord->shouldGenerateMetaTags()) { + return; + } + + foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['yoast_seo']['recordMetaTags'] ?? [] as $generatorClass) { + GeneralUtility::makeInstance($generatorClass)->generate($activeRecord); + } + } +} diff --git a/Classes/PageTitle/RecordPageTitleProvider.php b/Classes/PageTitle/RecordPageTitleProvider.php new file mode 100644 index 00000000..14d9e878 --- /dev/null +++ b/Classes/PageTitle/RecordPageTitleProvider.php @@ -0,0 +1,44 @@ +recordService = $recordService; + } + + public function getTitle(): string + { + $activeRecord = $this->recordService->getActiveRecord(); + if (!$activeRecord instanceof Record || !$activeRecord->shouldGeneratePageTitle()) { + return ''; + } + + $recordData = $activeRecord->getRecordData(); + + $title = $recordData[$activeRecord->getTitleField()] ?? ''; + if (!isset($recordData['seo_title'])) { + return $title; + } + + if (!empty($recordData['seo_title'])) { + $title = $recordData['seo_title']; + } + + return $title; + } +} diff --git a/Classes/Record/Builder/AbstractBuilder.php b/Classes/Record/Builder/AbstractBuilder.php new file mode 100644 index 00000000..beb4a59f --- /dev/null +++ b/Classes/Record/Builder/AbstractBuilder.php @@ -0,0 +1,24 @@ +record = $record; + return $this; + } + + abstract public function build(): void; + abstract public function getResult(): array; +} diff --git a/Classes/Record/Builder/SchemaBuilder.php b/Classes/Record/Builder/SchemaBuilder.php new file mode 100644 index 00000000..c0d9c778 --- /dev/null +++ b/Classes/Record/Builder/SchemaBuilder.php @@ -0,0 +1,67 @@ +sqlData[] = 'CREATE TABLE ' . $this->record->getTableName() . ' ('; + + if ($this->record->hasDefaultSeoFields()) { + $this->addDefaultSeoFields(); + } + + if ($this->record->hasYoastSeoFields()) { + $this->addYoastSeoFields(); + } + + if ($this->record->shouldAddDescriptionField()) { + $this->addDescriptionField(); + } + + $this->sqlData[] = ');'; + } + + protected function addDefaultSeoFields(): void + { + $this->sqlData[] = "seo_title varchar(255) DEFAULT '' NOT NULL,"; + $this->sqlData[] = "no_index tinyint(4) DEFAULT '0' NOT NULL,"; + $this->sqlData[] = "no_follow tinyint(4) DEFAULT '0' NOT NULL,"; + $this->sqlData[] = "og_title varchar(255) DEFAULT '' NOT NULL,"; + $this->sqlData[] = "og_description text,"; + $this->sqlData[] = "og_image int(11) unsigned DEFAULT '0' NOT NULL,"; + $this->sqlData[] = "twitter_title varchar(255) DEFAULT '' NOT NULL,"; + $this->sqlData[] = "twitter_description text,"; + $this->sqlData[] = "twitter_image int(11) unsigned DEFAULT '0' NOT NULL,"; + $this->sqlData[] = "twitter_card varchar(255) DEFAULT '' NOT NULL,"; + $this->sqlData[] = "canonical_link varchar(2048) DEFAULT '' NOT NULL,"; + $this->sqlData[] = "sitemap_priority decimal(2,1) DEFAULT '0.5' NOT NULL,"; + $this->sqlData[] = "sitemap_changefreq varchar(10) DEFAULT '' NOT NULL,"; + } + + protected function addYoastSeoFields(): void + { + $this->sqlData[] = "tx_yoastseo_focuskeyword tinytext,"; + $this->sqlData[] = "tx_yoastseo_focuskeyword_synonyms tinytext,"; + $this->sqlData[] = "tx_yoastseo_cornerstone tinyint(3) DEFAULT '0' NOT NULL,"; + $this->sqlData[] = "tx_yoastseo_score_readability varchar(50) DEFAULT '' NOT NULL,"; + $this->sqlData[] = "tx_yoastseo_score_seo varchar(50) DEFAULT '' NOT NULL,"; + $this->sqlData[] = "tx_yoastseo_focuskeyword_premium int(11) DEFAULT '0' NOT NULL,"; + $this->sqlData[] = "KEY tx_yoastseo_cornerstone (tx_yoastseo_cornerstone),"; + } + + protected function addDescriptionField(): void + { + $this->sqlData[] = $this->record->getDescriptionField() . " text,"; + } + + public function getResult(): array + { + return $this->sqlData; + } +} diff --git a/Classes/Record/Builder/TcaBuilder.php b/Classes/Record/Builder/TcaBuilder.php new file mode 100644 index 00000000..b89803de --- /dev/null +++ b/Classes/Record/Builder/TcaBuilder.php @@ -0,0 +1,103 @@ +record->hasDefaultSeoFields()) { + $this->addDefaultSeoFields(); + } + + if ($this->record->hasYoastSeoFields()) { + $this->addYoastFields(); + } + + if ($this->record->shouldAddDescriptionField()) { + $this->addDescriptionField(); + } + + $GLOBALS['TCA'][$this->record->getTableName()]['yoast_seo'] = [ + 'defaultSeoFields' => $this->record->hasDefaultSeoFields(), + 'yoastFields' => $this->record->hasYoastSeoFields(), + 'descriptionField' => $this->record->getDescriptionField(), + 'addDescriptionField' => $this->record->shouldAddDescriptionField(), + 'getParameters' => $this->record->getGetParameters(), + ]; + } + + protected function addDefaultSeoFields(): void + { + $tca = [ + 'palettes' => [ + 'seo' => $GLOBALS['TCA']['pages']['palettes']['seo'], + 'robots' => $GLOBALS['TCA']['pages']['palettes']['robots'], + 'canonical' => $GLOBALS['TCA']['pages']['palettes']['canonical'], + 'sitemap' => $GLOBALS['TCA']['pages']['palettes']['sitemap'], + 'opengraph' => $GLOBALS['TCA']['pages']['palettes']['opengraph'], + 'twittercards' => $GLOBALS['TCA']['pages']['palettes']['twittercards'], + ], + 'columns' => [ + 'seo_title' => $GLOBALS['TCA']['pages']['columns']['seo_title'], + 'no_index' => $GLOBALS['TCA']['pages']['columns']['no_index'], + 'no_follow' => $GLOBALS['TCA']['pages']['columns']['no_follow'], + 'sitemap_changefreq' => $GLOBALS['TCA']['pages']['columns']['sitemap_changefreq'], + 'sitemap_priority' => $GLOBALS['TCA']['pages']['columns']['sitemap_priority'], + 'canonical_link' => $GLOBALS['TCA']['pages']['columns']['canonical_link'], + 'og_title' => $GLOBALS['TCA']['pages']['columns']['og_title'], + 'og_description' => $GLOBALS['TCA']['pages']['columns']['og_description'], + 'og_image' => $GLOBALS['TCA']['pages']['columns']['og_image'], + 'twitter_title' => $GLOBALS['TCA']['pages']['columns']['twitter_title'], + 'twitter_description' => $GLOBALS['TCA']['pages']['columns']['twitter_description'], + 'twitter_image' => $GLOBALS['TCA']['pages']['columns']['twitter_image'], + 'twitter_card' => $GLOBALS['TCA']['pages']['columns']['twitter_card'], + ] + ]; + $GLOBALS['TCA'][$this->record->getTableName()] = array_replace_recursive( + $GLOBALS['TCA'][$this->record->getTableName()], + $tca + ); + + ExtensionManagementUtility::addToAllTCAtypes($this->record->getTableName(), ' + --div--;LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.tabs.seo, + --palette--;;seo, + --palette--;;robots, + --palette--;;canonical, + --palette--;;sitemap, + --div--;LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.tabs.socialmedia, + --palette--;;opengraph, + --palette--;;twittercards', + $this->record->getTypes(), + $this->record->getFieldsPosition() + ); + } + + protected function addYoastFields(): void + { + GeneralUtility::makeInstance(TcaService::class) + ->addYoastFields( + $this->record->getTableName(), + $this->record->getTypes(), + $this->record->getDescriptionField() + ); + } + + protected function addDescriptionField(): void + { + ExtensionManagementUtility::addTCAcolumns($this->record->getTableName(), [ + $this->record->getDescriptionField() => $GLOBALS['TCA']['pages']['columns']['description'] + ]); + } + + public function getResult(): array + { + return $GLOBALS['TCA']; + } +} diff --git a/Classes/Record/Record.php b/Classes/Record/Record.php new file mode 100644 index 00000000..1eefd136 --- /dev/null +++ b/Classes/Record/Record.php @@ -0,0 +1,192 @@ +tableName; + } + + public function setTableName(string $tableName): self + { + $this->tableName = $tableName; + return $this; + } + + public function hasDefaultSeoFields(): bool + { + return $this->defaultSeoFields; + } + + public function setDefaultSeoFields(bool $defaultSeoFields): Record + { + $this->defaultSeoFields = $defaultSeoFields; + return $this; + } + + public function hasYoastSeoFields(): bool + { + return $this->yoastSeoFields; + } + + public function setYoastSeoFields(bool $yoastSeoFields): Record + { + $this->yoastSeoFields = $yoastSeoFields; + return $this; + } + + public function getTypes(): string + { + return $this->types; + } + + public function setTypes(string $types): Record + { + $this->types = $types; + return $this; + } + + public function getTitleField(): string + { + return $this->titleField; + } + + public function setTitleField(string $titleField): Record + { + $this->titleField = $titleField; + return $this; + } + + public function getDescriptionField(): string + { + return $this->descriptionField; + } + + public function setDescriptionField(string $descriptionField): Record + { + $this->descriptionField = $descriptionField; + return $this; + } + + public function shouldAddDescriptionField(): bool + { + return $this->addDescriptionField; + } + + public function setAddDescriptionField(bool $addDescriptionField): Record + { + $this->addDescriptionField = $addDescriptionField; + return $this; + } + + public function getFieldsPosition(): string + { + return $this->fieldsPosition; + } + + public function setFieldsPosition(string $fieldsPosition): Record + { + $this->fieldsPosition = $fieldsPosition; + return $this; + } + + public function getOverrideTca(): array + { + return $this->overrideTca; + } + + public function setOverrideTca(array $overrideTca): Record + { + $this->overrideTca = $overrideTca; + return $this; + } + + public function getGetParameters(): array + { + return $this->getParameters; + } + + public function setGetParameters(array $getParameters): Record + { + $this->getParameters = $getParameters; + return $this; + } + + public function getRecordUid(): ?int + { + return $this->recordUid; + } + + public function setRecordUid(int $recordUid): Record + { + $this->recordUid = $recordUid; + return $this; + } + + public function getRecordData(): array + { + return $this->recordData; + } + + public function setRecordData(array $recordData): Record + { + $this->recordData = $recordData; + return $this; + } + + public function shouldGeneratePageTitle(): bool + { + return $this->generatePageTitle; + } + + public function setGeneratePageTitle(bool $generatePageTitle): Record + { + $this->generatePageTitle = $generatePageTitle; + return $this; + } + + public function shouldGenerateMetaTags(): bool + { + return $this->generateMetaTags; + } + + public function setGenerateMetaTags(bool $generateMetaTags): Record + { + $this->generateMetaTags = $generateMetaTags; + return $this; + } +} diff --git a/Classes/Record/RecordRegistry.php b/Classes/Record/RecordRegistry.php new file mode 100644 index 00000000..77cc1aa9 --- /dev/null +++ b/Classes/Record/RecordRegistry.php @@ -0,0 +1,85 @@ +getMajorVersion() < 10) { + throw new UnsupportedVersionException( + 'The record feature is only available for TYPO3 10 and higher', + 1659438424 + ); + } + return $this->records[$tableName] + ?? ($this->records[$tableName] = GeneralUtility::makeInstance(Record::class)->setTableName($tableName)); + } + + public function removeRecord(string $tableName): void + { + if (isset($this->records[$tableName])) { + unset($this->records); + } + } + + /** + * @return \YoastSeoForTypo3\YoastSeo\Record\Record[] + */ + public function getRecords(): array + { + if ($this->records === null) { + $this->retrieveRecords(); + } + return $this->records; + } + + protected function retrieveRecords(): void + { + $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('yoastseo_recordcache'); + if (($recordsFromCache = $cache->get('records')) !== false) { + $this->records = $recordsFromCache; + return; + } + + $this->buildRecordsFromTca(); + $cache->set('records', $this->records); + } + + protected function buildRecordsFromTca(): void + { + $this->records = []; + foreach ($GLOBALS['TCA'] as $table => $tca) { + if (!isset($tca['yoast_seo'])) { + continue; + } + + $yoastSeoConfiguration = $tca['yoast_seo']; + + $this->addRecord($table) + ->setDefaultSeoFields((bool)($yoastSeoConfiguration['defaultSeoFields'] ?? true)) + ->setYoastSeoFields((bool)($yoastSeoConfiguration['yoastFields'] ?? true)) + ->setDescriptionField((string)($yoastSeoConfiguration['descriptionField'] ?? 'description')) + ->setAddDescriptionField((bool)($yoastSeoConfiguration['addDescriptionField'] ?? false)) + ->setGetParameters((array)($yoastSeoConfiguration['getParameters'] ?? [])); + } + } +} diff --git a/Classes/Record/RecordService.php b/Classes/Record/RecordService.php new file mode 100644 index 00000000..da2177f1 --- /dev/null +++ b/Classes/Record/RecordService.php @@ -0,0 +1,81 @@ +recordsChecked) { + return $this->activeRecord; + } + + $records = RecordRegistry::getInstance()->getRecords(); + if (empty($records)) { + $this->recordsChecked = true; + return null; + } + + $this->activeRecord = $this->findRecord($records); + if ($this->activeRecord instanceof Record) { + $recordData = $this->getRecordData($this->activeRecord); + $this->activeRecord->setRecordData($recordData); + } + + $this->recordsChecked = true; + return $this->activeRecord; + } + + /** + * @param \YoastSeoForTypo3\YoastSeo\Record\Record[] $records + * @return \YoastSeoForTypo3\YoastSeo\Record\Record|null + */ + protected function findRecord(array $records): ?Record + { + $currentGetParameters = GeneralUtility::_GET(); + + foreach ($records as $record) { + if (empty($record->getGetParameters())) { + continue; + } + + foreach ($record->getGetParameters() as $getParameters) { + try { + $getValue = ArrayUtility::getValueByPath($currentGetParameters, implode('/', $getParameters)); + } catch (MissingArrayPathException $e) { + $getValue = null; + } + if ($getValue !== null) { + $record->setRecordUid((int)$getValue); + return $record; + } + } + } + + return null; + } + + protected function getRecordData(Record $record): array + { + $recordRow = GeneralUtility::makeInstance(ConnectionPool::class) + ->getConnectionForTable($record->getTableName()) + ->select(['*'], $record->getTableName(), ['uid' => $record->getRecordUid()]) + ->fetch(); + + return (array)GeneralUtility::makeInstance(PageRepository::class) + ->getLanguageOverlay($record->getTableName(), (array)$recordRow); + } +} diff --git a/Classes/Service/TcaService.php b/Classes/Service/TcaService.php new file mode 100644 index 00000000..84fa3e54 --- /dev/null +++ b/Classes/Service/TcaService.php @@ -0,0 +1,245 @@ +table = $table; + $this->types = $types; + $this->descriptionField = $descriptionField; + + $this->addDefaultFields(); + + if (!YoastUtility::isPremiumInstalled()) { + $this->addPremiumFields(); + } + + $this->addPalettes(); + $this->addToTypes(); + } + + protected function addDefaultFields(): void + { + $columns = [ + 'tx_yoastseo_snippetpreview' => [ + 'label' => self::LL_PREFIX . 'snippetPreview', + 'exclude' => true, + 'displayCond' => 'FIELD:tx_yoastseo_hide_snippet_preview:REQ:false', + 'config' => [ + 'type' => 'none', + 'renderType' => 'snippetPreview', + 'settings' => [ + 'titleField' => 'seo_title', + 'descriptionField' => $this->descriptionField + ] + ] + ], + 'tx_yoastseo_readability_analysis' => [ + 'label' => self::LL_PREFIX . 'analysis', + 'exclude' => true, + 'displayCond' => 'FIELD:tx_yoastseo_hide_snippet_preview:REQ:false', + 'config' => [ + 'type' => 'none', + 'renderType' => 'readabilityAnalysis' + ] + ], + 'tx_yoastseo_focuskeyword' => [ + 'label' => self::LL_PREFIX . 'seoFocusKeyword', + 'exclude' => true, + 'displayCond' => 'FIELD:tx_yoastseo_hide_snippet_preview:REQ:false', + 'config' => [ + 'type' => 'input', + ] + ], + 'tx_yoastseo_focuskeyword_analysis' => [ + 'label' => self::LL_PREFIX . 'analysis', + 'exclude' => true, + 'displayCond' => 'FIELD:tx_yoastseo_hide_snippet_preview:REQ:false', + 'config' => [ + 'type' => 'none', + 'renderType' => 'focusKeywordAnalysis', + 'settings' => [ + 'focusKeywordField' => 'tx_yoastseo_focuskeyword', + ] + ] + ], + 'tx_yoastseo_cornerstone' => [ + 'label' => '', + 'exclude' => true, + 'config' => [ + 'type' => 'input', + 'default' => 0, + 'renderType' => 'cornerstone' + ] + ], + 'tx_yoastseo_score_readability' => [ + 'label' => '', + 'exclude' => false, + 'config' => [ + 'type' => 'passthrough' + ] + ], + 'tx_yoastseo_score_seo' => [ + 'label' => '', + 'exclude' => false, + 'config' => [ + 'type' => 'passthrough' + ] + ], + ]; + if ($this->table === 'pages') { + $columns['tx_yoastseo_hide_snippet_preview'] = [ + 'label' => self::LL_PREFIX . 'hideSnippetPreview', + 'exclude' => true, + 'config' => [ + 'type' => 'check' + ] + ]; + } + ExtensionManagementUtility::addTCAcolumns( + $this->table, + $columns + ); + } + + protected function addPremiumFields(): void + { + ExtensionManagementUtility::addTCAcolumns( + $this->table, + [ + 'tx_yoastseo_focuskeyword_synonyms' => [ + 'label' => self::LL_PREFIX . 'synonyms', + 'exclude' => false, + 'displayCond' => 'FIELD:tx_yoastseo_hide_snippet_preview:REQ:false', + 'config' => [ + 'type' => 'none', + 'renderType' => 'synonyms', + ] + ], + 'tx_yoastseo_focuskeyword_premium' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'none', + 'renderType' => 'relatedKeyphrases' + ] + ], + 'tx_yoastseo_insights' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'none', + 'renderType' => 'insights' + ] + ], + 'tx_yoastseo_robots_noimageindex' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'none', + 'renderType' => 'advancedRobots' + ] + ] + ] + ); + } + + protected function addPalettes(): void + { + ExtensionManagementUtility::addFieldsToPalette( + $this->table, + 'seo', + '--linebreak--, tx_yoastseo_snippetpreview, --linebreak--', + 'before: seo_title' + ); + + ExtensionManagementUtility::addFieldsToPalette( + $this->table, + 'seo', + '--linebreak--, ' . $this->descriptionField . ', --linebreak--, tx_yoastseo_cornerstone', + 'after: seo_title' + ); + + ExtensionManagementUtility::addFieldsToPalette( + $this->table, + 'yoast-readability', + '--linebreak--, tx_yoastseo_readability_analysis' + ); + + ExtensionManagementUtility::addFieldsToPalette( + $this->table, + 'yoast-focuskeyword', + '--linebreak--, tx_yoastseo_focuskeyword, + --linebreak--, tx_yoastseo_focuskeyword_synonyms, + --linebreak--, tx_yoastseo_focuskeyword_analysis' + ); + + ExtensionManagementUtility::addFieldsToPalette( + $this->table, + 'yoast-relatedkeywords', + '--linebreak--, tx_yoastseo_focuskeyword_premium ' + ); + + ExtensionManagementUtility::addFieldsToPalette( + $this->table, + 'yoast-insights', + '--linebreak--, tx_yoastseo_insights' + ); + + ExtensionManagementUtility::addFieldsToPalette( + $this->table, + 'yoast-advanced-robots', + '--linebreak--, tx_yoastseo_robots_noimageindex' + ); + + ExtensionManagementUtility::addFieldsToPalette( + $this->table, + 'yoast-advanced', + '--linebreak--, tx_yoastseo_hide_snippet_preview' + ); + } + + protected function addToTypes(): void + { + ExtensionManagementUtility::addToAllTCAtypes( + $this->table, + '--palette--;Label;yoast-snippetpreview,', + $this->types, + 'before:seo_title' + ); + + ExtensionManagementUtility::addToAllTCAtypes( + $this->table, + '--palette--;' . self::LL_PREFIX . 'pages.palettes.readability;yoast-readability, + --palette--;' . self::LL_PREFIX . 'pages.palettes.focusKeyphrase;yoast-focuskeyword, + --palette--;' . self::LL_PREFIX . 'pages.palettes.focusRelatedKeyphrases;yoast-relatedkeywords, + --palette--;' . self::LL_PREFIX . 'pages.palettes.insights;yoast-insights,', + $this->types, + 'after: tx_yoastseo_cornerstone' + ); + + ExtensionManagementUtility::addToAllTCAtypes( + $this->table, + '--palette--;' . self::LL_PREFIX . 'pages.palettes.robots;yoast-advanced-robots,', + $this->types, + 'after:no_follow' + ); + + ExtensionManagementUtility::addToAllTCAtypes( + $this->table, + '--palette--;' . self::LL_PREFIX . 'pages.palettes.advances;yoast-advanced,', + $this->types, + 'after: twitter_image' + ); + } +} diff --git a/Classes/Utility/RecordUtility.php b/Classes/Utility/RecordUtility.php new file mode 100644 index 00000000..6a043d75 --- /dev/null +++ b/Classes/Utility/RecordUtility.php @@ -0,0 +1,21 @@ +addRecord($tableName); + } + + public static function removeForRecord(string $tableName): void + { + RecordRegistry::getInstance()->removeRecord($tableName); + } +} diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml new file mode 100644 index 00000000..008893d6 --- /dev/null +++ b/Configuration/Services.yaml @@ -0,0 +1,44 @@ +services: + _defaults: + autowire: false + autoconfigure: true + public: false + + YoastSeoForTypo3\YoastSeo\: + resource: '../Classes/*' + + YoastSeoForTypo3\YoastSeo\EventListener\TcaBuiltListener: + arguments: + - '@YoastSeoForTypo3\YoastSeo\Record\Builder\TcaBuilder' + tags: + - name: event.listener + identifier: 'yoastTca' + event: TYPO3\CMS\Core\Configuration\Event\AfterTcaCompilationEvent + method: 'addRecordTca' + + YoastSeoForTypo3\YoastSeo\EventListener\TableDefinitionsListener: + arguments: + - '@YoastSeoForTypo3\YoastSeo\Record\Builder\SchemaBuilder' + tags: + - name: event.listener + identifier: 'yoastSchema' + event: TYPO3\CMS\Core\Database\Event\AlterTableDefinitionStatementsEvent + method: 'addDatabaseSchema' + + YoastSeoForTypo3\YoastSeo\EventListener\RecordCanonicalListener: + arguments: + - '@TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController' + - '@YoastSeoForTypo3\YoastSeo\Record\RecordService' + tags: + - name: event.listener + identifier: 'yoastRecordCanonical' + event: TYPO3\CMS\Seo\Event\ModifyUrlForCanonicalTagEvent + method: 'setCanonical' + + YoastSeoForTypo3\YoastSeo\PageTitle\RecordPageTitleProvider: + arguments: + - '@YoastSeoForTypo3\YoastSeo\Record\RecordService' + + YoastSeoForTypo3\YoastSeo\MetaTag\RecordMetaTagGenerator: + arguments: + - '@YoastSeoForTypo3\YoastSeo\Record\RecordService' diff --git a/Configuration/TCA/Overrides/pages.php b/Configuration/TCA/Overrides/pages.php index ee0ebfec..187c5437 100644 --- a/Configuration/TCA/Overrides/pages.php +++ b/Configuration/TCA/Overrides/pages.php @@ -1,230 +1,12 @@ [ - 'label' => $llPrefix . 'snippetPreview', - 'exclude' => true, - 'displayCond' => 'FIELD:tx_yoastseo_hide_snippet_preview:REQ:false', - 'config' => [ - 'type' => 'none', - 'renderType' => 'snippetPreview', - 'settings' => [ - 'titleField' => 'seo_title', - 'descriptionField' => 'description' - ] - ] - ], - 'tx_yoastseo_readability_analysis' => [ - 'label' => $llPrefix . 'analysis', - 'exclude' => true, - 'displayCond' => 'FIELD:tx_yoastseo_hide_snippet_preview:REQ:false', - 'config' => [ - 'type' => 'none', - 'renderType' => 'readabilityAnalysis' - ] - ], - 'tx_yoastseo_hide_snippet_preview' => [ - 'label' => $llPrefix . 'hideSnippetPreview', - 'exclude' => true, - 'config' => [ - 'type' => 'check' - ] - ], - 'tx_yoastseo_focuskeyword' => [ - 'label' => $llPrefix . 'seoFocusKeyword', - 'exclude' => true, - 'displayCond' => 'FIELD:tx_yoastseo_hide_snippet_preview:REQ:false', - 'config' => [ - 'type' => 'input', - ] - ], - 'tx_yoastseo_focuskeyword_analysis' => [ - 'label' => $llPrefix . 'analysis', - 'exclude' => true, - 'displayCond' => 'FIELD:tx_yoastseo_hide_snippet_preview:REQ:false', - 'config' => [ - 'type' => 'none', - 'renderType' => 'focusKeywordAnalysis', - 'settings' => [ - 'focusKeywordField' => 'tx_yoastseo_focuskeyword', - ] - ] - ], - 'tx_yoastseo_cornerstone' => [ - 'label' => '', - 'exclude' => true, - 'config' => [ - 'type' => 'input', - 'default' => 0, - 'renderType' => 'cornerstone' - ] - ], - 'tx_yoastseo_score_readability' => [ - 'label' => '', - 'exclude' => false, - 'config' => [ - 'type' => 'passthrough' - ] - ], - 'tx_yoastseo_score_seo' => [ - 'label' => '', - 'exclude' => false, - 'config' => [ - 'type' => 'passthrough' - ] - ], - ] -); - -if (!\YoastSeoForTypo3\YoastSeo\Utility\YoastUtility::isPremiumInstalled()) { - \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns( +\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\YoastSeoForTypo3\YoastSeo\Service\TcaService::class) + ->addYoastFields( 'pages', - [ - 'tx_yoastseo_focuskeyword_synonyms' => [ - 'label' => $llPrefix . 'synonyms', - 'exclude' => false, - 'displayCond' => 'FIELD:tx_yoastseo_hide_snippet_preview:REQ:false', - 'config' => [ - 'type' => 'none', - 'renderType' => 'synonyms', - ] - ], - 'tx_yoastseo_focuskeyword_premium' => [ - 'exclude' => true, - 'config' => [ - 'type' => 'none', - 'renderType' => 'relatedKeyphrases' - ] - ], - 'tx_yoastseo_insights' => [ - 'exclude' => true, - 'config' => [ - 'type' => 'none', - 'renderType' => 'insights' - ] - ], - 'tx_yoastseo_robots_noimageindex' => [ - 'exclude' => true, - 'config' => [ - 'type' => 'none', - 'renderType' => 'advancedRobots' - ] - ] - ] + \YoastSeoForTypo3\YoastSeo\Utility\YoastUtility::getAllowedDoktypes(null, true) ); -} -\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addFieldsToPalette( - 'pages', - 'seo', - ' - --linebreak--, tx_yoastseo_snippetpreview, --linebreak-- - ', - 'before: seo_title' -); - -\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addFieldsToPalette( - 'pages', - 'seo', - ' - --linebreak--, description, --linebreak--, tx_yoastseo_cornerstone - ', - 'after: seo_title' -); - -\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addFieldsToPalette( - 'pages', - 'yoast-readability', - ' - --linebreak--, tx_yoastseo_readability_analysis - ' -); - -\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addFieldsToPalette( - 'pages', - 'yoast-focuskeyword', - ' - --linebreak--, tx_yoastseo_focuskeyword, - --linebreak--, tx_yoastseo_focuskeyword_synonyms, - --linebreak--, tx_yoastseo_focuskeyword_analysis - ' -); -\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addFieldsToPalette( - 'pages', - 'yoast-relatedkeywords', - ' - --linebreak--, tx_yoastseo_focuskeyword_premium - ' -); -\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addFieldsToPalette( - 'pages', - 'yoast-insights', - ' - --linebreak--, tx_yoastseo_insights - ' -); -\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addFieldsToPalette( - 'pages', - 'yoast-advanced-robots', - ' - --linebreak--, tx_yoastseo_robots_noimageindex - ' -); -\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addFieldsToPalette( - 'pages', - 'yoast-advanced', - ' - --linebreak--, tx_yoastseo_hide_snippet_preview - ' -); +// Remove description from metatags tab $GLOBALS['TCA']['pages']['palettes']['metatags']['showitem'] = preg_replace('/description(.*,|.*$)/', '', $GLOBALS['TCA']['pages']['palettes']['metatags']['showitem']); - -$dokTypes = '1'; -try { - $dokTypes = \YoastSeoForTypo3\YoastSeo\Utility\YoastUtility::getAllowedDoktypes(null, true); -} catch (\Doctrine\DBAL\Exception\TableNotFoundException $e) { - $dokTypes = '1,6'; -} - -\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes( - 'pages', - ' - --palette--;Label;yoast-snippetpreview, - ', - $dokTypes, - 'before:seo_title' -); - -\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes( - 'pages', - ' - --palette--;' . $llPrefix . 'pages.palettes.readability;yoast-readability, - --palette--;' . $llPrefix . 'pages.palettes.focusKeyphrase;yoast-focuskeyword, - --palette--;' . $llPrefix . 'pages.palettes.focusRelatedKeyphrases;yoast-relatedkeywords, - --palette--;' . $llPrefix . 'pages.palettes.insights;yoast-insights, -', - $dokTypes, - 'after: tx_yoastseo_cornerstone' -); - -\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes( - 'pages', - ' - --palette--;' . $llPrefix . 'pages.palettes.robots;yoast-advanced-robots, - ', - '', - 'after:no_follow' -); - -\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes( - 'pages', - ' - --palette--;' . $llPrefix . 'pages.palettes.advances;yoast-advanced, -', - $dokTypes, - 'after: twitter_image' -); diff --git a/Configuration/TypoScript/Setup/Config.typoscript b/Configuration/TypoScript/Setup/Config.typoscript index 682ae349..6be0fc2f 100644 --- a/Configuration/TypoScript/Setup/Config.typoscript +++ b/Configuration/TypoScript/Setup/Config.typoscript @@ -1 +1,21 @@ -config.yoast_seo.enabled = 1 +config { + yoast_seo.enabled = 1 + structuredData.providers { + breadcrumb { + provider = YoastSeoForTypo3\YoastSeo\StructuredData\BreadcrumbStructuredDataProvider + after = site + excludedDoktypes = + } + + site { + provider = YoastSeoForTypo3\YoastSeo\StructuredData\SiteStructuredDataProvider + } + } + pageTitleProviders { + yoastRecord { + provider = YoastSeoForTypo3\YoastSeo\PageTitle\RecordPageTitleProvider + before = record,seo + } + } +} + diff --git a/composer.json b/composer.json index dcd46d8c..2eaa26c9 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,7 @@ "typo3/cms-seo": "^9.5|^10.4|^11.0", "ext-curl": "*", "ext-json": "*", - "php": "^7.2 || ^8.0" + "php": "^7.4 || ^8.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.0", @@ -61,15 +61,9 @@ "dev-master": "8.x-dev" }, "typo3/cms": { - "cms-package-dir": "{$vendor-dir}/typo3/cms", - "web-dir": "public", "extension-key": "yoast_seo" } }, - "config": { - "vendor-dir": "vendor", - "bin-dir": "vendor/bin" - }, "scripts": { "test:php:lint": [ "phplint" diff --git a/ext_emconf.php b/ext_emconf.php index 67f31e1d..1fa48f53 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -7,7 +7,6 @@ 'author_company' => 'MaxServ B.V., Yoast', 'author_email' => '', 'clearCacheOnLoad' => 0, - 'dependencies' => '', 'state' => 'stable', 'uploadfolder' => 0, 'version' => '8.2.0', diff --git a/ext_localconf.php b/ext_localconf.php index 93781dfd..2ebdd88f 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -9,6 +9,8 @@ = \YoastSeoForTypo3\YoastSeo\Frontend\AdditionalPreviewData::class . '->render'; $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['usePageCache'][] = \YoastSeoForTypo3\YoastSeo\Frontend\UsePageCache::class; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Frontend\Page\PageGenerator']['generateMetaTags']['yoastRecord'] + = \YoastSeoForTypo3\YoastSeo\MetaTag\RecordMetaTagGenerator::class . '->generate'; } \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTypoScript( @@ -184,6 +186,12 @@ 'dataProvider' => YoastSeoForTypo3\YoastSeo\DataProviders\PagesWithoutDescriptionOverviewDataProvider::class . '->process', 'countProvider' => YoastSeoForTypo3\YoastSeo\DataProviders\PagesWithoutDescriptionOverviewDataProvider::class . '->numberOfItems' ], + ], + 'recordMetaTags' => [ + 'description' => \YoastSeoForTypo3\YoastSeo\MetaTag\Generator\DescriptionGenerator::class, + 'opengraph' => \YoastSeoForTypo3\YoastSeo\MetaTag\Generator\OpenGraphGenerator::class, + 'twitter' => \YoastSeoForTypo3\YoastSeo\MetaTag\Generator\TwitterGenerator::class, + 'robots' => \YoastSeoForTypo3\YoastSeo\MetaTag\Generator\RobotsGenerator::class, ] ]; @@ -206,19 +214,6 @@ $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['canonicalFieldUpdate'] = \YoastSeoForTypo3\YoastSeo\Install\CanonicalFieldUpdate::class; -\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTypoScriptSetup(trim(' - config.structuredData.providers { - breadcrumb { - provider = YoastSeoForTypo3\YoastSeo\StructuredData\BreadcrumbStructuredDataProvider - after = site - excludedDoktypes = - } - site { - provider = YoastSeoForTypo3\YoastSeo\StructuredData\SiteStructuredDataProvider - } - } -')); - if (TYPO3_MODE === 'BE') { $publicResourcesPath = \TYPO3\CMS\Core\Utility\PathUtility::getAbsoluteWebPath(\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('yoast_seo')) . 'Resources/Public/'; @@ -227,3 +222,6 @@ $pageRenderer = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Page\PageRenderer::class); $pageRenderer->addInlineSetting('Yoast', 'backendCssUrl', $publicResourcesPath . 'CSS/yoast-seo-backend.min.css'); } + +$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['yoastseo_recordcache'] ??= []; +$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['yoastseo_recordcache']['groups'] ??= ['system']; From b0e52aa2ce31a4996f8b5bdf56fe6cb81bc4da88 Mon Sep 17 00:00:00 2001 From: Riny van Tiggelen Date: Wed, 3 Aug 2022 13:29:40 +0200 Subject: [PATCH 2/6] [BUGFIX] Fixed CGL issues within the new record functionality --- Classes/Record/Builder/SchemaBuilder.php | 12 +++++------ Classes/Record/Builder/TcaBuilder.php | 5 +++-- Classes/Record/Record.php | 26 ++++++++++++------------ Classes/Record/RecordRegistry.php | 2 +- phpstan.cms9.neon | 3 +++ 5 files changed, 26 insertions(+), 22 deletions(-) diff --git a/Classes/Record/Builder/SchemaBuilder.php b/Classes/Record/Builder/SchemaBuilder.php index c0d9c778..7e619669 100644 --- a/Classes/Record/Builder/SchemaBuilder.php +++ b/Classes/Record/Builder/SchemaBuilder.php @@ -33,10 +33,10 @@ protected function addDefaultSeoFields(): void $this->sqlData[] = "no_index tinyint(4) DEFAULT '0' NOT NULL,"; $this->sqlData[] = "no_follow tinyint(4) DEFAULT '0' NOT NULL,"; $this->sqlData[] = "og_title varchar(255) DEFAULT '' NOT NULL,"; - $this->sqlData[] = "og_description text,"; + $this->sqlData[] = 'og_description text,'; $this->sqlData[] = "og_image int(11) unsigned DEFAULT '0' NOT NULL,"; $this->sqlData[] = "twitter_title varchar(255) DEFAULT '' NOT NULL,"; - $this->sqlData[] = "twitter_description text,"; + $this->sqlData[] = 'twitter_description text,'; $this->sqlData[] = "twitter_image int(11) unsigned DEFAULT '0' NOT NULL,"; $this->sqlData[] = "twitter_card varchar(255) DEFAULT '' NOT NULL,"; $this->sqlData[] = "canonical_link varchar(2048) DEFAULT '' NOT NULL,"; @@ -46,18 +46,18 @@ protected function addDefaultSeoFields(): void protected function addYoastSeoFields(): void { - $this->sqlData[] = "tx_yoastseo_focuskeyword tinytext,"; - $this->sqlData[] = "tx_yoastseo_focuskeyword_synonyms tinytext,"; + $this->sqlData[] = 'tx_yoastseo_focuskeyword tinytext,'; + $this->sqlData[] = 'tx_yoastseo_focuskeyword_synonyms tinytext,'; $this->sqlData[] = "tx_yoastseo_cornerstone tinyint(3) DEFAULT '0' NOT NULL,"; $this->sqlData[] = "tx_yoastseo_score_readability varchar(50) DEFAULT '' NOT NULL,"; $this->sqlData[] = "tx_yoastseo_score_seo varchar(50) DEFAULT '' NOT NULL,"; $this->sqlData[] = "tx_yoastseo_focuskeyword_premium int(11) DEFAULT '0' NOT NULL,"; - $this->sqlData[] = "KEY tx_yoastseo_cornerstone (tx_yoastseo_cornerstone),"; + $this->sqlData[] = 'KEY tx_yoastseo_cornerstone (tx_yoastseo_cornerstone),'; } protected function addDescriptionField(): void { - $this->sqlData[] = $this->record->getDescriptionField() . " text,"; + $this->sqlData[] = $this->record->getDescriptionField() . ' text,'; } public function getResult(): array diff --git a/Classes/Record/Builder/TcaBuilder.php b/Classes/Record/Builder/TcaBuilder.php index b89803de..706a0d55 100644 --- a/Classes/Record/Builder/TcaBuilder.php +++ b/Classes/Record/Builder/TcaBuilder.php @@ -65,8 +65,9 @@ protected function addDefaultSeoFields(): void $tca ); - ExtensionManagementUtility::addToAllTCAtypes($this->record->getTableName(), ' - --div--;LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.tabs.seo, + ExtensionManagementUtility::addToAllTCAtypes( + $this->record->getTableName(), + '--div--;LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.tabs.seo, --palette--;;seo, --palette--;;robots, --palette--;;canonical, diff --git a/Classes/Record/Record.php b/Classes/Record/Record.php index 1eefd136..59a67155 100644 --- a/Classes/Record/Record.php +++ b/Classes/Record/Record.php @@ -52,7 +52,7 @@ public function hasDefaultSeoFields(): bool return $this->defaultSeoFields; } - public function setDefaultSeoFields(bool $defaultSeoFields): Record + public function setDefaultSeoFields(bool $defaultSeoFields): self { $this->defaultSeoFields = $defaultSeoFields; return $this; @@ -63,7 +63,7 @@ public function hasYoastSeoFields(): bool return $this->yoastSeoFields; } - public function setYoastSeoFields(bool $yoastSeoFields): Record + public function setYoastSeoFields(bool $yoastSeoFields): self { $this->yoastSeoFields = $yoastSeoFields; return $this; @@ -74,7 +74,7 @@ public function getTypes(): string return $this->types; } - public function setTypes(string $types): Record + public function setTypes(string $types): self { $this->types = $types; return $this; @@ -85,7 +85,7 @@ public function getTitleField(): string return $this->titleField; } - public function setTitleField(string $titleField): Record + public function setTitleField(string $titleField): self { $this->titleField = $titleField; return $this; @@ -96,7 +96,7 @@ public function getDescriptionField(): string return $this->descriptionField; } - public function setDescriptionField(string $descriptionField): Record + public function setDescriptionField(string $descriptionField): self { $this->descriptionField = $descriptionField; return $this; @@ -107,7 +107,7 @@ public function shouldAddDescriptionField(): bool return $this->addDescriptionField; } - public function setAddDescriptionField(bool $addDescriptionField): Record + public function setAddDescriptionField(bool $addDescriptionField): self { $this->addDescriptionField = $addDescriptionField; return $this; @@ -118,7 +118,7 @@ public function getFieldsPosition(): string return $this->fieldsPosition; } - public function setFieldsPosition(string $fieldsPosition): Record + public function setFieldsPosition(string $fieldsPosition): self { $this->fieldsPosition = $fieldsPosition; return $this; @@ -129,7 +129,7 @@ public function getOverrideTca(): array return $this->overrideTca; } - public function setOverrideTca(array $overrideTca): Record + public function setOverrideTca(array $overrideTca): self { $this->overrideTca = $overrideTca; return $this; @@ -140,7 +140,7 @@ public function getGetParameters(): array return $this->getParameters; } - public function setGetParameters(array $getParameters): Record + public function setGetParameters(array $getParameters): self { $this->getParameters = $getParameters; return $this; @@ -151,7 +151,7 @@ public function getRecordUid(): ?int return $this->recordUid; } - public function setRecordUid(int $recordUid): Record + public function setRecordUid(int $recordUid): self { $this->recordUid = $recordUid; return $this; @@ -162,7 +162,7 @@ public function getRecordData(): array return $this->recordData; } - public function setRecordData(array $recordData): Record + public function setRecordData(array $recordData): self { $this->recordData = $recordData; return $this; @@ -173,7 +173,7 @@ public function shouldGeneratePageTitle(): bool return $this->generatePageTitle; } - public function setGeneratePageTitle(bool $generatePageTitle): Record + public function setGeneratePageTitle(bool $generatePageTitle): self { $this->generatePageTitle = $generatePageTitle; return $this; @@ -184,7 +184,7 @@ public function shouldGenerateMetaTags(): bool return $this->generateMetaTags; } - public function setGenerateMetaTags(bool $generateMetaTags): Record + public function setGenerateMetaTags(bool $generateMetaTags): self { $this->generateMetaTags = $generateMetaTags; return $this; diff --git a/Classes/Record/RecordRegistry.php b/Classes/Record/RecordRegistry.php index 77cc1aa9..65800058 100644 --- a/Classes/Record/RecordRegistry.php +++ b/Classes/Record/RecordRegistry.php @@ -17,7 +17,7 @@ class RecordRegistry implements SingletonInterface */ protected ?array $records = null; - public static function getInstance(): RecordRegistry + public static function getInstance(): self { return GeneralUtility::makeInstance(__CLASS__); } diff --git a/phpstan.cms9.neon b/phpstan.cms9.neon index 320e0bbe..390e8915 100644 --- a/phpstan.cms9.neon +++ b/phpstan.cms9.neon @@ -3,3 +3,6 @@ parameters: paths: - Classes - Configuration + ignoreErrors: + - '#Parameter \$event of method#' + - '#Class TYPO3\\CMS\\Core\\Domain\\Repository\\PageRepository not found.#' From 6bbb92abd2fcc5623fa032300bb7c320e812e2dd Mon Sep 17 00:00:00 2001 From: Riny van Tiggelen Date: Wed, 3 Aug 2022 15:31:50 +0200 Subject: [PATCH 3/6] [TASK] Add the possibility to disable/enable sitemap fields, fixed missing TCA fields for frontend functionality --- Classes/Record/Builder/SchemaBuilder.php | 12 +++++-- Classes/Record/Builder/TcaBuilder.php | 35 +++++++++++++++--- Classes/Record/Record.php | 45 +++++++++++++++--------- Classes/Record/RecordRegistry.php | 6 +++- 4 files changed, 75 insertions(+), 23 deletions(-) diff --git a/Classes/Record/Builder/SchemaBuilder.php b/Classes/Record/Builder/SchemaBuilder.php index 7e619669..475e345c 100644 --- a/Classes/Record/Builder/SchemaBuilder.php +++ b/Classes/Record/Builder/SchemaBuilder.php @@ -20,6 +20,10 @@ public function build(): void $this->addYoastSeoFields(); } + if ($this->record->hasSitemapFields()) { + $this->addSitemapFields(); + } + if ($this->record->shouldAddDescriptionField()) { $this->addDescriptionField(); } @@ -40,8 +44,6 @@ protected function addDefaultSeoFields(): void $this->sqlData[] = "twitter_image int(11) unsigned DEFAULT '0' NOT NULL,"; $this->sqlData[] = "twitter_card varchar(255) DEFAULT '' NOT NULL,"; $this->sqlData[] = "canonical_link varchar(2048) DEFAULT '' NOT NULL,"; - $this->sqlData[] = "sitemap_priority decimal(2,1) DEFAULT '0.5' NOT NULL,"; - $this->sqlData[] = "sitemap_changefreq varchar(10) DEFAULT '' NOT NULL,"; } protected function addYoastSeoFields(): void @@ -55,6 +57,12 @@ protected function addYoastSeoFields(): void $this->sqlData[] = 'KEY tx_yoastseo_cornerstone (tx_yoastseo_cornerstone),'; } + protected function addSitemapFields(): void + { + $this->sqlData[] = "sitemap_priority decimal(2,1) DEFAULT '0.5' NOT NULL,"; + $this->sqlData[] = "sitemap_changefreq varchar(10) DEFAULT '' NOT NULL,"; + } + protected function addDescriptionField(): void { $this->sqlData[] = $this->record->getDescriptionField() . ' text,'; diff --git a/Classes/Record/Builder/TcaBuilder.php b/Classes/Record/Builder/TcaBuilder.php index 706a0d55..51426c85 100644 --- a/Classes/Record/Builder/TcaBuilder.php +++ b/Classes/Record/Builder/TcaBuilder.php @@ -20,6 +20,10 @@ public function build(): void $this->addYoastFields(); } + if ($this->record->hasSitemapFields()) { + $this->addSitemapFields(); + } + if ($this->record->shouldAddDescriptionField()) { $this->addDescriptionField(); } @@ -27,9 +31,13 @@ public function build(): void $GLOBALS['TCA'][$this->record->getTableName()]['yoast_seo'] = [ 'defaultSeoFields' => $this->record->hasDefaultSeoFields(), 'yoastFields' => $this->record->hasYoastSeoFields(), + 'sitemapFields' => $this->record->hasSitemapFields(), + 'titleField' => $this->record->getTitleField(), 'descriptionField' => $this->record->getDescriptionField(), 'addDescriptionField' => $this->record->shouldAddDescriptionField(), 'getParameters' => $this->record->getGetParameters(), + 'generatePageTitle' => $this->record->shouldGeneratePageTitle(), + 'generateMetaTags' => $this->record->shouldGenerateMetaTags(), ]; } @@ -40,7 +48,6 @@ protected function addDefaultSeoFields(): void 'seo' => $GLOBALS['TCA']['pages']['palettes']['seo'], 'robots' => $GLOBALS['TCA']['pages']['palettes']['robots'], 'canonical' => $GLOBALS['TCA']['pages']['palettes']['canonical'], - 'sitemap' => $GLOBALS['TCA']['pages']['palettes']['sitemap'], 'opengraph' => $GLOBALS['TCA']['pages']['palettes']['opengraph'], 'twittercards' => $GLOBALS['TCA']['pages']['palettes']['twittercards'], ], @@ -48,8 +55,6 @@ protected function addDefaultSeoFields(): void 'seo_title' => $GLOBALS['TCA']['pages']['columns']['seo_title'], 'no_index' => $GLOBALS['TCA']['pages']['columns']['no_index'], 'no_follow' => $GLOBALS['TCA']['pages']['columns']['no_follow'], - 'sitemap_changefreq' => $GLOBALS['TCA']['pages']['columns']['sitemap_changefreq'], - 'sitemap_priority' => $GLOBALS['TCA']['pages']['columns']['sitemap_priority'], 'canonical_link' => $GLOBALS['TCA']['pages']['columns']['canonical_link'], 'og_title' => $GLOBALS['TCA']['pages']['columns']['og_title'], 'og_description' => $GLOBALS['TCA']['pages']['columns']['og_description'], @@ -71,7 +76,6 @@ protected function addDefaultSeoFields(): void --palette--;;seo, --palette--;;robots, --palette--;;canonical, - --palette--;;sitemap, --div--;LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.tabs.socialmedia, --palette--;;opengraph, --palette--;;twittercards', @@ -90,6 +94,29 @@ protected function addYoastFields(): void ); } + protected function addSitemapFields(): void + { + $tca = [ + 'palettes' => [ + 'sitemap' => $GLOBALS['TCA']['pages']['palettes']['sitemap'], + ], + 'columns' => [ + 'sitemap_changefreq' => $GLOBALS['TCA']['pages']['columns']['sitemap_changefreq'], + 'sitemap_priority' => $GLOBALS['TCA']['pages']['columns']['sitemap_priority'], + ] + ]; + $GLOBALS['TCA'][$this->record->getTableName()] = array_replace_recursive( + $GLOBALS['TCA'][$this->record->getTableName()], + $tca + ); + ExtensionManagementUtility::addToAllTCAtypes( + $this->record->getTableName(), + '--palette--;;sitemap', + $this->record->getTypes(), + 'after:--palette--;;canonical' + ); + } + protected function addDescriptionField(): void { ExtensionManagementUtility::addTCAcolumns($this->record->getTableName(), [ diff --git a/Classes/Record/Record.php b/Classes/Record/Record.php index 59a67155..7683e0fd 100644 --- a/Classes/Record/Record.php +++ b/Classes/Record/Record.php @@ -14,6 +14,8 @@ class Record protected bool $yoastSeoFields = true; + protected bool $sitemapFields = true; + protected string $types = ''; protected string $titleField = 'title'; @@ -69,6 +71,17 @@ public function setYoastSeoFields(bool $yoastSeoFields): self return $this; } + public function hasSitemapFields(): bool + { + return $this->yoastSeoFields; + } + + public function setSitemapFields(bool $sitemapFields): self + { + $this->sitemapFields = $sitemapFields; + return $this; + } + public function getTypes(): string { return $this->types; @@ -146,47 +159,47 @@ public function setGetParameters(array $getParameters): self return $this; } - public function getRecordUid(): ?int + public function shouldGeneratePageTitle(): bool { - return $this->recordUid; + return $this->generatePageTitle; } - public function setRecordUid(int $recordUid): self + public function setGeneratePageTitle(bool $generatePageTitle): self { - $this->recordUid = $recordUid; + $this->generatePageTitle = $generatePageTitle; return $this; } - public function getRecordData(): array + public function shouldGenerateMetaTags(): bool { - return $this->recordData; + return $this->generateMetaTags; } - public function setRecordData(array $recordData): self + public function setGenerateMetaTags(bool $generateMetaTags): self { - $this->recordData = $recordData; + $this->generateMetaTags = $generateMetaTags; return $this; } - public function shouldGeneratePageTitle(): bool + public function getRecordUid(): ?int { - return $this->generatePageTitle; + return $this->recordUid; } - public function setGeneratePageTitle(bool $generatePageTitle): self + public function setRecordUid(int $recordUid): self { - $this->generatePageTitle = $generatePageTitle; + $this->recordUid = $recordUid; return $this; } - public function shouldGenerateMetaTags(): bool + public function getRecordData(): array { - return $this->generateMetaTags; + return $this->recordData; } - public function setGenerateMetaTags(bool $generateMetaTags): self + public function setRecordData(array $recordData): self { - $this->generateMetaTags = $generateMetaTags; + $this->recordData = $recordData; return $this; } } diff --git a/Classes/Record/RecordRegistry.php b/Classes/Record/RecordRegistry.php index 65800058..a174a475 100644 --- a/Classes/Record/RecordRegistry.php +++ b/Classes/Record/RecordRegistry.php @@ -77,9 +77,13 @@ protected function buildRecordsFromTca(): void $this->addRecord($table) ->setDefaultSeoFields((bool)($yoastSeoConfiguration['defaultSeoFields'] ?? true)) ->setYoastSeoFields((bool)($yoastSeoConfiguration['yoastFields'] ?? true)) + ->setSitemapFields((bool)($yoastSeoConfiguration['sitemapFields'] ?? true)) + ->setTitleField((string)($yoastSeoConfiguration['titleField'] ?? 'title')) ->setDescriptionField((string)($yoastSeoConfiguration['descriptionField'] ?? 'description')) ->setAddDescriptionField((bool)($yoastSeoConfiguration['addDescriptionField'] ?? false)) - ->setGetParameters((array)($yoastSeoConfiguration['getParameters'] ?? [])); + ->setGetParameters((array)($yoastSeoConfiguration['getParameters'] ?? [])) + ->setGeneratePageTitle((bool)($yoastSeoConfiguration['generatePageTitle'] ?? true)) + ->setGenerateMetaTags((bool)($yoastSeoConfiguration['generateMetaTags'] ?? true)); } } } From 3cba3a079018b69ef71238d183c1effee7c0e7e2 Mon Sep 17 00:00:00 2001 From: Riny van Tiggelen Date: Wed, 3 Aug 2022 15:34:37 +0200 Subject: [PATCH 4/6] [TASK] Updated documentation for the new record functionality --- .../OtherPlugins/Automatic/Index.rst | 256 ++++++++++++++++++ .../Configuration/OtherPlugins/Index.rst | 104 +------ .../OtherPlugins/Manual/Index.rst | 97 +++++++ 3 files changed, 368 insertions(+), 89 deletions(-) create mode 100644 Documentation/Configuration/OtherPlugins/Automatic/Index.rst create mode 100644 Documentation/Configuration/OtherPlugins/Manual/Index.rst diff --git a/Documentation/Configuration/OtherPlugins/Automatic/Index.rst b/Documentation/Configuration/OtherPlugins/Automatic/Index.rst new file mode 100644 index 00000000..bfc55c82 --- /dev/null +++ b/Documentation/Configuration/OtherPlugins/Automatic/Index.rst @@ -0,0 +1,256 @@ +.. include:: /Includes.rst.txt + + +.. _otherplugins_automatic: + +Automatic functionality for records +=================================== + +.. warning:: + + This functionality is only available for TYPO3 10 and higher. This is due + to the usage of some hooks and Event Listeners which are not available in + version 9. + +What does the automatic functionality do? +----------------------------------------- +By using the automatic functionality the following will be added to your +records: + +* Backend: + * Fields of the core SEO extension: + * SEO title + * Robots: Index this page, Follow this page + * Opengraph fields: Title, Description, Image + * Twitter fields: Title, Description, Image, Card + * Canonical link + * Sitemap: Change frequency, Priority + * Functionality of Yoast SEO: + * Snippet preview + * Title and Description progress bar + * Readability Analysis + * SEO Analysis + * Focus keyword +* Frontend: + * Metatag tags: + * Description + * Opengraph + * Twitter + * Robots + * Canonical generation + * Page title based on SEO Title and Title field + +Configuration for a record +-------------------------- + +To get all the functionality mentioned above you only have to set the table +name and the GET parameters on which it responds to. This should be placed +in a file within ``Configuration/TCA/Overrides`` of your extension +(f.e. sitepackage): + +.. code-block:: php + + setGetParameters([ + ['tx_extension_pi1', 'record'] + ]); + +This will automatically set the TCA, Database fields and activate the frontend +functionality. + +Configuration options +--------------------- +The method ``configureRecord`` returns an object which has the following +methods: + +setGetParameters +~~~~~~~~~~~~~~~~ +:aspect:`Datatype` + array + +:aspect:`Default` + none + +:aspect:`Description` + The GET parameters on where the frontend functionality should be activated. + This should be a multidimensional array, which gives you the possibility to + react to multiple situations. + +:aspect:`Example` + +.. code-block:: php + + ->setGetParameters([ + ['tx_extension_pi1', 'record'], + ['tx_extension_pi1', 'record_preview'] + ]) + + +setDefaultSeoFields +~~~~~~~~~~~~~~~~~~~ +:aspect:`Datatype` + boolean + +:aspect:`Default` + true + +:aspect:`Description` + This will define if the default fields from EXT:seo should be added. + +setYoastSeoFields +~~~~~~~~~~~~~~~~~ +:aspect:`Datatype` + boolean + +:aspect:`Default` + true + +:aspect:`Description` + This will define if the fields from Yoast SEO should be added. + +setSitemapFields +~~~~~~~~~~~~~~~~~ +:aspect:`Datatype` + boolean + +:aspect:`Default` + true + +:aspect:`Description` + This will define if the fields "Change frequency" and "Priority" for Sitemap + should be added. + +setTypes +~~~~~~~~ +:aspect:`Datatype` + string + +:aspect:`Default` + empty (all types) + +:aspect:`Description` + Defines on which types of the record the fields should be added. + +setTitleField +~~~~~~~~~~~~~ +:aspect:`Datatype` + string + +:aspect:`Default` + title + +:aspect:`Description` + Sets the title field to another column. + +setDescriptionField +~~~~~~~~~~~~~~~~~~~ +:aspect:`Datatype` + string + +:aspect:`Default` + description + +:aspect:`Description` + Sets the description field to another column. + +setAddDescriptionField +~~~~~~~~~~~~~~~~~~~~~~ +:aspect:`Datatype` + boolean + +:aspect:`Default` + false + +:aspect:`Description` + Adds the description column to both TCA and database (in case there's none + already). + +setFieldsPosition +~~~~~~~~~~~~~~~~~ +:aspect:`Datatype` + string + +:aspect:`Default` + after:title + +:aspect:`Description` + Sets the position of the TCA fields. + +setOverrideTca +~~~~~~~~~~~~~~ +:aspect:`Datatype` + array + +:aspect:`Default` + none + +:aspect:`Description` + Override TCA of the complete table. This can be useful if you want to change + something of the TCA of the EXT:seo or Yoast SEO fields. This is needed + because the automatic TCA generation is executed after all generated TCA, so + trying to add this to one of the ``Overrides`` files will not take effect. + +:aspect:`Example` + +.. code-block:: php + + ->setOverrideTca([ + 'columns' => [ + 'seo_title' => [ + 'config' => [ + 'max' => 100 + ] + ] + ] + ]) + +setGeneratePageTitle +~~~~~~~~~~~~~~~~~~~~ +:aspect:`Datatype` + boolean + +:aspect:`Default` + true + +:aspect:`Description` + This will enable/disable the functionality of the Page Title Provider + in the frontend. + + +setGenerateMetaTags +~~~~~~~~~~~~~~~~~~~ +:aspect:`Datatype` + boolean + +:aspect:`Default` + true + +:aspect:`Description` + This will enable/disable the rendering of the metatags in the frontend (in + case you want to this yourself). This will not deactivate the canonical tag. + + +Example with EXT:news +--------------------- +EXT:news has his own sitemap fields and has multiple GET parameters to respond +to. The basic configuration can be: + +.. code-block:: php + + \YoastSeoForTypo3\YoastSeo\Utility\RecordUtility::configureForRecord('tx_news_domain_model_news') + ->setGetParameters([ + ['tx_news_pi1', 'news'], + ['tx_news_pi1', 'news_preview'] + ]) + ->setSitemapFields(false) + ->setFieldsPosition('after:bodytext'); + +.. note:: EXT:news provides an own Page Title Provider, if you want to use the + Page Title Provider of Yoast SEO you can unset the one from EXT:news + with typoscript: ``config.pageTitleProviders.news >`` + +Adding PageTsconfig +------------------- +Make sure to add the needed PageTsconfig mentioned in :ref:`otherplugins` diff --git a/Documentation/Configuration/OtherPlugins/Index.rst b/Documentation/Configuration/OtherPlugins/Index.rst index 93778479..7a025447 100644 --- a/Documentation/Configuration/OtherPlugins/Index.rst +++ b/Documentation/Configuration/OtherPlugins/Index.rst @@ -3,101 +3,27 @@ .. _otherplugins: -Other plugins -============= - -How to integrate in other plugins ---------------------------------- +Other Plugins +=================== By default the SEO analysis is only done on pages. -If you want more type of records to use the SEO functions from Yoast SEO you -have to add some fields to the TCA. - -.. code-block:: php - - $llPrefix = 'LLL:EXT:yoast_seo/Resources/Private/Language/BackendModule.xlf:'; - \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns( - 'tx_news_domain_model_news', - [ - 'tx_yoastseo_snippetpreview' => [ - 'label' => $llPrefix . 'snippetPreview', - 'exclude' => true, - 'displayCond' => 'REC:NEW:false', - 'config' => [ - 'type' => 'none', - 'renderType' => 'snippetPreview', - 'settings' => [ - 'titleField' => 'alternative_title', - 'descriptionField' => 'description' - ] - ] - ], - 'tx_yoastseo_readability_analysis' => [ - 'label' => $llPrefix . 'analysis', - 'exclude' => true, - 'config' => [ - 'type' => 'none', - 'renderType' => 'readabilityAnalysis' - ] - ], - 'tx_yoastseo_focuskeyword' => [ - 'label' => $llPrefix . 'seoFocusKeyword', - 'exclude' => true, - 'config' => [ - 'type' => 'input', - ] - ], - 'tx_yoastseo_focuskeyword_analysis' => [ - 'label' => $llPrefix . 'analysis', - 'exclude' => true, - 'config' => [ - 'type' => 'none', - 'renderType' => 'focusKeywordAnalysis', - 'settings' => [ - 'focusKeywordField' => 'tx_yoastseo_focuskeyword', - ] - ] - ], - - ] - ); - -In the example above, you see we define four fields for in this case the -EXT:news records. - -The first field with renderType snippetPreview, is the field for the snippet -preview. In the settings section, you have to define which fields contains the -SEO title and the description. The snippet preview will use these fields to show -you your search result will look like. Make sure the fields that are set, do -exist. This field doesn't need a column in the database. - -The second field doesn't need much configuration. Just add a field with -renderType readabilityAnalysis to have the readabilityAnalysis in your record. -This field also doesn't need a column in the database. - -To have your content checked for SEO, you have to set a focus keyword, so we -need to add that field as well. This can be a simple text field. This is the -only field that needs a column in the database. Make sure you add this field to -the database table for your records. For the news table this would be the -following within the :file:`ext_tables.sql` of your extension: - -.. code-block:: sql +If you want more type of records to use the SEO functions, these are two ways +of accomplishing this: - CREATE TABLE tx_news_domain_model_news ( - tx_yoastseo_focuskeyword varchar(100) DEFAULT '' NOT NULL, - ); +.. toctree:: + :maxdepth: 5 + :titlesonly: -To show the focus keyword analysis, we need to add a field of renderType -focusKeywordAnalysis. In the settings section you have to define the field you -have created with the focus keyword. This field also doesn't need a column in -the database. + Automatic/Index + Manual/Index -How to use the snippet preview in other plugins ------------------------------------------------ +Required: adding PageTsconfig preview configuration +--------------------------------------------------- One important thing to know is that the snippet preview of records other than -pages, only works when you have set a proper preview link. The only thing you -need to do is set some PageTsconfig. More information about the configuration of -the preview links can be found in the :ref:`documentation `. +pages, only works when you have set a proper preview link. This is needed for +both of the solutions above. The only thing you need to do is set some +PageTsconfig. More information about the configuration of the preview links +can be found in the :ref:`documentation `. An example configuration of the preview links for EXT:news records is: diff --git a/Documentation/Configuration/OtherPlugins/Manual/Index.rst b/Documentation/Configuration/OtherPlugins/Manual/Index.rst new file mode 100644 index 00000000..4548bc77 --- /dev/null +++ b/Documentation/Configuration/OtherPlugins/Manual/Index.rst @@ -0,0 +1,97 @@ +.. include:: /Includes.rst.txt + + +.. _otherplugins_manual: + +Manual configuration for records +================================ + +How to integrate in other plugins +--------------------------------- + +By default the SEO analysis is only done on pages. +If you want more type of records to use the SEO functions from Yoast SEO you +have to add some fields to the TCA. + +.. code-block:: php + + $llPrefix = 'LLL:EXT:yoast_seo/Resources/Private/Language/BackendModule.xlf:'; + \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns( + 'tx_news_domain_model_news', + [ + 'tx_yoastseo_snippetpreview' => [ + 'label' => $llPrefix . 'snippetPreview', + 'exclude' => true, + 'displayCond' => 'REC:NEW:false', + 'config' => [ + 'type' => 'none', + 'renderType' => 'snippetPreview', + 'settings' => [ + 'titleField' => 'alternative_title', + 'descriptionField' => 'description' + ] + ] + ], + 'tx_yoastseo_readability_analysis' => [ + 'label' => $llPrefix . 'analysis', + 'exclude' => true, + 'config' => [ + 'type' => 'none', + 'renderType' => 'readabilityAnalysis' + ] + ], + 'tx_yoastseo_focuskeyword' => [ + 'label' => $llPrefix . 'seoFocusKeyword', + 'exclude' => true, + 'config' => [ + 'type' => 'input', + ] + ], + 'tx_yoastseo_focuskeyword_analysis' => [ + 'label' => $llPrefix . 'analysis', + 'exclude' => true, + 'config' => [ + 'type' => 'none', + 'renderType' => 'focusKeywordAnalysis', + 'settings' => [ + 'focusKeywordField' => 'tx_yoastseo_focuskeyword', + ] + ] + ], + + ] + ); + +In the example above, you see we define four fields for in this case the +EXT:news records. + +The first field with renderType snippetPreview, is the field for the snippet +preview. In the settings section, you have to define which fields contains the +SEO title and the description. The snippet preview will use these fields to show +you your search result will look like. Make sure the fields that are set, do +exist. This field doesn't need a column in the database. + +The second field doesn't need much configuration. Just add a field with +renderType readabilityAnalysis to have the readabilityAnalysis in your record. +This field also doesn't need a column in the database. + +To have your content checked for SEO, you have to set a focus keyword, so we +need to add that field as well. This can be a simple text field. This is the +only field that needs a column in the database. Make sure you add this field to +the database table for your records. For the news table this would be the +following within the :file:`ext_tables.sql` of your extension: + +.. code-block:: sql + + CREATE TABLE tx_news_domain_model_news ( + tx_yoastseo_focuskeyword varchar(100) DEFAULT '' NOT NULL, + ); + +To show the focus keyword analysis, we need to add a field of renderType +focusKeywordAnalysis. In the settings section you have to define the field you +have created with the focus keyword. This field also doesn't need a column in +the database. + +Adding PageTsconfig +------------------- +Make sure to add the needed PageTsconfig mentioned in :ref:`otherplugins` From c631f87ab4c2d281646ec9d9623d8f62c6e6a46d Mon Sep 17 00:00:00 2001 From: Riny van Tiggelen Date: Thu, 4 Aug 2022 08:22:27 +0200 Subject: [PATCH 5/6] [BUGFIX] Added missing overrideTca functionality within TcaBuilder --- Classes/Record/Builder/TcaBuilder.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Classes/Record/Builder/TcaBuilder.php b/Classes/Record/Builder/TcaBuilder.php index 51426c85..c5825a04 100644 --- a/Classes/Record/Builder/TcaBuilder.php +++ b/Classes/Record/Builder/TcaBuilder.php @@ -39,6 +39,13 @@ public function build(): void 'generatePageTitle' => $this->record->shouldGeneratePageTitle(), 'generateMetaTags' => $this->record->shouldGenerateMetaTags(), ]; + + if (!empty($this->record->getOverrideTca())) { + $GLOBALS['TCA'][$this->record->getTableName()] = array_replace_recursive( + $GLOBALS['TCA'][$this->record->getTableName()], + $this->record->getOverrideTca() + ); + } } protected function addDefaultSeoFields(): void From 4448a6721e5e3416f1fa87b6b9b8862908f41cd2 Mon Sep 17 00:00:00 2001 From: Riny van Tiggelen Date: Tue, 16 Aug 2022 08:54:46 +0200 Subject: [PATCH 6/6] [RELEASE] Version 9.0.0-alpha-1 --- CHANGELOG.md | 2 +- ext_emconf.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36389d8d..c5f471a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ We will follow [Semantic Versioning](http://semver.org/). ## Yoast SEO Premium for TYPO3 Besides the free version of our plugin, we also have a premium version. The free version enables you to do all necessary optimizations. With the premium version, we make it even easier to do! More information can be found on https://www.maxserv.com/yoast. -## UNRELEASED +## 9.0.0-alpha-1 August 16, 2022 ### Breaking - Dropped support for PHP <7.4 diff --git a/ext_emconf.php b/ext_emconf.php index ded33843..f886637b 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -9,7 +9,7 @@ 'clearCacheOnLoad' => 0, 'state' => 'stable', 'uploadfolder' => 0, - 'version' => '8.3.1', + 'version' => '9.0.0', 'constraints' => [ 'depends' => [ 'typo3' => '9.5.0-11.5.99',