first commit

This commit is contained in:
salam committed 2026-10-19 08:18:39 +02:00
commit 1218dd1517
12716 files changed
+989233

No files matched your search

@@ -0,0 +1,168 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Seo\Canonical;
/*
* 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\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
use TYPO3\CMS\Frontend\Page\PageRepository;
use TYPO3\CMS\Frontend\Utility\CanonicalizationUtility;
/**
* Class to add the canonical tag to the page
*
* @internal this class is not part of TYPO3's Core API.
*/
class CanonicalGenerator
{
/**
* @var TypoScriptFrontendController
*/
protected $typoScriptFrontendController;
/**
* @var PageRepository
*/
protected $pageRepository;
/**
* @var Dispatcher
*/
protected $signalSlotDispatcher;
/**
* CanonicalGenerator constructor
*
* @param TypoScriptFrontendController $typoScriptFrontendController
* @param Dispatcher $signalSlotDispatcher
*/
public function __construct(TypoScriptFrontendController $typoScriptFrontendController = null, Dispatcher $signalSlotDispatcher = null)
{
if ($typoScriptFrontendController === null) {
$typoScriptFrontendController = $this->getTypoScriptFrontendController();
}
if ($signalSlotDispatcher === null) {
$signalSlotDispatcher = GeneralUtility::makeInstance(Dispatcher::class);
}
$this->typoScriptFrontendController = $typoScriptFrontendController;
$this->signalSlotDispatcher = $signalSlotDispatcher;
$this->pageRepository = GeneralUtility::makeInstance(PageRepository::class);
}
/**
* @return string
* @throws \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotException
* @throws \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotReturnException
*/
public function generate(): string
{
$href = '';
$this->signalSlotDispatcher->dispatch(self::class, 'beforeGeneratingCanonical', [&$href]);
if (empty($href) && (int)$this->typoScriptFrontendController->page['no_index'] === 1) {
return '';
}
if (empty($href)) {
// 1) Check if page show content from other page
$href = $this->checkContentFromPid();
}
if (empty($href)) {
// 2) Check if page has canonical URL set
$href = $this->checkForCanonicalLink();
}
if (empty($href)) {
// 3) Fallback, create canonical URL
$href = $this->checkDefaultCanonical();
}
if (!empty($href)) {
$canonical = '<link ' . GeneralUtility::implodeAttributes([
'rel' => 'canonical',
'href' => $href
], true) . '/>' . LF;
$this->typoScriptFrontendController->additionalHeaderData[] = $canonical;
return $canonical;
}
return '';
}
/**
* @return string
*/
protected function checkForCanonicalLink(): string
{
if (!empty($this->typoScriptFrontendController->page['canonical_link'])) {
return $this->typoScriptFrontendController->cObj->typoLink_URL([
'parameter' => $this->typoScriptFrontendController->page['canonical_link'],
'forceAbsoluteUrl' => true,
]);
}
return '';
}
/**
* @return string
*/
protected function checkContentFromPid(): string
{
if (!empty($this->typoScriptFrontendController->page['content_from_pid'])) {
$parameter = (int)$this->typoScriptFrontendController->page['content_from_pid'];
if ($parameter > 0) {
$targetPage = $this->pageRepository->getPage($parameter, true);
if (!empty($targetPage['canonical_link'])) {
$parameter = $targetPage['canonical_link'];
}
return $this->typoScriptFrontendController->cObj->typoLink_URL([
'parameter' => $parameter,
'forceAbsoluteUrl' => true,
]);
}
}
return '';
}
/**
* @return string
*/
protected function checkDefaultCanonical(): string
{
return $this->typoScriptFrontendController->cObj->typoLink_URL([
'parameter' => $this->typoScriptFrontendController->id . ',' . $this->typoScriptFrontendController->type,
'forceAbsoluteUrl' => true,
'addQueryString' => true,
'addQueryString.' => [
'method' => 'GET',
'exclude' => implode(
',',
CanonicalizationUtility::getParamsToExcludeForCanonicalizedUrl(
(int)$this->typoScriptFrontendController->id,
(array)$GLOBALS['TYPO3_CONF_VARS']['FE']['additionalCanonicalizedUrlParameters']
)
)
]
]);
}
/**
* @return TypoScriptFrontendController
*/
protected function getTypoScriptFrontendController(): TypoScriptFrontendController
{
return $GLOBALS['TSFE'];
}
}
@@ -0,0 +1,146 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Seo\HrefLang;
/*
* 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 Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Http\Uri;
use TYPO3\CMS\Core\Site\Entity\Site;
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
use TYPO3\CMS\Frontend\DataProcessing\LanguageMenuProcessor;
/**
* Class to add the hreflang tags to the page
*
* @internal
*/
class HrefLangGenerator
{
/**
* The content object renderer
*
* @var ContentObjectRenderer
*/
public $cObj;
/**
* @var TypoScriptFrontendController
*/
protected $typoScriptFrontendController;
/**
* @var ServerRequestInterface
*/
protected $request;
/**
* HreflangGenerator constructor
*
* @param ContentObjectRenderer|null $cObj
* @param TypoScriptFrontendController|null $typoScriptFrontendController
*/
public function __construct(ContentObjectRenderer $cObj = null, TypoScriptFrontendController $typoScriptFrontendController = null, ServerRequestInterface $request = null)
{
if ($cObj === null) {
$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
}
if ($typoScriptFrontendController === null) {
$typoScriptFrontendController = $this->getTypoScriptFrontendController();
}
if ($request === null) {
$request = $this->getRequest();
}
$this->cObj = $cObj;
$this->typoScriptFrontendController = $typoScriptFrontendController;
$this->request = $request;
}
public function generate(): string
{
$hreflangs = [];
if ((int)$this->typoScriptFrontendController->page['no_index'] === 1) {
return '';
}
if ($this->request->getAttribute('site') instanceof Site) {
$languageMenu = GeneralUtility::makeInstance(LanguageMenuProcessor::class);
$languages = $languageMenu->process($this->cObj, [], [], []);
foreach ($languages['languagemenu'] as $language) {
if ($language['available'] === 1 && !empty($language['link'])) {
$href = $this->getAbsoluteUrl($language['link']);
$hreflangs[] =
'<link rel="alternate" hreflang="' . htmlspecialchars($language['hreflang']) . '" href="' . htmlspecialchars($href) . '"/>';
}
}
if (count($hreflangs) > 1) {
$href = $this->getAbsoluteUrl($languages['languagemenu'][0]['link']);
$hreflangs[] =
'<link rel="alternate" hreflang="x-default" href="' . htmlspecialchars($href) . '"/>' . LF;
$this->getTypoScriptFrontendController()->additionalHeaderData[] = implode(LF, $hreflangs);
}
}
return implode(LF, $hreflangs);
}
/**
* @param string $url
* @return string
*/
protected function getAbsoluteUrl(string $url): string
{
$uri = new Uri($url);
if (empty($uri->getHost())) {
$url = $this->getSiteLanguage()->getBase()->withPath($uri->getPath());
if ($uri->getQuery()) {
$url = $url->withQuery($uri->getQuery());
}
}
return (string)$url;
}
/**
* @return SiteLanguage
*/
protected function getSiteLanguage(): SiteLanguage
{
return $this->request->getAttribute('language');
}
/**
* @return TypoScriptFrontendController
*/
protected function getTypoScriptFrontendController(): TypoScriptFrontendController
{
return $GLOBALS['TSFE'];
}
/**
* @return ServerRequestInterface
*/
protected function getRequest(): ServerRequestInterface
{
return $GLOBALS['TYPO3_REQUEST'];
}
}
@@ -0,0 +1,170 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Seo\MetaTag;
/*
* 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
);
}
}
/*
* Set type of twitter card to summary. This value can be overridden by TypoScript or the MetaTag API by
* using the replace option. In v10 this will be a page property
*/
$manager = $metaTagManagerRegistry->getManagerForProperty('twitter:card');
$manager->addProperty('twitter:card', 'summary');
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';
if ($noIndex === 'noindex' || $noFollow === 'nofollow') {
$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'] ?: 'social';
$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;
}
}
@@ -0,0 +1,68 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Seo\MetaTag;
/*
* 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\MetaTag\AbstractMetaTagManager;
/**
* @internal this class is not part of TYPO3's Core API.
*/
class OpenGraphMetaTagManager extends AbstractMetaTagManager
{
/**
* The default attribute that defines the name of the property
*
* This creates tags like <meta property="" /> by default
*
* @var string
*/
protected $defaultNameAttribute = 'property';
/**
* Array of properties that can be handled by this manager
*
* @var array
*/
protected $handledProperties = [
'og:type' => [],
'og:title' => [],
'og:description' => [],
'og:site_name' => [],
'og:url' => [],
'og:audio' => [],
'og:video' => [],
'og:determiner' => [],
'og:locale' => [
'allowedSubProperties' => [
'alternate' => [
'allowMultipleOccurrences' => true
],
]
],
'og:image' => [
'allowMultipleOccurrences' => true,
'allowedSubProperties' => [
'url' => [],
'secure_url' => [],
'type' => [],
'width' => [],
'height' => [],
'alt' => [],
]
]
];
}
@@ -0,0 +1,71 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Seo\MetaTag;
/*
* 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\MetaTag\AbstractMetaTagManager;
/**
* @internal this class is not part of TYPO3's Core API.
*/
class TwitterCardMetaTagManager extends AbstractMetaTagManager
{
/**
* Array of properties that can be handled by this manager
*
* @var array
*/
protected $handledProperties = [
'twitter:card' => [],
'twitter:site' => [
'allowedSubProperties' => [
'id' => [],
]
],
'twitter:creator' => [
'allowedSubProperties' => [
'id' => [],
]
],
'twitter:description' => [],
'twitter:title' => [],
'twitter:image' => [
'allowedSubProperties' => [
'alt' => [],
]
],
'twitter:player' => [
'allowedSubProperties' => [
'width' => [],
'height' => [],
'stream' => [],
]
],
'twitter:app' => [
'allowedSubProperties' => [
'name:iphone' => [],
'id:iphone' => [],
'url:iphone' => [],
'name:ipad' => [],
'id:ipad' => [],
'url:ipad' => [],
'name:googleplay' => [],
'id:googleplay' => [],
'url:googleplay' => [],
]
],
];
}
@@ -0,0 +1,31 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Seo\PageTitle;
/*
* 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\PageTitle\AbstractPageTitleProvider;
/**
* This class will take care of the seo title that can be set in the backend
* @internal this class is not part of TYPO3's Core API.
*/
class SeoTitlePageTitleProvider extends AbstractPageTitleProvider
{
public function __construct()
{
$this->title = (string)$GLOBALS['TSFE']->page['seo_title'];
}
}
@@ -0,0 +1,138 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Seo\XmlSitemap;
/*
* 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 Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
/**
* Base class for XmlSitemapProviders to extend
*/
abstract class AbstractXmlSitemapDataProvider implements XmlSitemapDataProviderInterface
{
/**
* @var string
*/
protected $key;
/**
* @var int
*/
protected $lastModified;
/**
* @var array
*/
protected $items = [];
/**
* @var array
*/
protected $config = [];
/**
* @var ContentObjectRenderer
*/
protected $cObj;
/**
* @var int
*/
protected $numberOfItemsPerPage = 1000;
/**
* @var ServerRequestInterface
*/
protected $request;
/**
* AbstractXmlSitemapDataProvider constructor
*
* @param \Psr\Http\Message\ServerRequestInterface $request
* @param string $key
* @param array $config
* @param ContentObjectRenderer $cObj
*/
public function __construct(ServerRequestInterface $request, string $key, array $config = [], ContentObjectRenderer $cObj = null)
{
$this->key = $key;
$this->config = $config;
$this->request = $request;
if ($cObj === null) {
$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
}
$this->cObj = $cObj;
}
/**
* @return string
*/
public function getKey(): string
{
return $this->key;
}
/**
* @return int
*/
public function getNumberOfPages(): int
{
return (int)ceil(count($this->items) / $this->numberOfItemsPerPage);
}
/**
* @return int
*/
public function getLastModified(): int
{
$lastMod = 0;
foreach ($this->items as $item) {
if ((int)$item['lastMod'] > $lastMod) {
$lastMod = (int)$item['lastMod'];
}
}
return $lastMod;
}
/**
* @param array $data
* @return array
*/
protected function defineUrl(array $data): array
{
return $data;
}
/**
* @return array
*/
public function getItems(): array
{
$pageNumber = (int)($this->request->getQueryParams()['page'] ?? 0);
$page = $pageNumber > 0 ? $pageNumber : 0;
$items = array_slice(
$this->items,
$page * $this->numberOfItemsPerPage,
$this->numberOfItemsPerPage
);
return array_map([$this, 'defineUrl'], $items);
}
}
@@ -0,0 +1,20 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Seo\XmlSitemap\Exception;
/*
* 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!
*/
class InvalidConfigurationException extends \TYPO3\CMS\Core\Resource\Exception
{
}
@@ -0,0 +1,20 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Seo\XmlSitemap\Exception;
/*
* 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!
*/
class MissingConfigurationException extends \TYPO3\CMS\Core\Resource\Exception
{
}
@@ -0,0 +1,124 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Seo\XmlSitemap;
/*
* 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 Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Context\LanguageAspect;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryHelper;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\Page\PageRepository;
/**
* Class to generate a XML sitemap for pages
* @internal this class is not part of TYPO3's Core API.
*/
class PagesXmlSitemapDataProvider extends AbstractXmlSitemapDataProvider
{
public function __construct(ServerRequestInterface $request, string $key, array $config = [], ContentObjectRenderer $cObj = null)
{
parent::__construct($request, $key, $config, $cObj);
$this->generateItems();
}
protected function generateItems(): void
{
$pageRepository = GeneralUtility::makeInstance(PageRepository::class);
$pages = $pageRepository->getPagesOverlay($this->getPages());
$languageAspect = $this->getCurrentLanguageAspect();
foreach ($pages as $page) {
if (!$pageRepository->isPageSuitableForLanguage($page, $languageAspect)) {
continue;
}
$this->items[] = [
'uid' => $page['uid'],
'lastMod' => (int)($page['SYS_LASTCHANGED'] ?: $page['tstamp'])
];
}
}
/**
* @return array
*/
protected function getPages(): array
{
if (!empty($this->config['rootPage'])) {
$rootPageId = (int)$this->config['rootPage'];
} else {
$site = $this->request->getAttribute('site');
$rootPageId = $site->getRootPageId();
}
$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
$treeList = $cObj->getTreeList(-$rootPageId, 99);
$treeListArray = GeneralUtility::intExplode(',', $treeList);
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('pages');
$constraints = [
$queryBuilder->expr()->in('uid', $treeListArray)
];
if (!empty($this->config['additionalWhere'])) {
$constraints[] = QueryHelper::stripLogicalOperatorPrefix($this->config['additionalWhere']);
}
if (!empty($this->config['excludedDoktypes'])) {
$excludedDoktypes = GeneralUtility::intExplode(',', $this->config['excludedDoktypes']);
if (!empty($excludedDoktypes)) {
$constraints[] = $queryBuilder->expr()->notIn('doktype', implode(',', $excludedDoktypes));
}
}
$pages = $queryBuilder->select('*')
->from('pages')
->where(...$constraints)
->orderBy('uid', 'ASC')
->execute()
->fetchAll();
return $pages;
}
/**
* @return LanguageAspect
*/
protected function getCurrentLanguageAspect(): LanguageAspect
{
return GeneralUtility::makeInstance(Context::class)->getAspect('language');
}
/**
* @param array $data
* @return array
*/
protected function defineUrl(array $data): array
{
$typoLinkConfig = [
'parameter' => $data['uid'],
'forceAbsoluteUrl' => 1,
];
$data['loc'] = $this->cObj->typoLink_URL($typoLinkConfig);
return $data;
}
}
@@ -0,0 +1,195 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Seo\XmlSitemap;
/*
* 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 Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryHelper;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Seo\XmlSitemap\Exception\MissingConfigurationException;
/**
* XmlSiteDataProvider will provide information for the XML sitemap for a specific database table
* @internal this class is not part of TYPO3's Core API.
*/
class RecordsXmlSitemapDataProvider extends AbstractXmlSitemapDataProvider
{
/**
* @param ServerRequestInterface $request
* @param string $key
* @param array $config
* @param ContentObjectRenderer|null $cObj
* @throws MissingConfigurationException
*/
public function __construct(ServerRequestInterface $request, string $key, array $config = [], ContentObjectRenderer $cObj = null)
{
parent::__construct($request, $key, $config, $cObj);
$this->generateItems();
}
/**
* @throws MissingConfigurationException
*/
public function generateItems(): void
{
$table = $this->config['table'];
if (empty($table)) {
throw new MissingConfigurationException(
'No configuration found for sitemap ' . $this->getKey(),
1535576053
);
}
$pids = !empty($this->config['pid']) ? GeneralUtility::intExplode(',', $this->config['pid']) : [];
$lastModifiedField = $this->config['lastModifiedField'] ?? 'tstamp';
$sortField = $this->config['sortField'] ?? 'sorting';
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable($table);
$constraints = [];
if (!empty($GLOBALS['TCA'][$table]['ctrl']['languageField'])) {
$constraints[] = $queryBuilder->expr()->in(
$GLOBALS['TCA'][$table]['ctrl']['languageField'],
[
-1, // All languages
$this->getLanguageId() // Current language
]
);
}
if (!empty($pids)) {
$recursiveLevel = isset($this->config['recursive']) ? (int)$this->config['recursive'] : 0;
if ($recursiveLevel) {
$newList = [];
foreach ($pids as $pid) {
$list = $this->cObj->getTreeList($pid, $recursiveLevel);
if ($list) {
$newList = array_merge($newList, explode(',', $list));
}
}
$pids = array_merge($pids, $newList);
}
$constraints[] = $queryBuilder->expr()->in('pid', $pids);
}
if (!empty($this->config['additionalWhere'])) {
$constraints[] = QueryHelper::stripLogicalOperatorPrefix($this->config['additionalWhere']);
}
$queryBuilder->select('*')
->from($table);
if (!empty($constraints)) {
$queryBuilder->where(
...$constraints
);
}
$rows = $queryBuilder->orderBy($sortField)
->execute()
->fetchAll();
foreach ($rows as $row) {
$this->items[] = [
'data' => $row,
'lastMod' => (int)$row[$lastModifiedField]
];
}
}
/**
* @param array $data
* @return array
*/
protected function defineUrl(array $data): array
{
$pageId = $this->config['url']['pageId'] ?? $GLOBALS['TSFE']->id;
$additionalParams = [];
$additionalParams = $this->getUrlFieldParameterMap($additionalParams, $data['data']);
$additionalParams = $this->getUrlAdditionalParams($additionalParams);
$additionalParamsString = http_build_query(
$additionalParams,
'',
'&',
PHP_QUERY_RFC3986
);
$typoLinkConfig = [
'parameter' => $pageId,
'additionalParams' => $additionalParamsString ? '&' . $additionalParamsString : '',
'forceAbsoluteUrl' => 1,
'useCacheHash' => $this->config['url']['useCacheHash'] ?? 0
];
$data['loc'] = $this->cObj->typoLink_URL($typoLinkConfig);
return $data;
}
/**
* @param array $additionalParams
* @param array $data
* @return array
*/
protected function getUrlFieldParameterMap(array $additionalParams, array $data): array
{
if (!empty($this->config['url']['fieldToParameterMap']) &&
\is_array($this->config['url']['fieldToParameterMap'])) {
foreach ($this->config['url']['fieldToParameterMap'] as $field => $urlPart) {
$additionalParams[$urlPart] = $data[$field];
}
}
return $additionalParams;
}
/**
* @param array $additionalParams
* @return array
*/
protected function getUrlAdditionalParams(array $additionalParams): array
{
if (!empty($this->config['url']['additionalGetParameters']) &&
is_array($this->config['url']['additionalGetParameters'])) {
foreach ($this->config['url']['additionalGetParameters'] as $extension => $extensionConfig) {
foreach ($extensionConfig as $key => $value) {
$additionalParams[$extension . '[' . $key . ']'] = $value;
}
}
}
return $additionalParams;
}
/**
* @return int
* @throws \TYPO3\CMS\Core\Context\Exception\AspectNotFoundException
*/
protected function getLanguageId(): int
{
$context = GeneralUtility::makeInstance(Context::class);
return (int)$context->getPropertyFromAspect('language', 'id');
}
}
@@ -0,0 +1,32 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Seo\XmlSitemap;
/*
* 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 Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
/**
* Interface for XmlSitemapDataProviders containing the methods that are called by the XmlSitemapRenderer
*/
interface XmlSitemapDataProviderInterface
{
public function __construct(ServerRequestInterface $request, string $name, array $config = [], ContentObjectRenderer $cObj = null);
public function getKey(): string;
public function getItems(): array;
public function getLastModified(): int;
public function getNumberOfPages(): int;
}
@@ -0,0 +1,169 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Seo\XmlSitemap;
/*
* 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 Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Fluid\View\StandaloneView;
use TYPO3\CMS\Seo\XmlSitemap\Exception\InvalidConfigurationException;
/**
* Class to render the XML Sitemap to be used as a UserFunction
* @internal this class is not part of TYPO3's Core API.
*/
class XmlSitemapRenderer
{
/**
* @var array
*/
protected $configuration;
/**
* @var \TYPO3\CMS\Fluid\View\StandaloneView
*/
protected $view;
public function __construct()
{
$this->configuration = $this->getConfiguration();
$this->view = $this->getStandaloneView();
$this->view->assign(
'xslFile',
PathUtility::stripPathSitePrefix(
ExtensionManagementUtility::extPath('seo', 'Resources/Public/CSS/Sitemap.xsl')
)
);
}
/**
* @return string
* @throws InvalidConfigurationException
*/
public function render(): string
{
// Inject request from globals until request will be available to cObj
$request = $GLOBALS['TYPO3_REQUEST'];
$this->view->assign('type', $GLOBALS['TSFE']->type);
if (!empty($sitemap = $request->getQueryParams()['sitemap'])) {
return $this->renderSitemap($request, $sitemap);
}
return $this->renderIndex($request);
}
/**
* @param \Psr\Http\Message\ServerRequestInterface $request
* @return string
*/
protected function renderIndex(ServerRequestInterface $request): string
{
$sitemaps = [];
foreach ($this->configuration['config']['xmlSitemap']['sitemaps'] ?? [] as $sitemap => $config) {
if (class_exists($config['provider']) &&
is_subclass_of($config['provider'], XmlSitemapDataProviderInterface::class)) {
/** @var XmlSitemapDataProviderInterface $provider */
$provider = GeneralUtility::makeInstance(
$config['provider'],
$request,
$sitemap,
(array)$config['config']
);
$pages = $provider->getNumberOfPages();
for ($page = 0; $page < $pages; $page++) {
$sitemaps[] = [
'key' => $sitemap,
'page' => $page,
'lastMod' => $provider->getLastModified()
];
}
}
}
$this->view->assign('sitemaps', $sitemaps);
$this->view->setTemplate('Index');
return $this->view->render();
}
/**
* @param \Psr\Http\Message\ServerRequestInterface $request
* @param string $sitemap
* @return string
* @throws \TYPO3\CMS\Seo\XmlSitemap\Exception\InvalidConfigurationException
*/
protected function renderSitemap(ServerRequestInterface $request, string $sitemap): string
{
if (!empty($sitemapConfig = $this->configuration['config']['xmlSitemap']['sitemaps'][$sitemap])) {
if (class_exists($sitemapConfig['provider']) &&
is_subclass_of($sitemapConfig['provider'], XmlSitemapDataProviderInterface::class)) {
/** @var XmlSitemapDataProviderInterface $provider */
$provider = GeneralUtility::makeInstance(
$sitemapConfig['provider'],
$request,
$sitemap,
(array)$sitemapConfig['config']
);
$items = $provider->getItems();
$template = $sitemapConfig['config']['template'] ?: 'Sitemap';
$this->view->setTemplate($template);
$this->view->assign('items', $items);
return $this->view->render();
}
throw new InvalidConfigurationException('No valid provider set for ' . $sitemap, 1535578522);
}
throw new InvalidConfigurationException('No valid configuration found for sitemap ' . $sitemap, 1535578569);
}
/**
* @return \TYPO3\CMS\Fluid\View\StandaloneView
*/
protected function getStandaloneView(): StandaloneView
{
$view = GeneralUtility::makeInstance(StandaloneView::class);
$view->setTemplateRootPaths($this->configuration['view']['templateRootPaths']);
$view->setLayoutRootPaths($this->configuration['view']['layoutRootPaths']);
$view->setPartialRootPaths($this->configuration['view']['partialRootPaths']);
$view->setFormat('xml');
return $view;
}
/**
* Get the whole typoscript array
* @return array
*/
private function getConfiguration(): array
{
$configurationManager = GeneralUtility::makeInstance(ObjectManager::class)
->get(ConfigurationManagerInterface::class);
return $configurationManager->getConfiguration(
ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK,
'seo'
);
}
}
@@ -0,0 +1,245 @@
<?php
defined('TYPO3_MODE') or die();
$openGraphCropConfiguration = [
'config' => [
'cropVariants' => [
'default' => [
'disabled' => true,
],
'social' => [
'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_wizards.xlf:imwizard.crop_variant.social',
'coverAreas' => [],
'cropArea' => [
'x' => '0.0',
'y' => '0.0',
'width' => '1.0',
'height' => '1.0'
],
'allowedAspectRatios' => [
'1.91:1' => [
'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_wizards.xlf:imwizard.ratio.191_1',
'value' => 1.91 / 1
],
'NaN' => [
'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_wizards.xlf:imwizard.ratio.free',
'value' => 0.0
],
],
'selectedRatio' => '1.91:1',
],
],
],
];
$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',
],
'canonical' => [
'label' => 'LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.palettes.canonical',
'showitem' => 'canonical_link',
],
'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',
'onChange' => 'reload',
'label' => 'LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.no_index_formlabel',
'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_formlabel',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'items' => [
[
'0' => '',
'1' => '',
'invertStateDisplay' => true
]
]
]
],
'canonical_link' => [
'exclude' => true,
'label' => 'LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.canonical_link',
'displayCond' => 'FIELD:no_index:=:0',
'config' => [
'type' => 'input',
'renderType' => 'inputLink',
'size' => 50,
'max' => 1024,
'eval' => 'trim',
'fieldControl' => [
'linkPopup' => [
'options' => [
'title' => 'LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.canonical_link',
'blindLinkFields' => 'class,target,title',
'blindLinkOptions' => 'mail,folder,file'
],
],
],
'softref' => 'typolink'
]
],
'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'
]
],
'columns' => [
'crop' => $openGraphCropConfiguration
]
],
'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'
]
],
'columns' => [
'crop' => $openGraphCropConfiguration
]
],
'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--;;canonical,
--div--;LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.tabs.socialmedia,
--palette--;;opengraph,
--palette--;;twittercards',
(string)\TYPO3\CMS\Frontend\Page\PageRepository::DOKTYPE_DEFAULT,
'after:title'
);
@@ -0,0 +1,8 @@
<?php
defined('TYPO3_MODE') or die();
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addStaticFile(
'seo',
'Configuration/TypoScript/XmlSitemap',
'XML Sitemap'
);
@@ -0,0 +1,21 @@
plugin.tx_seo {
view {
# cat=plugin.tx_seo/file; type=string; label=Path to template root (FE)
templateRootPath = EXT:seo/Resources/Private/Templates/
# cat=plugin.tx_seo/file; type=string; label=Path to template partials (FE)
partialRootPath = EXT:seo/Resources/Private/Partials/
# cat=plugin.tx_seo/file; type=string; label=Path to template layouts (FE)
layoutRootPath = EXT:seo/Resources/Private/Layouts/
}
settings {
xmlSitemap {
sitemaps {
pages {
excludedDoktypes = 3, 4, 6, 7, 199, 254, 255
additionalWhere = no_index = 0 AND canonical_link = ''
}
}
}
}
}
@@ -0,0 +1,51 @@
seo_sitemap = PAGE
seo_sitemap {
typeNum = 1533906435
config {
cache_period = 900
disableAllHeaderCode = 1
admPanel = 0
removeDefaultJS = 1
removeDefaultCss = 1
removePageCss = 1
additionalHeaders {
10.header = Content-Type:application/xml;charset=utf-8
20.header = X-Robots-Tag:noindex
}
}
10 = USER
10.userFunc = TYPO3\CMS\Seo\XmlSitemap\XmlSitemapRenderer->render
}
plugin.tx_seo {
view {
templateRootPaths {
0 = EXT:seo/Resources/Private/Templates/XmlSitemap
10 = {$plugin.tx_seo.view.templateRootPath}
}
partialRootPaths {
0 = EXT:seo/Resources/Private/Partials/XmlSitemap
10 = {$plugin.tx_seo.view.partialRootPath}
}
layoutRootPaths {
0 = EXT:seo/Resources/Private/Layouts/XmlSitemap
10 = {$plugin.tx_seo.view.layoutRootPath}
}
}
config {
xmlSitemap {
sitemaps {
pages {
provider = TYPO3\CMS\Seo\XmlSitemap\PagesXmlSitemapDataProvider
config {
excludedDoktypes = {$plugin.tx_seo.settings.xmlSitemap.sitemaps.pages.excludedDoktypes}
additionalWhere = {$plugin.tx_seo.settings.xmlSitemap.sitemaps.pages.additionalWhere}
}
}
}
}
}
}
@@ -0,0 +1,190 @@
.. include:: ../Includes.txt
.. _configuration:
=============
Configuration
=============
Target group: **Developers, Integrators**
TypoScript Settings
===================
There are a couple of TypoScript settings that can influence the output regarding search engine optimization.
* `config.pageTitleFirst <https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Setup/Config/Index.html#pagetitlefirst>`__
* `config.pageTitleSeparator <https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Setup/Config/Index.html#pagetitleseparator>`__
* `config.pageTitleProviders <https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Setup/Config/Index.html#pagetitleproviders>`__
* `config.noPageTitle <https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Setup/Config/Index.html#nopagetitle>`__
Site configuration
==================
From version 9 of TYPO3, the configuration of sites is done with the Site Management module. As the settings for
your websites are important for SEO purposes as well, please make sure you check the following fields.
.. figure:: ../Images/site.png
:class: with-shadow
:alt: example site
Example site
To get more in depth information about the site handling please refer to the :ref:`t3coreapi:sitehandling` docs.
Domains
-------
Please ensure, that you have configured your sites so that they all have an entry point. This is used for
generating the canonical tags, for example.
.. warning::
Please be aware that for SEO purposes it is best practise to use a fully qualified domain (for example: https://www.example.com).
Therefor we don't support the SEO enhancements in TYPO3 without a full domain. It might work, but it is not officially
supported.
Language
--------
Ensure, that you setup the languages correctly. All languages should have the right information in the :guilabel:`Locale`
and :guilabel:`Language Tag` field. When set correctly, TYPO3 will automatically connect your page in the different languages
for search engines. This it to ensure that the search engine knows which page to show when someone is searching in a
specific language.
.. hint::
Even if you have only one language, make sure your :guilabel:`Locale` and :guilabel:`Language Tag` fields are set correctly.
Giving wrong information to search engines will not help you to rank higher.
See :ref:`t3coreapi:sitehandling-addingLanguages` for more details.
Error pages
-----------
Although TYPO3 will respond with a HTTP status code 404 (Not found) when a page is not found, it is best practise to
have a proper message telling the user that the page they requested is not available and to guide them to another
page or for example to a search function of your website.
See :ref:`t3coreapi:sitehandling-errorHandling` for more details.
robots.txt
----------
The robots.txt is a powerful tool and should be used with care. It will deny or allow search engines to access your pages.
By blocking access to your pages, search engines won't crawl these pages. You should make sure that this will not
prevent the search engines from finding important pages.
It is best practise to keep your robots.txt as clean as possible. An example of a minimal version of your robots.txt:
.. code-block:: php
# This space intentionally left blank. Only add entries when you know how powerful the robots.txt is.
User-agent: *
On :ref:`t3coreapi:sitehandling-staticRoutes` you can find more details on how to create a static route that will show
this information when visiting https://www.example.com/robots.txt.
When you want to disallow specific URLs, you can use the :ref:`index-page` option in the backend or set the robot HTTP
header `X-Robots-tag` manually.
Redirects
---------
Having correct redirects is a very important part of SEO. Especially the status code that is used for redirects is
really important. Please use the appropriate status code for your use case.
External Resources:
* See `Tutorial of redirect module in TYPO3 <https://www.toujou.de/en/service/tutorials/redirects/>`__ by toujou
* An `Overview which redirect to use in your situation <https://yoast.com/which-redirect/>`__ by Yoast
.. _config-tags:
Tags
====
.. _config-hreflang-tags:
Hreflang Tags
-------------
The generation of the :html:`<link rel="alternate" hreflang="" href="" />`
tags is done automatically if the page is available in other languages.
This feature should work correctly in almost all cases.
If you have a specific edge case, and you don't want TYPO3 to render those tags, you can disable rendering of the those tags completely.
You just have to put this line in the :file:`ext_localconf.php` of an extension and make sure your extension is loaded after EXT:seo.
.. code-block:: php
unset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Frontend\Page\PageGenerator']['generateMetaTags']['hreflang']);
.. _config-canonical-tag:
Canonical Tag
-------------
Just like the hreflang tags, the :html:`<link rel="canonical" href="" />` tag is also generated automatically.
If you have a specific edge case, and you don't want TYPO3 to render the tag, you can disable rendering completely.
You just have to put this line in the :file:`ext_localconf.php` of an extension and make sure your extension is loaded after EXT:seo.
.. code-block:: php
unset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Frontend\Page\PageGenerator']['generateMetaTags']['canonical']);
Working links
=============
Links in your website are quite important. You can use third party applications to check all your links, but you can
also use the core extension linkvalidator to ensure, all the links in your site are working as expected.
Please check the documentation of :ref:`linkvalidator:start` .
TypoScript examples
===================
This section will provide you examples on how to configure several behaviours in the frontend.
Setting fallbacks for og:image and twitter:image
------------------------------------------------
If you want to have a fallback og:image or twitter:image, you can use this little snippet.
.. code-block:: typoscript
page {
meta {
og:image.stdWrap.cObject = IMG_RESOURCE
og:image.stdWrap.cObject {
file = EXT:your_extension/Resources/Public/Backend/OgImage.svg
}
twitter:image.stdWrap.cObject = IMG_RESOURCE
twitter:image.stdWrap.cObject {
file = EXT:your_extension/Resources/Public/Backend/TwitterCardImage.svg
}
}
}
More information about the Meta Tag Api can be found on:
* PHP :ref:`t3coreapi:metatagapi`
* TypoScript :ref:`t3tsref:meta`
Setting defaults for the author on meta tags
--------------------------------------------
This example shows how to set a default author based on the TypoScript constant :ts:`{$my.default.author}`:
.. code-block:: typoscript
page {
meta {
author = {$my.default.author}
}
}
.. seealso::
:ref:`recommendations_field_description`
@@ -0,0 +1,15 @@
.. include:: ../Includes.txt
.. _developer:
================
Developer Corner
================
When you work with a pages-only approach you most likely don't need to use the meta tag APIs. If you have a special
edge-case or have a detail view to show a record, you need the APIs to render the corresponding meta information for your
page. You can refer to the documentation of the following APIs:
- The MetaTagApi (see :ref:`t3coreapi:metatagapi`) to define what metatags should be rendered on your page
- The PageTitleAPI (see :ref:`t3coreapi:pagetitle`) will give you the possibility to set the title of the page
@@ -0,0 +1,192 @@
.. include:: ../Includes.txt
.. _for-editors:
===========
For Editors
===========
Target group: **Editors**
Welcome to our small SEO introduction.
We will explain the basic fields the TYPO3 core provides and introduce the SEO tools this way.
All the following fields are part of the page record.
You can reach it by:
* Open the Web > Page module
* Use the pen in the top bar icon to edit the page record
An alternative way is to use the context menu.
* Open Web > Page module
* Right click the page you want to edit in the page tree
* Select the Edit action
General tab
===========
.. figure:: ../Images/tab-general.png
:class: with-shadow
:alt: General Tab in the TYPO3 Backend
General Tab in the TYPO3 Backend
Page Title
----------
The page title field is used for several purposes. From a SEO perspective it is mainly used to define the default
URL segment and as a fallback title if you don't set a specific SEO title. More information about the usage of the title
fields can be found at :ref:`title-for-search-engines`.
URL Segment
-----------
The URL segment is the part of the URL the user enters to access your page. Depending on your use case URLs have to
fulfill certain criteria.
By default the URL segment is based on the page tree and page title, but you can override this and set it yourself.
If you want to optimize your page for search engines, it is recommended to have at least the keyword you want to be
found on in your URL segment. Please make sure to check other best practises with your SEO specialist.
SEO Tab
=======
.. figure:: ../Images/tab-seo.png
:class: with-shadow
:alt: SEO Tab in the TYPO3 Backend
SEO Tab in the TYPO3 Backend
.. _title-for-search-engines:
Title for search engines
------------------------
The :guilabel:`Title for search engines` field is used in the `<title>...</title>` tag of your web page. This title is used in the
search results of search engines. There are some best practices for the value of this field.
* Use a speaking title, which targets your audience
* If possible, use the keywords you want to be found on in the title
* Please make sure your title fits in a Google Snippet. You can use 3rd party extensions to help you with this.
When no value for the :guilabel:`Title for search engines` field is defined, it will have a fallback to the field
:guilabel:`Page title`. It will show the page title in the search results in that case.
The title can have the name of the site as a prefix or suffix. The behaviour of this can be defined by an integrator.
By default the title will have the name of the site as a suffix, separated by a dash.
.. figure:: ../Images/google-preview.png
:class: with-shadow
:alt: Google result preview
Google result preview
.. _index-page:
Index this page
---------------
Defines whether the page should be indexed by a search engine or not.
Follow this page
----------------
Defines whether search engines should follow the links on this page.
Canonical link
--------------
You want to avoid duplicate content because your ranking can suffer from this. If you have duplicate content, you have to
link to the origin of the content. This can be an internal page or an external page. You can use the default link wizard
of TYPO3 to select the destination. If you don't set a canonical link yourself, TYPO3 will generate a link to the current
page.
.. hint::
Avoid duplicate content warnings by supplying the original source of this content.
Metadata Tab
============
.. figure:: ../Images/tab-meta-data.png
:class: with-shadow
:alt: Metadata Tab in the TYPO3 Backend
Metadata tab in the TYPO3 Backend
.. _description:
Description
-----------
The description field is important for SEO purposes. This field is used to instruct search engines to use
this text to show as a teaser of your page in the search result snippets. With this description, you can
give the user a clear overview of the topic of this page.
.. figure:: ../Images/google-preview.png
:class: with-shadow
:alt: Google result preview
Google result preview
.. hint::
It is best practises to at least use the keywords you want to be found on in this description. Make sure the
description is not too long, so it fits in the search result snippets.
Social media
============
.. figure:: ../Images/tab-social-media.png
:class: with-shadow
:alt: Social Media Tab in the TYPO3 Backend
Social Media Tab in the TYPO3 Backend
The social media tab contains information which is used to enrich the snippet that is shown when visitors share your URLs
in social networks, modern browsers etc.
TYPO3 offers to have different content for OpenGraph (used for example by Facebook and LinkedIn) and Twitter Cards
(used on Twitter of course). Both implementations allow you to set a title, a description and to provide an image. When this
information is not set manually, the social network will decide itself what to show. Most probably it will use the
information from :ref:`title-for-search-engines` and :ref:`description`.
.. figure:: ../Images/social-preview-no-image.png
:class: with-shadow
:alt: Facebook preview without image
Facebook preview without image
Title
-----
This title can be specified to be shown as the title of the snippet preview when your page is shared on the social
network. You only have to fill this field when you want a different title as in the search results.
Description
-----------
Used to adjust the description of the preview snippet when your page is shared at a social network. You only have to
fill this field when you want a different description as the description in the search results.
Image
-----
You can specify the image that should be shown when your page is shared on a social network. Please be aware that the
different social networks need specific dimensions for an image. TYPO3 ships with the most used dimension. If you want
to have more dimensions, please let your integrator add more dimensions.
.. hint::
It is best practise to add an image for social networks so your result will stand out and will be easily recognizable.
.. figure:: ../Images/social-preview-image.png
:class: with-shadow
:alt: Facebook preview with image
Facebook preview with image
@@ -0,0 +1,51 @@
.. include:: ../Includes.txt
.. _recommendations:
=======================
General Recommendations
=======================
.. _recommendations_extensions:
Recommendations for additional seo extensions
=============================================
The TYPO3 core ships the whole API and the needed fields to have all necessary technical SEO.
Besides that, there are lots of tools you can use to optimize your rankings.
If you install additional SEO extensions in TYPO3 make sure you check the following recommendations:
* The extension **should** stick to the core fields where possible
* The extension **should** stick to the core behaviour where possible
* The extension **could** extend TCA with additional helpers for your editors, like:
* Readability checks
* Keyword and content checks
* Previews
* Page speed insights
Some of these tools might need external services, others can work completely on-premise.
.. _recommendations_field_description:
Recommendations for the description field
=========================================
.. danger::
Current state of SEO research states that the description should be either lovely crafted by hand,
of left our completely. (end of 2019)
If you are unsure contact an expert.
.. youtube:: W4gr88oHb-k
* Duplicate meta descriptions should only be used under specific circumstances
* Duplicate meta descriptions should only be used on a few pages
* Leave the description empty if you do not can provide one
* Provide an engaging explanation of your page, to ensure people will be motivated to visit your site
* If SEO is not your thing, get professional help
Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 549 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

@@ -0,0 +1,23 @@
.. ==================================================
.. FOR YOUR INFORMATION
.. --------------------------------------------------
.. -*- coding: utf-8 -*- with BOM.
.. This is 'Includes.txt'. It is included at the very top of each and
every ReST source file in this documentation project (= manual).
.. ==================================================
.. DEFINE SOME TEXT ROLES
.. --------------------------------------------------
.. role:: typoscript(code)
.. role:: ts(typoscript)
:class: typoscript
.. role:: php(code)
.. role:: html(code)
.. highlight:: php
+70
View File
@@ -0,0 +1,70 @@
.. every .rst file should include Includes.txt
.. use correct path!
.. include:: Includes.txt
.. Every manual should have a start label for cross-referencing to
.. start page. Do not remove this!
.. _start:
==================
SEO Core extension
==================
:Extension key:
seo
:Version:
|release|
:Language:
en
:Description:
Manual for our SEO extension, which adds basic SEO features to TYPO3.
:Keywords:
seo, google, meta tags, search engine optimization, sitemap
:Copyright:
2018-2021
:Author:
TYPO3 CMS Core Development Team
:License:
Open Content License available from `www.opencontent.org/opl.shtml
<http://www.opencontent.org/opl.shtml>`_
:Rendered:
|today|
The content of this document is related to TYPO3,
a GNU/GPL CMS/Framework available from `www.typo3.org
<http://www.typo3.org/>`_
**TYPO3**
The content of this document is related to TYPO3 CMS,
a GNU/GPL CMS/Framework available from `typo3.org
<https://typo3.org/>`_ .
**For Contributors**
You are welcome to help improve this guide. Just click on "Edit me on GitHub"
on the top right to submit your change request.
**Table of Contents**
.. toctree::
:maxdepth: 3
Introduction/Index
Editor/Index
Installation/Index
Configuration/Index
Developer/Index
GeneralRecommendations/Index
@@ -0,0 +1,56 @@
.. include:: ../Includes.txt
.. _installation:
============
Installation
============
Target group: **Administrators**
This extension is part of the TYPO3 core.
Installation with composer
==========================
Check whether you are already using the extension with
.. code-block:: bash
composer show | grep seo
This should either give you no result or something similar to:
.. code-block:: bash
typo3/cms-seo v9.5.10 SEO features for TYPO3.
If it is not yet installed, use the ``composer require`` command to install the extension
.. code-block:: bash
composer require typo3/cms-seo ^9.5
The given version depends on the version of the TYPO3 core you are using.
Now head over to the extension manager and activate the extension.
.. figure:: ../Images/em.png
:class: with-shadow
:alt: Extension manager with filtered seo ext
Extension manager with filtered seo ext
Installation without composer
=============================
In an installation without composer, the code of EXT:seo is already shipped. You just have to activate the extension.
Head over to the extension manager and activate the extension.
.. figure:: ../Images/em.png
:class: with-shadow
:alt: Extension manager with filtered seo ext
Extension manager with filtered seo ext
@@ -0,0 +1,85 @@
.. include:: ../Includes.txt
.. _introduction:
============
Introduction
============
What does it do?
================
The goal of the core extension "seo" is to make sure basic technical Search Engine Optimization (SEO) is done right
out of the box. Specific fields for SEO purposes are added, as well as the rendering of HTML meta tags in the frontend.
There are several things you can do to optimize your site for search engines.
Some tasks are only solvable with good human writers, others are technical tasks, which just need to be implemented.
The goal of the extension seo is to help you with the following parts of your seo journey.
* Give editors all the fields needed to set the necessary meta tags
* Provide the rendering of meta tags in the frontend for SEO related information, to avoid reimplementing it again and again
* Provide stable APIs for developers to give them the chance to change the behaviour when needed in edge cases
This manual will not cover all the developer information, as these are covered in the TYPO3 API documentation.
.. _screenshots:
Screenshots
===========
.. figure:: ../Images/tab-general.png
:class: with-shadow
:alt: General Tab in the TYPO3 Backend
General Tab in the TYPO3 Backend
.. figure:: ../Images/tab-seo.png
:class: with-shadow
:alt: SEO Tab in the TYPO3 Backend
SEO Tab in the TYPO3 Backend
.. figure:: ../Images/tab-meta-data.png
:class: with-shadow
:alt: Metadata Tab in the TYPO3 Backend
Metadata Tab in the TYPO3 Backend
Hreflang Tags
=============
"hreflang" tags are added automatically for multi language websites based on the one-tree principle. The links are
based on the Site configuration and depending on translations of a page.
The :html:`x-default` href is the link of the current page in the default language.
The value of :html:`hreflang` is set for each language in the :guilabel:`Sites module` (see :ref:`t3coreapi:sitehandling-addingLanguages`)
Canonical Tags
==============
TYPO3 provides built-in support for the canonical tag.
If the core extension "seo" is installed, it will automatically add the canonical link to the page.
The canonical link is basically the same absolute link as the link to the current hreflang and is meant
to indicate where the original source of the content is. It is a tool to prevent duplicate content
penalties.
In the page properties, the canonical link can be overwritten per language. The link wizard offers all
possibilities including external links and link handler configurations.
If no canonical link is given manually, TYPO3 will generate a canonicalized URL to the current page in the current
language.
.. warning::
If you have other SEO extensions installed that generate canonical links, you have to make sure only one creates it.
If both core and an extension are generating a canonical link, it will result in 2 canonical links which might
cause confusion for search engines.
XML Sitemap
===========
See :ref:`t3coreapi:xmlsitemap` for the documentation how to configure your XML sitemaps.
@@ -0,0 +1,43 @@
[general]
; endless list of all of the general simple settings
; you can use in 'conf.py'
project = Search engine optimization (seo)
version = latest (10-dev)
release = latest (10-dev)
t3author = TYPO3 Core Team + Contributors, TYPO3 Documentation Team
copyright = 2018-2021
description = This documentation is about TYPO3's system extension
"seo" which provides provides search engine optimizations
[html_theme_options]
; for theme t3SphinxThemeRtd
# Add 'Edit me on Github' button
github_branch = master
github_repository = typo3/typo3
path_to_documentation_dir = typo3/sysext/seo/Documentation
# show as related links
project_contact =
project_discussions = http://lists.typo3.org/cgi-bin/mailman/listinfo/typo3-project-content-rendering
project_home = https://forge.typo3.org/projects/typo3cms-core
project_issues = https://forge.typo3.org/projects/typo3cms-core/issues
project_repository = https://github.com/typo3/typo3
[intersphinx_mapping]
# These are common mappings. Uncomment only what you really use!
# see https://docs.typo3.org/m/typo3/docs-how-to-document/master/en-us/GeneralConventions/DirectoryFilenames.html#intersphinx-mapping
h2document = https://docs.typo3.org/m/typo3/docs-how-to-document/master/en-us/
t3coreapi = https://docs.typo3.org/m/typo3/reference-coreapi/9.5/en-us/
t3install = https://docs.typo3.org/m/typo3/guide-installation/9.5/en-us/
t3tsref = https://docs.typo3.org/m/typo3/reference-typoscript/9.5/en-us/
linkvalidator = https://docs.typo3.org/c/typo3/cms-linkvalidator/9.5/en-us/
+345
View File
@@ -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.
@@ -0,0 +1,66 @@
<?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="EXT:seo/Resources/Private/Language/locallang_tca.xlf" date="2018-08-09T16:22:32Z"
product-name="seo">
<header/>
<body>
<trans-unit id="pages.tabs.seo" resname="pages.tabs.seo">
<source>SEO</source>
</trans-unit>
<trans-unit id="pages.palettes.seo" resname="pages.palettes.seo">
<source>General SEO settings</source>
</trans-unit>
<trans-unit id="pages.seo_title" resname="pages.seo_title">
<source>Title for search engines</source>
</trans-unit>
<trans-unit id="pages.palettes.robots" resname="pages.palettes.robots">
<source>Robot instructions</source>
</trans-unit>
<trans-unit id="pages.no_index" resname="pages.no_index">
<source>No index</source>
</trans-unit>
<trans-unit id="pages.no_index_formlabel" resname="pages.no_index_formlabel">
<source>Index this page</source>
</trans-unit>
<trans-unit id="pages.no_follow" resname="pages.no_follow">
<source>No follow</source>
</trans-unit>
<trans-unit id="pages.no_follow_formlabel" resname="pages.no_follow_formlabel">
<source>Follow this page</source>
</trans-unit>
<trans-unit id="pages.palettes.opengraph" resname="pages.palettes.opengraph">
<source>Open Graph (Facebook)</source>
</trans-unit>
<trans-unit id="pages.og_title" resname="pages.og_title">
<source>Title</source>
</trans-unit>
<trans-unit id="pages.og_description" resname="pages.og_description">
<source>Description</source>
</trans-unit>
<trans-unit id="pages.og_image" resname="pages.og_image">
<source>Image</source>
</trans-unit>
<trans-unit id="pages.palettes.twittercards" resname="pages.palettes.twittercards">
<source>Twitter Cards</source>
</trans-unit>
<trans-unit id="pages.twitter_title" resname="pages.twitter_title">
<source>Title</source>
</trans-unit>
<trans-unit id="pages.twitter_description" resname="pages.twitter_description">
<source>Description</source>
</trans-unit>
<trans-unit id="pages.twitter_image" resname="pages.twitter_image">
<source>Image</source>
</trans-unit>
<trans-unit id="pages.palettes.canonical" resname="pages.palettes.canonical">
<source>Canonical</source>
</trans-unit>
<trans-unit id="pages.canonical_link" resname="pages.canonical_link">
<source>Canonical link</source>
</trans-unit>
<trans-unit id="pages.tabs.socialmedia" resname="pages.tabs.socialmedia">
<source>Social media</source>
</trans-unit>
</body>
</file>
</xliff>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="{xslFile}"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<f:for each="{sitemaps}" as="sitemap">
<sitemap>
<f:spaceless>
<f:if condition="{sitemap.page}">
<f:then><loc><f:uri.page pageType="{type}" additionalParams="{sitemap: sitemap.key, page: sitemap.page}" absolute="true" noCacheHash="false" /></loc></f:then>
<f:else><loc><f:uri.page pageType="{type}" additionalParams="{sitemap: sitemap.key}" absolute="true" noCacheHash="false" /></loc></f:else>
</f:if>
</f:spaceless>
<lastmod>{sitemap.lastMod -> f:format.date(format: 'c')}</lastmod>
</sitemap>
</f:for>
</sitemapindex>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="{xslFile}"?>
<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<f:for each="{items}" as="item">
<f:if condition="{item.loc}">
<url>
<loc>{item.loc}</loc>
<lastmod>{item.lastMod -> f:format.date(format: 'c')}</lastmod>
</url>
</f:if>
</f:for>
</urlset>
@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:html="http://www.w3.org/TR/REC-html40"
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
xmlns:sitemap="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>TYPO3 XML Sitemap</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css">
body {
font-family: Helvetica, Arial, sans-serif;
font-size: 13px;
color: #545353;
}
table {
border: none;
border-collapse: collapse;
}
#sitemap tr:nth-child(odd) td {
background-color: #eee !important;
}
#sitemap tbody tr:hover td {
background-color: #ccc;
}
#sitemap tbody tr:hover td, #sitemap tbody tr:hover td a {
color: #000;
}
#content {
margin: 0 auto;
width: 1000px;
}
.expl {
margin: 18px 3px;
line-height: 1.2em;
}
a {
color: #000;
text-decoration: none;
}
a:visited {
color: #777;
}
a:hover {
text-decoration: underline;
}
td {
font-size:11px;
}
th {
text-align:left;
padding-right:30px;
font-size:11px;
}
thead th {
border-bottom: 1px solid #000;
}
</style>
</head>
<body>
<div id="content">
<h1>TYPO3 XML Sitemap</h1>
<xsl:if test="count(sitemap:sitemapindex/sitemap:sitemap) &gt; 0">
<p class="expl">
This XML Sitemap Index file contains <xsl:value-of select="count(sitemap:sitemapindex/sitemap:sitemap)"/> sitemaps.
</p>
<table id="sitemap" cellpadding="3" width="100%">
<thead>
<tr>
<th>Sitemap</th>
<th>Last modified</th>
</tr>
</thead>
<tbody>
<xsl:for-each select="sitemap:sitemapindex/sitemap:sitemap">
<xsl:variable name="sitemapURL">
<xsl:value-of select="sitemap:loc"/>
</xsl:variable>
<tr>
<td>
<a href="{$sitemapURL}"><xsl:value-of select="sitemap:loc"/></a>
</td>
<td>
<a href="{$sitemapURL}"><xsl:value-of select="sitemap:lastmod"/></a>
</td>
</tr>
</xsl:for-each>
</tbody>
</table>
</xsl:if>
<xsl:if test="count(sitemap:sitemapindex/sitemap:sitemap) &lt; 1">
<p class="expl">
This XML Sitemap contains <xsl:value-of select="count(sitemap:urlset/sitemap:url)"/> URLs.
</p>
<table id="sitemap" cellpadding="3" width="100%">
<thead>
<tr>
<th width="80%">URL</th>
<th title="Last Modification Time" width="20%">Last Mod.</th>
</tr>
</thead>
<tbody>
<xsl:variable name="lower" select="'abcdefghijklmnopqrstuvwxyz'"/>
<xsl:variable name="upper" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
<xsl:for-each select="sitemap:urlset/sitemap:url">
<tr>
<td>
<xsl:variable name="itemURL">
<xsl:value-of select="sitemap:loc"/>
</xsl:variable>
<a href="{$itemURL}">
<xsl:value-of select="sitemap:loc"/>
</a>
</td>
<td>
<xsl:value-of select="concat(substring(sitemap:lastmod,0,11),concat(' ', substring(sitemap:lastmod,12,5)),concat(' ', substring(sitemap:lastmod,20,6)))"/>
</td>
</tr>
</xsl:for-each>
</tbody>
</table>
</xsl:if>
</div>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

+41
View File
@@ -0,0 +1,41 @@
{
"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.5.31",
"typo3/cms-frontend": "9.5.31",
"typo3/cms-extbase": "9.5.31"
},
"conflict": {
"typo3/cms": "*"
},
"extra": {
"typo3/cms": {
"extension-key": "seo",
"Package": {
"partOfFactoryDefault": true
}
}
},
"autoload": {
"psr-4": {
"TYPO3\\CMS\\Seo\\": "Classes/"
}
},
"autoload-dev": {
"psr-4": {
"TYPO3\\CMS\\Seo\\Tests\\": "Tests/"
}
}
}
+21
View File
@@ -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.5.31',
'constraints' => [
'depends' => [
'typo3' => '9.5.31'
],
'conflicts' => [],
'suggests' => [],
],
];
+31
View File
@@ -0,0 +1,31 @@
<?php
defined('TYPO3_MODE') or die();
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Frontend\Page\PageGenerator']['generateMetaTags']['metatag'] =
\TYPO3\CMS\Seo\MetaTag\MetaTagGenerator::class . '->generate';
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Frontend\Page\PageGenerator']['generateMetaTags']['hreflang'] =
\TYPO3\CMS\Seo\HrefLang\HrefLangGenerator::class . '->generate';
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Frontend\Page\PageGenerator']['generateMetaTags']['canonical'] =
\TYPO3\CMS\Seo\Canonical\CanonicalGenerator::class . '->generate';
$metaTagManagerRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\MetaTag\MetaTagManagerRegistry::class);
$metaTagManagerRegistry->registerManager(
'opengraph',
\TYPO3\CMS\Seo\MetaTag\OpenGraphMetaTagManager::class
);
$metaTagManagerRegistry->registerManager(
'twitter',
\TYPO3\CMS\Seo\MetaTag\TwitterCardMetaTagManager::class
);
unset($metaTagManagerRegistry);
// Add module configuration
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTypoScriptSetup(trim('
config.pageTitleProviders {
seo {
provider = TYPO3\CMS\Seo\PageTitle\SeoTitlePageTitleProvider
before = record
after = altPageTitle
}
}
'));
+15
View File
@@ -0,0 +1,15 @@
#
# 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,
canonical_link varchar(2048) DEFAULT '' NOT NULL,
);