[TASK] Move new SEO fields to separate system extension 48/57148/17
authorRichard Haeser <richard@maxserv.com>
Fri, 8 Jun 2018 09:05:36 +0000 (11:05 +0200)
committerSusanne Moog <susanne.moog@typo3.org>
Sat, 9 Jun 2018 14:36:06 +0000 (16:36 +0200)
Move the new SEO fields to EXT:seo and prepare the extension for some
basic SEO features.

Resolves: #85194
Releases: master
Change-Id: I6ec087928080e217ce1824b2e9ad5cf0ca0606c8
Reviewed-on: https://review.typo3.org/57148
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
28 files changed:
composer.json
composer.lock
typo3/sysext/core/Configuration/TCA/pages.php
typo3/sysext/core/Documentation/Changelog/master/Feature-84798-AddSEOFieldsToPagesTCA.rst
typo3/sysext/core/Documentation/Changelog/master/Feature-85147-RenderSEOMetaTagsInFrontend.rst
typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/Modify/DataSet/localizeNCopyPageWSynchronization.csv
typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/Modify/DataSet/localizePageAddMonoglotHotelChildNCopyPageWSynchronization.csv
typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/Modify/DataSet/localizePageNAddHotelChildWExclude.csv
typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/Modify/DataSet/localizePageNAddHotelChildWSynchronization.csv
typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/Modify/DataSet/localizePageNAddMonoglotHotelChildWSynchronization.csv
typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/Modify/DataSet/localizePageWSynchronization.csv
typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/Modify/DataSet/localizePageWithSynchronizationAndCustomLocalizedHotel.csv
typo3/sysext/core/Tests/Functional/DataHandling/Regular/Modify/DataSet/localizeNCopyPageWSynchronization.csv
typo3/sysext/core/ext_tables.sql
typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
typo3/sysext/frontend/Classes/Page/PageGenerator.php
typo3/sysext/frontend/Resources/Private/Language/locallang_tca.xlf
typo3/sysext/frontend/Tests/Unit/Page/PageGeneratorTest.php
typo3/sysext/seo/.gitattributes [new file with mode: 0644]
typo3/sysext/seo/Classes/Generator/MetaTagGenerator.php [new file with mode: 0644]
typo3/sysext/seo/Configuration/TCA/Overrides/pages.php [new file with mode: 0644]
typo3/sysext/seo/LICENSE.txt [new file with mode: 0644]
typo3/sysext/seo/Resources/Private/Language/locallang_tca.xlf [new file with mode: 0644]
typo3/sysext/seo/Resources/Public/Icons/Extension.png [new file with mode: 0644]
typo3/sysext/seo/composer.json [new file with mode: 0644]
typo3/sysext/seo/ext_emconf.php [new file with mode: 0644]
typo3/sysext/seo/ext_localconf.php [new file with mode: 0644]
typo3/sysext/seo/ext_tables.sql [new file with mode: 0644]

index eef2d1d..7db2e9b 100644 (file)
                "typo3/cms-rte-ckeditor": "self.version",
                "typo3/cms-saltedpasswords": "self.version",
                "typo3/cms-scheduler": "self.version",
+               "typo3/cms-seo": "self.version",
                "typo3/cms-setup": "self.version",
                "typo3/cms-sys-action": "self.version",
                "typo3/cms-sys-note": "self.version",
                        "TYPO3\\CMS\\Rsaauth\\": "typo3/sysext/rsaauth/Classes/",
                        "TYPO3\\CMS\\RteCKEditor\\": "typo3/sysext/rte_ckeditor/Classes/",
                        "TYPO3\\CMS\\Saltedpasswords\\": "typo3/sysext/saltedpasswords/Classes/",
+                       "TYPO3\\CMS\\Seo\\": "typo3/sysext/seo/Classes/",
                        "TYPO3\\CMS\\Scheduler\\": "typo3/sysext/scheduler/Classes/",
                        "TYPO3\\CMS\\Setup\\": "typo3/sysext/setup/Classes/",
                        "TYPO3\\CMS\\SysAction\\": "typo3/sysext/sys_action/Classes/",
index 2078433..66bdbfd 100644 (file)
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "fae1860a766922f01655060e86edf0b6",
+    "content-hash": "80a09c87266d7b40d69d7963c0d78d2f",
     "packages": [
         {
             "name": "cogpowered/finediff",
index d1c3f4c..254f70d 100644 (file)
@@ -880,147 +880,6 @@ return [
                 'softref' => 'ext_fileref'
             ]
         ],
-        'seo_title' => [
-            'exclude' => true,
-            'l10n_mode' => 'prefixLangTitle',
-            'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.seo_title',
-            'config' => [
-                'type' => 'input',
-                'size' => 40,
-                'max' => 255,
-                'eval' => 'trim'
-            ]
-        ],
-        'no_index' => [
-            'exclude' => true,
-            'l10n_mode' => 'exclude',
-            'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.no_index',
-            'config' => [
-                'type' => 'check',
-                'renderType' => 'checkboxToggle',
-                'items' => [
-                    [
-                        '0' => '',
-                        '1' => '',
-                        'invertStateDisplay' => true
-                    ]
-                ]
-            ]
-        ],
-        'no_follow' => [
-            'exclude' => true,
-            'l10n_mode' => 'exclude',
-            'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.no_follow',
-            'config' => [
-                'type' => 'check',
-                'renderType' => 'checkboxToggle',
-                'items' => [
-                    [
-                        '0' => '',
-                        '1' => '',
-                        'invertStateDisplay' => true
-                    ]
-                ]
-            ]
-        ],
-        'og_title' => [
-            'exclude' => true,
-            'l10n_mode' => 'prefixLangTitle',
-            'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.og_title',
-            'config' => [
-                'type' => 'input',
-                'size' => 40,
-                'max' => 255,
-                'eval' => 'trim'
-            ]
-        ],
-        'og_description' => [
-            'exclude' => true,
-            'l10n_mode' => 'prefixLangTitle',
-            'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.og_description',
-            'config' => [
-                'type' => 'text',
-                'cols' => 40,
-                'rows' => 3
-            ]
-        ],
-        'og_image' => [
-            'exclude' => true,
-            'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.og_image',
-            'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig(
-                'og_image',
-                [
-                    // Use the imageoverlayPalette instead of the basicoverlayPalette
-                    'overrideChildTca' => [
-                        'types' => [
-                            '0' => [
-                                'showitem' => '
-                                    --palette--;;imageoverlayPalette,
-                                    --palette--;;filePalette'
-                            ],
-                            \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [
-                                'showitem' => '
-                                    --palette--;;imageoverlayPalette,
-                                    --palette--;;filePalette'
-                            ]
-                        ],
-                    ],
-                    'behaviour' => [
-                        'allowLanguageSynchronization' => true
-                    ]
-                ],
-                $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']
-            )
-        ],
-        'twitter_title' => [
-            'exclude' => true,
-            'l10n_mode' => 'prefixLangTitle',
-            'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.twitter_title',
-            'config' => [
-                'type' => 'input',
-                'size' => 40,
-                'max' => 255,
-                'eval' => 'trim'
-            ]
-        ],
-        'twitter_description' => [
-            'exclude' => true,
-            'l10n_mode' => 'prefixLangTitle',
-            'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.twitter_description',
-            'config' => [
-                'type' => 'text',
-                'cols' => 40,
-                'rows' => 3
-            ]
-        ],
-        'twitter_image' => [
-            'exclude' => true,
-            'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.twitter_image',
-            'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig(
-                'twitter_image',
-                [
-                    // Use the imageoverlayPalette instead of the basicoverlayPalette
-                    'overrideChildTca' => [
-                        'types' => [
-                            '0' => [
-                                'showitem' => '
-                                    --palette--;;imageoverlayPalette,
-                                    --palette--;;filePalette'
-                            ],
-                            \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [
-                                'showitem' => '
-                                    --palette--;;imageoverlayPalette,
-                                    --palette--;;filePalette'
-                            ]
-                        ],
-                    ],
-                    'behaviour' => [
-                        'allowLanguageSynchronization' => true
-                    ]
-                ],
-                $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']
-            )
-        ],
     ],
     'types' => [
         // normal
@@ -1029,11 +888,6 @@ return [
                 --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general,
                     --palette--;;standard,
                     --palette--;;title,
-                --div--;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.tabs.seo,
-                    --palette--;;seo,
-                    --palette--;;robots,
-                    --palette--;;opengraph,
-                    --palette--;;twittercards,
                 --div--;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.tabs.metadata,
                     --palette--;;abstract,
                     --palette--;;metatags,
@@ -1266,7 +1120,7 @@ return [
         ],
         'metatags' => [
             'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.palettes.metatags',
-            'showitem' => 'keywords;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.keywords_formlabel,',
+            'showitem' => 'keywords;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.keywords_formlabel, --linebreak--, description;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.description_formlabel',
         ],
         'editorial' => [
             'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.palettes.editorial',
@@ -1316,21 +1170,5 @@ return [
             'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.palettes.config',
             'showitem' => 'tsconfig_includes;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.tsconfig_includes, --linebreak--, TSconfig;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.TSconfig_formlabel',
         ],
-        'seo' => [
-            'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.palettes.seo',
-            'showitem' => 'seo_title;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.seo_title, --linebreak--, description;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.description_formlabel',
-        ],
-        'robots' => [
-            'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.palettes.robots',
-            'showitem' => 'no_index;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.no_index_formlabel, no_follow;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.no_follow_formlabel',
-        ],
-        'opengraph' => [
-            'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.palettes.opengraph',
-            'showitem' => 'og_title, --linebreak--, og_description, --linebreak--, og_image',
-        ],
-        'twittercards' => [
-            'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.palettes.twittercards',
-            'showitem' => 'twitter_title, --linebreak--, twitter_description, --linebreak--, twitter_image',
-        ],
     ]
 ];
index 31f8b28..8e460df 100644 (file)
@@ -9,8 +9,9 @@ See :issue:`84798`
 Description
 ===========
 
-This feature adds SEO fields to Pages TCA. A new tab SEO now exists in the Page module which contains SEO related
-metadata. Other non-SEO metadata is still on the Metadata tab.
+A new system extension called SEO is introduced. This extension adds SEO fields to Pages TCA. When this extension is
+installed, a new tab SEO appears in the Page module which contains SEO related metadata. Other non-SEO metadata is
+still on the Metadata tab.
 
 
 Impact
@@ -29,4 +30,4 @@ New fields added to Pages table:
 - twitter_description
 - twitter_image
 
-.. index:: Backend, Database, TCA, ext:core
+.. index:: Backend, Database, TCA, ext:seo
index 979dd40..56d577e 100644 (file)
@@ -9,14 +9,15 @@ See :issue:`85147`
 Description
 ===========
 
-The SEO meta tags that can be set in the page properties, are now rendered in frontend by default.
+The SEO meta tags that can be set in the page properties, are now rendered in frontend by default if the system extension
+SEO is installed.
 
 
 Impact
 ======
 
-No addition configuration is needed to render these meta tags. If you want to override the meta tags set by
-the pageproperties, you can use the replace parameter in TypoScript or in the addProperty method of the specific
+No additional configuration is needed to render these meta tags. If you want to override the meta tags set by
+the page properties, you can use the replace parameter in TypoScript or in the addProperty method of the specific
 MetaTagManager.
 
-.. index:: Frontend, ext:core
\ No newline at end of file
+.. index:: Frontend, ext:seo
\ No newline at end of file
index c441bb8..9475ad8 100644 (file)
@@ -4,9 +4,9 @@
 ,88,1,256,0,0,0,0,0,0,0,0,0,"DataHandlerTest",0,,,
 ,89,88,256,0,0,0,0,0,0,0,0,0,"Relations",1,,,
 ,90,88,512,0,0,0,0,0,0,0,0,0,"Target",0,,,
-,91,88,256,0,1,89,0,0,0,0,0,0,"[Translate to Dansk:] Relations",1,"{""starttime"":""parent"",""endtime"":""parent"",""url"":""parent"",""lastUpdated"":""parent"",""newUntil"":""parent"",""no_search"":""parent"",""shortcut"":""parent"",""shortcut_mode"":""parent"",""author"":""parent"",""author_email"":""parent"",""media"":""parent"",""og_image"":""parent"",""twitter_image"":""parent"",""tx_irretutorial_hotels"":""parent""}",,
+,91,88,256,0,1,89,0,0,0,0,0,0,"[Translate to Dansk:] Relations",1,"{""starttime"":""parent"",""endtime"":""parent"",""url"":""parent"",""lastUpdated"":""parent"",""newUntil"":""parent"",""no_search"":""parent"",""shortcut"":""parent"",""shortcut_mode"":""parent"",""author"":""parent"",""author_email"":""parent"",""media"":""parent"",""tx_irretutorial_hotels"":""parent""}",,
 ,92,90,256,0,0,0,89,0,0,0,0,0,"Relations",1,,,
-,93,90,256,0,1,92,91,0,0,0,0,0,"[Translate to Dansk:] Relations",1,"{""starttime"":""parent"",""endtime"":""parent"",""url"":""parent"",""lastUpdated"":""parent"",""newUntil"":""parent"",""no_search"":""parent"",""shortcut"":""parent"",""shortcut_mode"":""parent"",""author"":""parent"",""author_email"":""parent"",""media"":""parent"",""og_image"":""parent"",""twitter_image"":""parent"",""tx_irretutorial_hotels"":""parent""}",,
+,93,90,256,0,1,92,91,0,0,0,0,0,"[Translate to Dansk:] Relations",1,"{""starttime"":""parent"",""endtime"":""parent"",""url"":""parent"",""lastUpdated"":""parent"",""newUntil"":""parent"",""no_search"":""parent"",""shortcut"":""parent"",""shortcut_mode"":""parent"",""author"":""parent"",""author_email"":""parent"",""media"":""parent"",""tx_irretutorial_hotels"":""parent""}",,
 "tt_content",,,,,,,,,,,,,,,,,
 ,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","header","tx_irretutorial_1nff_hotels",,,
 ,297,89,256,0,0,0,0,0,0,0,0,0,"Regular Element #1",2,,,
index 72d396b..5ed7792 100644 (file)
@@ -4,9 +4,9 @@
 ,88,1,256,0,0,0,0,0,0,0,0,0,"DataHandlerTest",0,,,
 ,89,88,256,0,0,0,0,0,0,0,0,0,"Relations",2,,,
 ,90,88,512,0,0,0,0,0,0,0,0,0,"Target",0,,,
-,91,88,256,0,1,89,0,0,0,0,0,0,"[Translate to Dansk:] Relations",2,"{""starttime"":""parent"",""endtime"":""parent"",""url"":""parent"",""lastUpdated"":""parent"",""newUntil"":""parent"",""no_search"":""parent"",""shortcut"":""parent"",""shortcut_mode"":""parent"",""author"":""parent"",""author_email"":""parent"",""media"":""parent"",""og_image"":""parent"",""twitter_image"":""parent"",""tx_irretutorial_hotels"":""parent""}",,
+,91,88,256,0,1,89,0,0,0,0,0,0,"[Translate to Dansk:] Relations",2,"{""starttime"":""parent"",""endtime"":""parent"",""url"":""parent"",""lastUpdated"":""parent"",""newUntil"":""parent"",""no_search"":""parent"",""shortcut"":""parent"",""shortcut_mode"":""parent"",""author"":""parent"",""author_email"":""parent"",""media"":""parent"",""tx_irretutorial_hotels"":""parent""}",,
 ,92,90,256,0,0,0,89,0,0,0,0,0,"Relations",2,,,
-,93,90,256,0,1,92,91,0,0,0,0,0,"[Translate to Dansk:] Relations",2,"{""starttime"":""parent"",""endtime"":""parent"",""url"":""parent"",""lastUpdated"":""parent"",""newUntil"":""parent"",""no_search"":""parent"",""shortcut"":""parent"",""shortcut_mode"":""parent"",""author"":""parent"",""author_email"":""parent"",""media"":""parent"",""og_image"":""parent"",""twitter_image"":""parent"",""tx_irretutorial_hotels"":""parent""}",,
+,93,90,256,0,1,92,91,0,0,0,0,0,"[Translate to Dansk:] Relations",2,"{""starttime"":""parent"",""endtime"":""parent"",""url"":""parent"",""lastUpdated"":""parent"",""newUntil"":""parent"",""no_search"":""parent"",""shortcut"":""parent"",""shortcut_mode"":""parent"",""author"":""parent"",""author_email"":""parent"",""media"":""parent"",""tx_irretutorial_hotels"":""parent""}",,
 "tt_content",,,,,,,,,,,,,,,,,
 ,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","header","tx_irretutorial_1nff_hotels",,,
 ,297,89,256,0,0,0,0,0,0,0,0,0,"Regular Element #1",2,,,
index fac3e32..2b059db 100644 (file)
@@ -4,7 +4,7 @@
 ,88,1,256,0,0,0,0,0,0,0,0,0,"DataHandlerTest",0,,,
 ,89,88,256,0,0,0,0,0,0,0,0,0,"Relations",2,,,
 ,90,88,512,0,0,0,0,0,0,0,0,0,"Target",0,,,
-,91,88,256,0,1,89,0,0,0,0,0,0,"[Translate to Dansk:] Relations",2,"{""starttime"":""parent"",""endtime"":""parent"",""url"":""parent"",""lastUpdated"":""parent"",""newUntil"":""parent"",""no_search"":""parent"",""shortcut"":""parent"",""shortcut_mode"":""parent"",""author"":""parent"",""author_email"":""parent"",""media"":""parent"",""og_image"":""parent"",""twitter_image"":""parent""}",,
+,91,88,256,0,1,89,0,0,0,0,0,0,"[Translate to Dansk:] Relations",2,"{""starttime"":""parent"",""endtime"":""parent"",""url"":""parent"",""lastUpdated"":""parent"",""newUntil"":""parent"",""no_search"":""parent"",""shortcut"":""parent"",""shortcut_mode"":""parent"",""author"":""parent"",""author_email"":""parent"",""media"":""parent""}",,
 "tt_content",,,,,,,,,,,,,,,,,
 ,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","header","tx_irretutorial_1nff_hotels",,,
 ,297,89,256,0,0,0,0,0,0,0,0,0,"Regular Element #1",2,,,
index 87d9822..9413a53 100644 (file)
@@ -4,7 +4,7 @@
 ,88,1,256,0,0,0,0,0,0,0,0,0,"DataHandlerTest",0,,,
 ,89,88,256,0,0,0,0,0,0,0,0,0,"Relations",2,,,
 ,90,88,512,0,0,0,0,0,0,0,0,0,"Target",0,,,
-,91,88,256,0,1,89,0,0,0,0,0,0,"[Translate to Dansk:] Relations",2,"{""starttime"":""parent"",""endtime"":""parent"",""url"":""parent"",""lastUpdated"":""parent"",""newUntil"":""parent"",""no_search"":""parent"",""shortcut"":""parent"",""shortcut_mode"":""parent"",""author"":""parent"",""author_email"":""parent"",""media"":""parent"",""og_image"":""parent"",""twitter_image"":""parent"",""tx_irretutorial_hotels"":""parent""}",,
+,91,88,256,0,1,89,0,0,0,0,0,0,"[Translate to Dansk:] Relations",2,"{""starttime"":""parent"",""endtime"":""parent"",""url"":""parent"",""lastUpdated"":""parent"",""newUntil"":""parent"",""no_search"":""parent"",""shortcut"":""parent"",""shortcut_mode"":""parent"",""author"":""parent"",""author_email"":""parent"",""media"":""parent"",""tx_irretutorial_hotels"":""parent""}",,
 "tt_content",,,,,,,,,,,,,,,,,
 ,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","header","tx_irretutorial_1nff_hotels",,,
 ,297,89,256,0,0,0,0,0,0,0,0,0,"Regular Element #1",2,,,
index 1bc26bb..1c8f7fc 100644 (file)
@@ -4,7 +4,7 @@
 ,88,1,256,0,0,0,0,0,0,0,0,0,"DataHandlerTest",0,,,
 ,89,88,256,0,0,0,0,0,0,0,0,0,"Relations",2,,,
 ,90,88,512,0,0,0,0,0,0,0,0,0,"Target",0,,,
-,91,88,256,0,1,89,0,0,0,0,0,0,"[Translate to Dansk:] Relations",2,"{""starttime"":""parent"",""endtime"":""parent"",""url"":""parent"",""lastUpdated"":""parent"",""newUntil"":""parent"",""no_search"":""parent"",""shortcut"":""parent"",""shortcut_mode"":""parent"",""author"":""parent"",""author_email"":""parent"",""media"":""parent"",""og_image"":""parent"",""twitter_image"":""parent"",""tx_irretutorial_hotels"":""parent""}",,
+,91,88,256,0,1,89,0,0,0,0,0,0,"[Translate to Dansk:] Relations",2,"{""starttime"":""parent"",""endtime"":""parent"",""url"":""parent"",""lastUpdated"":""parent"",""newUntil"":""parent"",""no_search"":""parent"",""shortcut"":""parent"",""shortcut_mode"":""parent"",""author"":""parent"",""author_email"":""parent"",""media"":""parent"",""tx_irretutorial_hotels"":""parent""}",,
 "tt_content",,,,,,,,,,,,,,,,,
 ,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","header","tx_irretutorial_1nff_hotels",,,
 ,297,89,256,0,0,0,0,0,0,0,0,0,"Regular Element #1",2,,,
index 6dab8bf..749f4f0 100644 (file)
@@ -4,7 +4,7 @@
 ,88,1,256,0,0,0,0,0,0,0,0,0,"DataHandlerTest",0,,,
 ,89,88,256,0,0,0,0,0,0,0,0,0,"Relations",1,,,
 ,90,88,512,0,0,0,0,0,0,0,0,0,"Target",0,,,
-,91,88,256,0,1,89,0,0,0,0,0,0,"[Translate to Dansk:] Relations",1,"{""starttime"":""parent"",""endtime"":""parent"",""url"":""parent"",""lastUpdated"":""parent"",""newUntil"":""parent"",""no_search"":""parent"",""shortcut"":""parent"",""shortcut_mode"":""parent"",""author"":""parent"",""author_email"":""parent"",""media"":""parent"",""og_image"":""parent"",""twitter_image"":""parent"",""tx_irretutorial_hotels"":""parent""}",,
+,91,88,256,0,1,89,0,0,0,0,0,0,"[Translate to Dansk:] Relations",1,"{""starttime"":""parent"",""endtime"":""parent"",""url"":""parent"",""lastUpdated"":""parent"",""newUntil"":""parent"",""no_search"":""parent"",""shortcut"":""parent"",""shortcut_mode"":""parent"",""author"":""parent"",""author_email"":""parent"",""media"":""parent"",""tx_irretutorial_hotels"":""parent""}",,
 "tt_content",,,,,,,,,,,,,,,,,
 ,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","header","tx_irretutorial_1nff_hotels",,,
 ,297,89,256,0,0,0,0,0,0,0,0,0,"Regular Element #1",2,,,
index 03ad1cb..40e5f40 100644 (file)
@@ -4,7 +4,7 @@
 ,88,1,256,0,0,0,0,0,0,0,0,0,"DataHandlerTest",0,,,
 ,89,88,256,0,0,0,0,0,0,0,0,0,"Relations",1,,,
 ,90,88,512,0,0,0,0,0,0,0,0,0,"Target",0,,,
-,91,88,256,0,1,89,0,0,0,0,0,0,"[Translate to Dansk:] Relations",2,"{""starttime"":""parent"",""endtime"":""parent"",""url"":""parent"",""lastUpdated"":""parent"",""newUntil"":""parent"",""no_search"":""parent"",""shortcut"":""parent"",""shortcut_mode"":""parent"",""author"":""parent"",""author_email"":""parent"",""media"":""parent"",""og_image"":""parent"",""twitter_image"":""parent"",""tx_irretutorial_hotels"":""custom""}",,
+,91,88,256,0,1,89,0,0,0,0,0,0,"[Translate to Dansk:] Relations",2,"{""starttime"":""parent"",""endtime"":""parent"",""url"":""parent"",""lastUpdated"":""parent"",""newUntil"":""parent"",""no_search"":""parent"",""shortcut"":""parent"",""shortcut_mode"":""parent"",""author"":""parent"",""author_email"":""parent"",""media"":""parent"",""tx_irretutorial_hotels"":""custom""}",,
 "tx_irretutorial_1nff_hotel",,,,,,,,,,,,,,,,,
 ,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","title","parentid","parenttable","parentidentifier","offers"
 ,2,89,512,0,0,0,0,0,0,0,0,0,"Hotel #0",89,"pages",,0
index 034ce7c..acc24e6 100644 (file)
@@ -4,9 +4,9 @@
 ,88,1,256,0,0,0,0,0,0,0,0,0,"DataHandlerTest",
 ,89,88,256,0,0,0,0,0,0,0,0,0,"Testing #1",
 ,90,88,512,0,0,0,0,0,0,0,0,0,"Target",
-,91,88,256,1,89,0,0,0,0,0,0,0,"Testing #1","{""title"":""parent"",""starttime"":""parent"",""endtime"":""parent"",""url"":""parent"",""lastUpdated"":""parent"",""newUntil"":""parent"",""no_search"":""parent"",""shortcut"":""parent"",""shortcut_mode"":""parent"",""author"":""parent"",""author_email"":""parent"",""media"":""parent"",""og_image"":""parent"",""twitter_image"":""parent""}"
+,91,88,256,1,89,0,0,0,0,0,0,0,"Testing #1","{""title"":""parent"",""starttime"":""parent"",""endtime"":""parent"",""url"":""parent"",""lastUpdated"":""parent"",""newUntil"":""parent"",""no_search"":""parent"",""shortcut"":""parent"",""shortcut_mode"":""parent"",""author"":""parent"",""author_email"":""parent"",""media"":""parent""}"
 ,92,90,256,0,0,0,89,0,0,0,0,0,"Testing #1",
-,93,90,256,1,92,0,91,0,0,0,0,0,"Testing #1","{""title"":""parent"",""starttime"":""parent"",""endtime"":""parent"",""url"":""parent"",""lastUpdated"":""parent"",""newUntil"":""parent"",""no_search"":""parent"",""shortcut"":""parent"",""shortcut_mode"":""parent"",""author"":""parent"",""author_email"":""parent"",""media"":""parent"",""og_image"":""parent"",""twitter_image"":""parent""}"
+,93,90,256,1,92,0,91,0,0,0,0,0,"Testing #1","{""title"":""parent"",""starttime"":""parent"",""endtime"":""parent"",""url"":""parent"",""lastUpdated"":""parent"",""newUntil"":""parent"",""no_search"":""parent"",""shortcut"":""parent"",""shortcut_mode"":""parent"",""author"":""parent"",""author_email"":""parent"",""media"":""parent""}"
 "tt_content",,,,,,,,,,,,,,
 ,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","l10n_source","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","header"
 ,296,88,256,0,0,0,0,0,0,0,0,0,0,"Regular Element #0"
index 95742f8..4c9ac18 100644 (file)
@@ -112,15 +112,6 @@ CREATE TABLE pages (
        backend_layout_next_level varchar(64) DEFAULT '' NOT NULL,
        tsconfig_includes text,
        legacy_overlay_uid int(11) unsigned DEFAULT '0' NOT NULL,
-       seo_title varchar(255) DEFAULT '' NOT NULL,
-       no_index tinyint(4) DEFAULT '0' NOT NULL,
-       no_follow tinyint(4) DEFAULT '0' NOT NULL,
-       og_title varchar(255) DEFAULT '' NOT NULL,
-       og_description text,
-       og_image int(11) unsigned DEFAULT '0' NOT NULL,
-       twitter_title varchar(255) DEFAULT '' NOT NULL,
-       twitter_description text,
-       twitter_image int(11) unsigned DEFAULT '0' NOT NULL,
 
        KEY alias (alias),
        KEY determineSiteRoot (is_siteroot),
index 386b231..63d4bc6 100644 (file)
@@ -34,16 +34,12 @@ use TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction;
 use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
 use TYPO3\CMS\Core\Error\Http\ServiceUnavailableException;
 use TYPO3\CMS\Core\Error\Http\ShortcutTargetPageNotFoundException;
-use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
 use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Locking\Exception\LockAcquireWouldBlockException;
 use TYPO3\CMS\Core\Locking\LockFactory;
 use TYPO3\CMS\Core\Locking\LockingStrategyInterface;
 use TYPO3\CMS\Core\Log\LogManager;
-use TYPO3\CMS\Core\MetaTag\MetaTagManagerRegistry;
 use TYPO3\CMS\Core\Page\PageRenderer;
-use TYPO3\CMS\Core\Resource\FileReference;
-use TYPO3\CMS\Core\Resource\ProcessedFile;
 use TYPO3\CMS\Core\Resource\StorageRepository;
 use TYPO3\CMS\Core\Service\DependencyOrderingService;
 use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
@@ -57,13 +53,11 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\HttpUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Core\Utility\PathUtility;
-use TYPO3\CMS\Extbase\Service\ImageService;
 use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
 use TYPO3\CMS\Frontend\Http\UrlHandlerInterface;
 use TYPO3\CMS\Frontend\Page\CacheHashCalculator;
 use TYPO3\CMS\Frontend\Page\PageRepository;
-use TYPO3\CMS\Frontend\Resource\FileCollector;
 
 /**
  * Class for the built TypoScript based frontend. Instantiated in
@@ -3470,129 +3464,6 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     }
 
     /**
-     * Generate the meta tags that can be set in backend and add them to frontend by using the MetaTag API
-     */
-    public function generateMetaTags()
-    {
-        $metaTagManagerRegistry = MetaTagManagerRegistry::getInstance();
-
-        if (!empty($this->page['description'])) {
-            $manager = $metaTagManagerRegistry->getManagerForProperty('description');
-            $manager->addProperty('description', $this->page['description']);
-        }
-
-        if (!empty($this->page['og_title'])) {
-            $manager = $metaTagManagerRegistry->getManagerForProperty('og:title');
-            $manager->addProperty('og:title', $this->page['og_title']);
-        }
-
-        if (!empty($this->page['og_description'])) {
-            $manager = $metaTagManagerRegistry->getManagerForProperty('og:description');
-            $manager->addProperty('og:description', $this->page['og_description']);
-        }
-
-        if (!empty($this->page['og_image'])) {
-            $fileCollector = GeneralUtility::makeInstance(FileCollector::class);
-            $fileCollector->addFilesFromRelation('pages', 'og_image', $this->page);
-            $manager = $metaTagManagerRegistry->getManagerForProperty('og:image');
-
-            $ogImages = $this->generateSocialImages($fileCollector->getFiles());
-            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
-                );
-            }
-        }
-
-        if (!empty($this->page['twitter_title'])) {
-            $manager = $metaTagManagerRegistry->getManagerForProperty('twitter:title');
-            $manager->addProperty('twitter:title', $this->page['twitter_title']);
-        }
-
-        if (!empty($this->page['twitter_description'])) {
-            $manager = $metaTagManagerRegistry->getManagerForProperty('twitter:description');
-            $manager->addProperty('twitter:description', $this->page['twitter_description']);
-        }
-
-        if (!empty($this->page['twitter_image'])) {
-            $fileCollector = GeneralUtility::makeInstance(FileCollector::class);
-            $fileCollector->addFilesFromRelation('pages', 'twitter_image', $this->page);
-            $manager = $metaTagManagerRegistry->getManagerForProperty('twitter:image');
-
-            $twitterImages = $this->generateSocialImages($fileCollector->getFiles());
-            foreach ($twitterImages as $twitterImage) {
-                $subProperties = [];
-
-                if (!empty($twitterImage['alternative'])) {
-                    $subProperties['alt'] = $twitterImage['alternative'];
-                }
-
-                $manager->addProperty(
-                    'twitter:image',
-                    $twitterImage['url'],
-                    $subProperties
-                );
-            }
-        }
-
-        $noIndex = ((bool)$this->page['no_index']) ? 'noindex' : 'index';
-        $noFollow = ((bool)$this->page['no_follow']) ? 'nofollow' : 'follow';
-
-        $manager = $metaTagManagerRegistry->getManagerForProperty('robots');
-        $manager->addProperty('robots', implode(',', [$noIndex, $noFollow]));
-    }
-
-    /**
-     * @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'] ?: 'default';
-            $cropArea = $cropVariantCollection->getCropArea($cropVariant);
-            $crop = $cropArea->makeAbsoluteBasedOnFile($file);
-
-            $cropInformation = $crop->asArray();
-
-            $processingConfiguration = [
-                'crop' => $crop
-            ];
-
-            $processedImage = $file->getOriginalFile()->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $processingConfiguration);
-
-            $imageUri = $imageService->getImageUri($processedImage, true);
-
-            $socialImages[] = [
-                'url' => $imageUri,
-                'width' => floor($cropInformation['width']),
-                'height' => floor($cropInformation['height']),
-                'alternative' => $arguments['alternative'],
-            ];
-        }
-
-        return $socialImages;
-    }
-
-    /**
      * Compiles the content for the page <title> tag.
      *
      * @param string $pageTitle The input title string, typically the "title" field of a page's record.
index ef31940..4ce8b6c 100644 (file)
@@ -533,7 +533,13 @@ class PageGenerator
             $pageRenderer->addFooterData($tsfe->cObj->cObjGet($tsfe->pSetup['footerData.'], 'footerData.'));
         }
         $tsfe->generatePageTitle();
-        $tsfe->generateMetaTags();
+
+        // @internal hook for EXT:seo, will be gone soon, do not use it in your own extensions
+        $_params = ['page' => $tsfe->page];
+        $_ref = '';
+        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Frontend\Page\PageGenerator']['generateMetaTags'] ?? [] as $_funcRef) {
+            GeneralUtility::callUserFunction($_funcRef, $_params, $_ref);
+        }
 
         static::generateMetaTagHtml(
             $tsfe->pSetup['meta.'] ?? [],
index b368509..4a8ce65 100644 (file)
                        <trans-unit id="pages.editlock_formlabel">
                                <source>Editable for Admins Only</source>
                        </trans-unit>
-                       <trans-unit id="pages.tabs.seo">
-                               <source>SEO</source>
-                       </trans-unit>
                        <trans-unit id="pages.tabs.metadata">
                                <source>Metadata</source>
                        </trans-unit>
                        <trans-unit id="backend_layout.tabs.extended">
                                <source>Extended</source>
                        </trans-unit>
-            <trans-unit id="pages.palettes.seo">
-                <source>General SEO settings</source>
-            </trans-unit>
-            <trans-unit id="pages.title.page_title">
-                <source>Page Title</source>
-            </trans-unit>
-            <trans-unit id="pages.seo_title">
-                <source>Title for search engines</source>
-            </trans-unit>
-            <trans-unit id="pages.palettes.robots">
-                <source>Robot instructions</source>
-            </trans-unit>
-            <trans-unit id="pages.no_index">
-                <source>No index</source>
-            </trans-unit>
-            <trans-unit id="pages.no_index_formlabel">
-                <source>Index this page</source>
-            </trans-unit>
-            <trans-unit id="pages.no_follow">
-                <source>No follow</source>
-            </trans-unit>
-            <trans-unit id="pages.no_follow_formlabel">
-                <source>Follow this page</source>
-            </trans-unit>
-            <trans-unit id="pages.palettes.opengraph">
-                <source>Open Graph (Facebook)</source>
-            </trans-unit>
-            <trans-unit id="pages.og_title">
-                <source>Title</source>
-            </trans-unit>
-            <trans-unit id="pages.og_description">
-                <source>Description</source>
-            </trans-unit>
-            <trans-unit id="pages.og_image">
-                <source>Image</source>
-            </trans-unit>
-            <trans-unit id="pages.palettes.twittercards">
-                <source>Twitter Cards</source>
-            </trans-unit>
-                       <trans-unit id="pages.twitter_title">
-                               <source>Title</source>
-                       </trans-unit>
-                       <trans-unit id="pages.twitter_description">
-                               <source>Description</source>
-                       </trans-unit>
-                       <trans-unit id="pages.twitter_image">
-                               <source>Image</source>
-                       </trans-unit>
                </body>
        </file>
 </xliff>
index 8cfec5b..80c850e 100644 (file)
@@ -185,7 +185,6 @@ class PageGeneratorTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         $tmpl = $this->prophesize(TemplateService::class);
         $tsfe = $this->prophesize(TypoScriptFrontendController::class);
         $tsfe->generatePageTitle()->willReturn('');
-        $tsfe->generateMetaTags()->shouldBeCalled();
         $tsfe->INTincScript_loadJSCode()->shouldBeCalled();
         $tsfe->cObj = $cObj->reveal();
         $tsfe->tmpl = $tmpl->reveal();
@@ -221,7 +220,6 @@ class PageGeneratorTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         $tmpl = $this->prophesize(TemplateService::class);
         $tsfe = $this->prophesize(TypoScriptFrontendController::class);
         $tsfe->generatePageTitle()->willReturn('');
-        $tsfe->generateMetaTags()->shouldBeCalled();
         $tsfe->INTincScript_loadJSCode()->shouldBeCalled();
         $tsfe->cObj = $cObj->reveal();
         $tsfe->tmpl = $tmpl->reveal();
@@ -259,7 +257,6 @@ class PageGeneratorTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         $tmpl = $this->prophesize(TemplateService::class);
         $tsfe = $this->prophesize(TypoScriptFrontendController::class);
         $tsfe->generatePageTitle()->willReturn('');
-        $tsfe->generateMetaTags()->shouldBeCalled();
         $tsfe->INTincScript_loadJSCode()->shouldBeCalled();
         $tsfe->cObj = $cObj->reveal();
         $tsfe->tmpl = $tmpl->reveal();
@@ -351,7 +348,6 @@ class PageGeneratorTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         $tmpl = $this->prophesize(TemplateService::class);
         $tsfe = $this->prophesize(TypoScriptFrontendController::class);
         $tsfe->generatePageTitle()->willReturn('');
-        $tsfe->generateMetaTags()->shouldBeCalled();
         $tsfe->INTincScript_loadJSCode()->shouldBeCalled();
         $tsfe->cObj = $cObj->reveal();
         $tsfe->tmpl = $tmpl->reveal();
diff --git a/typo3/sysext/seo/.gitattributes b/typo3/sysext/seo/.gitattributes
new file mode 100644 (file)
index 0000000..79ede15
--- /dev/null
@@ -0,0 +1,2 @@
+/.gitattributes export-ignore
+/Tests/ export-ignore
diff --git a/typo3/sysext/seo/Classes/Generator/MetaTagGenerator.php b/typo3/sysext/seo/Classes/Generator/MetaTagGenerator.php
new file mode 100644 (file)
index 0000000..73215df
--- /dev/null
@@ -0,0 +1,160 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Seo\Generator;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
+use TYPO3\CMS\Core\MetaTag\MetaTagManagerRegistry;
+use TYPO3\CMS\Core\Resource\FileReference;
+use TYPO3\CMS\Core\Resource\ProcessedFile;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Service\ImageService;
+use TYPO3\CMS\Frontend\Resource\FileCollector;
+
+/**
+ * Class to add the metatags for the SEO fields in core
+ *
+ * @internal
+ */
+class MetaTagGenerator
+{
+    /**
+     * Generate the meta tags that can be set in backend and add them to frontend by using the MetaTag API
+     *
+     * @param array $params
+     */
+    public function generate(array $params)
+    {
+        $metaTagManagerRegistry = GeneralUtility::makeInstance(MetaTagManagerRegistry::class);
+
+        if (!empty($params['page']['description'])) {
+            $manager = $metaTagManagerRegistry->getManagerForProperty('description');
+            $manager->addProperty('description', $params['page']['description']);
+        }
+
+        if (!empty($params['page']['og_title'])) {
+            $manager = $metaTagManagerRegistry->getManagerForProperty('og:title');
+            $manager->addProperty('og:title', $params['page']['og_title']);
+        }
+
+        if (!empty($params['page']['og_description'])) {
+            $manager = $metaTagManagerRegistry->getManagerForProperty('og:description');
+            $manager->addProperty('og:description', $params['page']['og_description']);
+        }
+
+        if (!empty($params['page']['og_image'])) {
+            $fileCollector = GeneralUtility::makeInstance(FileCollector::class);
+            $fileCollector->addFilesFromRelation('pages', 'og_image', $params['page']);
+            $manager = $metaTagManagerRegistry->getManagerForProperty('og:image');
+
+            $ogImages = $this->generateSocialImages($fileCollector->getFiles());
+            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
+                );
+            }
+        }
+
+        if (!empty($params['page']['twitter_title'])) {
+            $manager = $metaTagManagerRegistry->getManagerForProperty('twitter:title');
+            $manager->addProperty('twitter:title', $params['page']['twitter_title']);
+        }
+
+        if (!empty($params['page']['twitter_description'])) {
+            $manager = $metaTagManagerRegistry->getManagerForProperty('twitter:description');
+            $manager->addProperty('twitter:description', $params['page']['twitter_description']);
+        }
+
+        if (!empty($params['page']['twitter_image'])) {
+            $fileCollector = GeneralUtility::makeInstance(FileCollector::class);
+            $fileCollector->addFilesFromRelation('pages', 'twitter_image', $params['page']);
+            $manager = $metaTagManagerRegistry->getManagerForProperty('twitter:image');
+
+            $twitterImages = $this->generateSocialImages($fileCollector->getFiles());
+            foreach ($twitterImages as $twitterImage) {
+                $subProperties = [];
+
+                if (!empty($twitterImage['alternative'])) {
+                    $subProperties['alt'] = $twitterImage['alternative'];
+                }
+
+                $manager->addProperty(
+                    'twitter:image',
+                    $twitterImage['url'],
+                    $subProperties
+                );
+            }
+        }
+
+        $noIndex = ((bool)$params['page']['no_index']) ? 'noindex' : 'index';
+        $noFollow = ((bool)$params['page']['no_follow']) ? 'nofollow' : 'follow';
+
+        $manager = $metaTagManagerRegistry->getManagerForProperty('robots');
+        $manager->addProperty('robots', implode(',', [$noIndex, $noFollow]));
+    }
+
+    /**
+     * @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'] ?: 'default';
+            $cropArea = $cropVariantCollection->getCropArea($cropVariant);
+            $crop = $cropArea->makeAbsoluteBasedOnFile($file);
+
+            $cropInformation = $crop->asArray();
+
+            $processingConfiguration = [
+                'crop' => $crop
+            ];
+
+            $processedImage = $file->getOriginalFile()->process(
+                ProcessedFile::CONTEXT_IMAGECROPSCALEMASK,
+                $processingConfiguration
+            );
+
+            $imageUri = $imageService->getImageUri($processedImage, true);
+
+            $socialImages[] = [
+                'url' => $imageUri,
+                'width' => floor($cropInformation['width']),
+                'height' => floor($cropInformation['height']),
+                'alternative' => $arguments['alternative'],
+            ];
+        }
+
+        return $socialImages;
+    }
+}
diff --git a/typo3/sysext/seo/Configuration/TCA/Overrides/pages.php b/typo3/sysext/seo/Configuration/TCA/Overrides/pages.php
new file mode 100644 (file)
index 0000000..d797209
--- /dev/null
@@ -0,0 +1,179 @@
+<?php
+defined('TYPO3_MODE') or die();
+
+$tca = [
+    'palettes' => [
+        'seo' => [
+            'label' => 'LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.palettes.seo',
+            'showitem' => 'seo_title;LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.seo_title',
+        ],
+        'robots' => [
+            'label' => 'LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.palettes.robots',
+            'showitem' => 'no_index;LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.no_index_formlabel, no_follow;LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.no_follow_formlabel',
+        ],
+        'opengraph' => [
+            'label' => 'LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.palettes.opengraph',
+            'showitem' => 'og_title, --linebreak--, og_description, --linebreak--, og_image',
+        ],
+        'twittercards' => [
+            'label' => 'LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.palettes.twittercards',
+            'showitem' => 'twitter_title, --linebreak--, twitter_description, --linebreak--, twitter_image',
+        ],
+    ],
+    'columns' => [
+        'seo_title' => [
+            'exclude' => true,
+            'l10n_mode' => 'prefixLangTitle',
+            'label' => 'LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.seo_title',
+            'config' => [
+                'type' => 'input',
+                'size' => 40,
+                'max' => 255,
+                'eval' => 'trim'
+            ]
+        ],
+        'no_index' => [
+            'exclude' => true,
+            'l10n_mode' => 'exclude',
+            'label' => 'LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.no_index',
+            'config' => [
+                'type' => 'check',
+                'renderType' => 'checkboxToggle',
+                'items' => [
+                    [
+                        '0' => '',
+                        '1' => '',
+                        'invertStateDisplay' => true
+                    ]
+                ]
+            ]
+        ],
+        'no_follow' => [
+            'exclude' => true,
+            'l10n_mode' => 'exclude',
+            'label' => 'LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.no_follow',
+            'config' => [
+                'type' => 'check',
+                'renderType' => 'checkboxToggle',
+                'items' => [
+                    [
+                        '0' => '',
+                        '1' => '',
+                        'invertStateDisplay' => true
+                    ]
+                ]
+            ]
+        ],
+        'og_title' => [
+            'exclude' => true,
+            'l10n_mode' => 'prefixLangTitle',
+            'label' => 'LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.og_title',
+            'config' => [
+                'type' => 'input',
+                'size' => 40,
+                'max' => 255,
+                'eval' => 'trim'
+            ]
+        ],
+        'og_description' => [
+            'exclude' => true,
+            'l10n_mode' => 'prefixLangTitle',
+            'label' => 'LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.og_description',
+            'config' => [
+                'type' => 'text',
+                'cols' => 40,
+                'rows' => 3
+            ]
+        ],
+        'og_image' => [
+            'exclude' => true,
+            'label' => 'LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.og_image',
+            'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig(
+                'og_image',
+                [
+                    // Use the imageoverlayPalette instead of the basicoverlayPalette
+                    'overrideChildTca' => [
+                        'types' => [
+                            '0' => [
+                                'showitem' => '
+                                    --palette--;;imageoverlayPalette,
+                                    --palette--;;filePalette'
+                            ],
+                            \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [
+                                'showitem' => '
+                                    --palette--;;imageoverlayPalette,
+                                    --palette--;;filePalette'
+                            ]
+                        ],
+                    ],
+                    'behaviour' => [
+                        'allowLanguageSynchronization' => true
+                    ]
+                ],
+                $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']
+            )
+        ],
+        'twitter_title' => [
+            'exclude' => true,
+            'l10n_mode' => 'prefixLangTitle',
+            'label' => 'LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.twitter_title',
+            'config' => [
+                'type' => 'input',
+                'size' => 40,
+                'max' => 255,
+                'eval' => 'trim'
+            ]
+        ],
+        'twitter_description' => [
+            'exclude' => true,
+            'l10n_mode' => 'prefixLangTitle',
+            'label' => 'LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.twitter_description',
+            'config' => [
+                'type' => 'text',
+                'cols' => 40,
+                'rows' => 3
+            ]
+        ],
+        'twitter_image' => [
+            'exclude' => true,
+            'label' => 'LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.twitter_image',
+            'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig(
+                'twitter_image',
+                [
+                    // Use the imageoverlayPalette instead of the basicoverlayPalette
+                    'overrideChildTca' => [
+                        'types' => [
+                            '0' => [
+                                'showitem' => '
+                                    --palette--;;imageoverlayPalette,
+                                    --palette--;;filePalette'
+                            ],
+                            \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [
+                                'showitem' => '
+                                    --palette--;;imageoverlayPalette,
+                                    --palette--;;filePalette'
+                            ]
+                        ],
+                    ],
+                    'behaviour' => [
+                        'allowLanguageSynchronization' => true
+                    ]
+                ],
+                $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']
+            )
+        ],
+    ],
+];
+
+$GLOBALS['TCA']['pages'] = array_replace_recursive($GLOBALS['TCA']['pages'], $tca);
+\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes(
+    'pages',
+    '
+    --div--;LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.tabs.seo,
+        --palette--;;seo,
+        --palette--;;robots,
+        --palette--;;opengraph,
+        --palette--;;twittercards,',
+    (string)\TYPO3\CMS\Frontend\Page\PageRepository::DOKTYPE_DEFAULT,
+    'after:title'
+);
diff --git a/typo3/sysext/seo/LICENSE.txt b/typo3/sysext/seo/LICENSE.txt
new file mode 100644 (file)
index 0000000..95d36a7
--- /dev/null
@@ -0,0 +1,345 @@
+Some icons used in the TYPO3 project are retrieved from the "Silk" icon set of
+Mark James, which can be found at http://famfamfam.com/lab/icons/silk/. This
+set is distributed under a Creative Commons Attribution 2.5 License. The
+license can be found at http://creativecommons.org/licenses/by/2.5/.
+---------------------------------
+
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/typo3/sysext/seo/Resources/Private/Language/locallang_tca.xlf b/typo3/sysext/seo/Resources/Private/Language/locallang_tca.xlf
new file mode 100644 (file)
index 0000000..4bdbd70
--- /dev/null
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xliff version="1.0" xmlns:t3="http://typo3.org/schemas/xliff">
+       <file t3:id="1528554340" source-language="en" datatype="plaintext" original="messages" date="2018-08-09T16:22:32Z" product-name="seo">
+               <header/>
+               <body>
+                       <trans-unit id="pages.tabs.seo">
+                               <source>SEO</source>
+                       </trans-unit>
+            <trans-unit id="pages.palettes.seo">
+                <source>General SEO settings</source>
+            </trans-unit>
+            <trans-unit id="pages.seo_title">
+                <source>Title for search engines</source>
+            </trans-unit>
+            <trans-unit id="pages.palettes.robots">
+                <source>Robot instructions</source>
+            </trans-unit>
+            <trans-unit id="pages.no_index">
+                <source>No index</source>
+            </trans-unit>
+            <trans-unit id="pages.no_index_formlabel">
+                <source>Index this page</source>
+            </trans-unit>
+            <trans-unit id="pages.no_follow">
+                <source>No follow</source>
+            </trans-unit>
+            <trans-unit id="pages.no_follow_formlabel">
+                <source>Follow this page</source>
+            </trans-unit>
+            <trans-unit id="pages.palettes.opengraph">
+                <source>Open Graph (Facebook)</source>
+            </trans-unit>
+            <trans-unit id="pages.og_title">
+                <source>Title</source>
+            </trans-unit>
+            <trans-unit id="pages.og_description">
+                <source>Description</source>
+            </trans-unit>
+            <trans-unit id="pages.og_image">
+                <source>Image</source>
+            </trans-unit>
+            <trans-unit id="pages.palettes.twittercards">
+                <source>Twitter Cards</source>
+            </trans-unit>
+                       <trans-unit id="pages.twitter_title">
+                               <source>Title</source>
+                       </trans-unit>
+                       <trans-unit id="pages.twitter_description">
+                               <source>Description</source>
+                       </trans-unit>
+                       <trans-unit id="pages.twitter_image">
+                               <source>Image</source>
+                       </trans-unit>
+               </body>
+       </file>
+</xliff>
diff --git a/typo3/sysext/seo/Resources/Public/Icons/Extension.png b/typo3/sysext/seo/Resources/Public/Icons/Extension.png
new file mode 100644 (file)
index 0000000..ef81ce2
Binary files /dev/null and b/typo3/sysext/seo/Resources/Public/Icons/Extension.png differ
diff --git a/typo3/sysext/seo/composer.json b/typo3/sysext/seo/composer.json
new file mode 100644 (file)
index 0000000..ef3c78f
--- /dev/null
@@ -0,0 +1,42 @@
+{
+       "name": "typo3/cms-seo",
+       "type": "typo3-cms-framework",
+       "description": "SEO features for TYPO3.",
+       "homepage": "https://typo3.org",
+       "license": ["GPL-2.0-or-later"],
+       "authors": [{
+               "name": "TYPO3 Core Team",
+               "email": "typo3cms@typo3.org",
+               "role": "Developer"
+       }],
+       "config": {
+               "sort-packages": true
+       },
+       "require": {
+               "typo3/cms-core": "9.3.*@dev",
+               "typo3/cms-frontend": "9.3.*@dev",
+               "typo3/cms-extbase": "9.3.*@dev"
+       },
+       "conflict": {
+               "typo3/cms": "*"
+       },
+       "replace": {
+               "seo": "*"
+       },
+       "extra": {
+               "branch-alias": {
+                       "dev-master": "9.3.x-dev"
+               },
+               "typo3/cms": {
+                       "extension-key": "seo",
+                       "Package": {
+                               "partOfFactoryDefault": true
+                       }
+               }
+       },
+       "autoload": {
+               "psr-4": {
+                       "TYPO3\\CMS\\Seo\\": "Classes/"
+               }
+       }
+}
diff --git a/typo3/sysext/seo/ext_emconf.php b/typo3/sysext/seo/ext_emconf.php
new file mode 100644 (file)
index 0000000..c37c9e0
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+$EM_CONF[$_EXTKEY] = [
+    'title' => 'SEO',
+    'description' => 'Add basic SEO features to TYPO3',
+    'category' => 'fe',
+    'author' => 'TYPO3 Core Team',
+    'author_email' => 'typo3cms@typo3.org',
+    'author_company' => '',
+    'state' => 'stable',
+    'uploadfolder' => 0,
+    'createDirs' => '',
+    'clearCacheOnLoad' => 0,
+    'version' => '9.3.0',
+    'constraints' => [
+        'depends' => [
+            'typo3' => '9.3.0'
+        ],
+        'conflicts' => [],
+        'suggests' => [],
+    ],
+];
diff --git a/typo3/sysext/seo/ext_localconf.php b/typo3/sysext/seo/ext_localconf.php
new file mode 100644 (file)
index 0000000..8de0b29
--- /dev/null
@@ -0,0 +1,6 @@
+<?php
+
+defined('TYPO3_MODE') or die();
+
+$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Frontend\Page\PageGenerator']['generateMetaTags'][] =
+    \TYPO3\CMS\Seo\Generator\MetaTagGenerator::class . '->generate';
diff --git a/typo3/sysext/seo/ext_tables.sql b/typo3/sysext/seo/ext_tables.sql
new file mode 100644 (file)
index 0000000..9256f9a
--- /dev/null
@@ -0,0 +1,14 @@
+#
+# Table structure for table 'pages'
+#
+CREATE TABLE pages (
+       seo_title varchar(255) DEFAULT '' NOT NULL,
+       no_index tinyint(4) DEFAULT '0' NOT NULL,
+       no_follow tinyint(4) DEFAULT '0' NOT NULL,
+       og_title varchar(255) DEFAULT '' NOT NULL,
+       og_description text,
+       og_image int(11) unsigned DEFAULT '0' NOT NULL,
+       twitter_title varchar(255) DEFAULT '' NOT NULL,
+       twitter_description text,
+       twitter_image int(11) unsigned DEFAULT '0' NOT NULL,
+);