From 33d7d143b872e5428b8a7932a61227e0f3793b7a Mon Sep 17 00:00:00 2001 From: Julian Wundrak Date: Mon, 14 May 2018 11:11:44 +0200 Subject: [PATCH] [Feature] Add alternate langhref links to sitemap --- .../Generator/AbstractSitemapGenerator.php | 67 +++++++++++++ Classes/Generator/PagesSitemapGenerator.php | 95 ++++++++++++++++--- .../AbstractExtendedSitemapRenderer.php | 78 +++++++++++++++ Classes/Renderers/StandardSitemapRenderer.php | 13 ++- 4 files changed, 235 insertions(+), 18 deletions(-) create mode 100644 Classes/Renderers/AbstractExtendedSitemapRenderer.php diff --git a/Classes/Generator/AbstractSitemapGenerator.php b/Classes/Generator/AbstractSitemapGenerator.php index b4f8ae8..e543a68 100644 --- a/Classes/Generator/AbstractSitemapGenerator.php +++ b/Classes/Generator/AbstractSitemapGenerator.php @@ -68,6 +68,11 @@ abstract class AbstractSitemapGenerator { */ protected $rendererClass = 'DmitryDulepov\\DdGooglesitemap\\Renderers\\StandardSitemapRenderer'; + /** + * @var array of sys languages [uid => languageCode] + */ + protected $sysLanguages = null; + /** * Initializes the instance of this class. This constructir sets starting * point for the sitemap to the current page id @@ -117,4 +122,66 @@ protected function createRenderer() { * @return void */ abstract protected function generateSitemapContent(); + + /** + * @return array + */ + protected function getAlternateSysLanguageIds() { + if ($this->sysLanguages === null) { + $this->sysLanguages = array(); + + /** @var \TYPO3\CMS\Frontend\Page\PageRepository $pageRepository */ + $pageRepository = $GLOBALS['TSFE']->sys_page; + $sys_languages = $pageRepository->getRecordsByField('sys_language', 'hidden', 0); + + // empty table + if (!is_array($sys_languages)) { + $sys_languages = array(); + } + + // default language not in table, so add manually + array_unshift($sys_languages, array('language_isocode' => 'x-default', 'uid' => 0)); + + foreach ($sys_languages as $language) { + // use iso code as default + $setLocale = $language['language_isocode']; + + // check typoscript config + /** @var \TYPO3\CMS\Core\TypoScript\TemplateService $templateService */ + $templateService = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\TypoScript\\TemplateService'); + $templateService->matchAlternative[] = '[globalVar = GP:L = ' . $language['uid'] . ']'; + $templateService->init(); + $templateService->start($GLOBALS['TSFE']->rootLine); + // apply language condition + $templateService->generateConfig(); + + // allow custom modifications. We not change TSFE->config array and this could be important. + // Add possibility, that extension could add their modification on setup-array + if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['dd_google_stitemap/abstract_sitemap_generator']['alternateSysLanguageIdsPostProc'])) { + $params = array('templateService' => $templateService); + foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['dd_google_stitemap/abstract_sitemap_generator']['alternateSysLanguageIdsPostProc'] as $funcRef) { + GeneralUtility::callUserFunction($funcRef, $params, $this); + } + } + + if (isset($templateService->setup['config.']['locale_all'])) { + // could contain charset + list($locale_all) = explode('.', $templateService->setup['config.']['locale_all'], 2); + + if (\strpos($locale_all, '_') === 2) { + list($lang, $region) = explode('_', $locale_all, 3); + $setLocale = strtolower($lang . '-' . $region); + } else { + $setLocale = $locale_all; + } + } elseif (isset($templateService->setup['config.']['language'])) { + $setLocale = $templateService->setup['config.']['language']; + } + + $this->sysLanguages[(int)$language['uid']] = $setLocale; + } + } + + return $this->sysLanguages; + } } diff --git a/Classes/Generator/PagesSitemapGenerator.php b/Classes/Generator/PagesSitemapGenerator.php index 05c1779..08edf1e 100644 --- a/Classes/Generator/PagesSitemapGenerator.php +++ b/Classes/Generator/PagesSitemapGenerator.php @@ -70,7 +70,7 @@ class PagesSitemapGenerator extends AbstractSitemapGenerator { protected $hookObjects; /** - * Initializes the instance of this class. This constructir sets starting + * Initializes the instance of this class. This constructor sets starting * point for the sitemap to the current page id */ public function __construct() { @@ -154,7 +154,7 @@ protected function generateSitemapContent() { // Notice: no sorting (for speed)! $GLOBALS['TSFE']->sys_page->sys_language_uid = $GLOBALS['TSFE']->config['config']['sys_language_uid']; $morePages = $GLOBALS['TSFE']->sys_page->getMenu($pageInfo['uid'], '*', '', '', false); - $this->removeNonTranslatedPages($morePages); + $morePages = $this->filterNonTranslatedPages($morePages); $this->pageList = array_merge($this->pageList, array_values($morePages)); unset($morePages); } @@ -178,21 +178,49 @@ protected function getLastMod(array $pageInfo) { /** * Exclude pages from given list * + * @deprecated use filterNonTranslatedPages + * * @param array $pages * @return void */ protected function removeNonTranslatedPages(array &$pages) { - $language = (int)$GLOBALS['TSFE']->config['config']['sys_language_uid']; - foreach ($pages as $pageUid => $page) { - // Hide page in default language - if ($language === 0 && GeneralUtility::hideIfDefaultLanguage($page['l18n_cfg'])) { - unset($pages[$pageUid]); + $pages = $this->filterNonTranslatedPages($pages); + } + + /** + * Get only translated pages + * + * @param array $pages + * @param int|null $languageUid + * @return array + */ + protected function filterNonTranslatedPages(array $pages, $languageUid = null) { + if($languageUid === null) { + $languageUid = (int)$GLOBALS['TSFE']->config['config']['sys_language_uid']; + } + + $filterFunction = function ($page) use($languageUid) { + if ($languageUid === 0 && GeneralUtility::hideIfDefaultLanguage($page['l18n_cfg'])) { + return false; } - elseif ($language !== 0 && !isset($page['_PAGES_OVERLAY']) && GeneralUtility::hideIfNotTranslated($page['l18n_cfg'])) { - // Hide page if no translation is set - unset($pages[$pageUid]); + + if ($languageUid !== 0 && !isset($page['_PAGES_OVERLAY']) && GeneralUtility::hideIfNotTranslated($page['l18n_cfg'])) { + return false; } + + return true; + }; + + // requested language === current language + if ($languageUid === (int)$GLOBALS['TSFE']->config['config']['sys_language_uid']) { + return array_filter($pages, $filterFunction); } + + // other language: load overlay information + $overlayPages = $GLOBALS['TSFE']->sys_page->getPagesOverlay($pages, $languageUid); + $overlayPages = array_filter($overlayPages, $filterFunction); + + return array_intersect_key($pages, $overlayPages); } /** @@ -215,7 +243,10 @@ protected function writeSingleUrl(array $pageInfo) { if ($this->shouldIncludePageInSitemap($pageInfo) && ($url = $this->getPageLink($pageInfo['uid']))) { echo $this->renderer->renderEntry($url, $pageInfo['title'], $this->getLastMod($pageInfo), - $this->getChangeFrequency($pageInfo), '', $pageInfo['tx_ddgooglesitemap_priority']); + $this->getChangeFrequency($pageInfo), '', $pageInfo['tx_ddgooglesitemap_priority'], + array( + 'hreflangs' => $this->getAlternateLinks($pageInfo, $url) + )); // Post-process current page and possibly append data // @see http://forge.typo3.org/issues/45637 @@ -280,18 +311,52 @@ protected function calculateChangeFrequency(array $pageInfo) { ($average <= 14*24*60*60 ? 'weekly' : 'monthly')))); } + /** + * @param array $pageInfo + * @param string $targetUrl + * @return array + */ + protected function getAlternateLinks($pageInfo, $targetUrl) { + $links = array(); + $pageUid = $pageInfo['uid']; + $alternativeLanguages = $this->getAlternateSysLanguageIds(); + if (!empty($alternativeLanguages)) { + foreach ($alternativeLanguages as $languageUid => $locale) { + $translatedPage = $this->filterNonTranslatedPages(array($pageInfo), $languageUid); + if(empty($translatedPage)) { + continue; + } + + // can generate url and it different to target url + if (($url = $this->getPageLink($pageUid, $languageUid))) { + $links[$locale] = $url; + } + } + } + + return $links; + } + /** * Creates a link to a single page * - * @param array $pageId Page ID + * @param int $pageId Page ID + * @param int $languageId Language Id * @return string Full URL of the page including host name (escaped) */ - protected function getPageLink($pageId) { + protected function getPageLink($pageId, $languageId = null) { $conf = array( 'parameter' => $pageId, 'returnLast' => 'url', + 'forceAbsoluteUrl' => 1 ); - $link = htmlspecialchars($this->cObj->typoLink('', $conf)); - return GeneralUtility::locationHeaderUrl($link); + + if ($languageId !== null) { + $conf['additionalParams'] = '&L=' . $languageId; + // cHash is important for e.g. realUrl + $conf['useCacheHash'] = true; + } + + return htmlspecialchars($this->cObj->typoLink('', $conf)); } } diff --git a/Classes/Renderers/AbstractExtendedSitemapRenderer.php b/Classes/Renderers/AbstractExtendedSitemapRenderer.php new file mode 100644 index 0000000..be76862 --- /dev/null +++ b/Classes/Renderers/AbstractExtendedSitemapRenderer.php @@ -0,0 +1,78 @@ + +* All rights reserved +* +* This script is part of the TYPO3 project. The TYPO3 project 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. +* +* The GNU General Public License can be found at +* http://www.gnu.org/copyleft/gpl.html. +* +* This script 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. +* +* This copyright notice MUST APPEAR in all copies of the script! +***************************************************************/ + +namespace DmitryDulepov\DdGooglesitemap\Renderers; + +use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; + +/** + * This class contains an abstract renderer for sitemaps. + * + * NOTE: interface is internal and it is not stable. Any XCLASS is not guarantied + * to work! + * + * @author Dmitry Dulepov + * @package TYPO3 + * @subpackage tx_ddgooglesitemap + */ +abstract class AbstractExtendedSitemapRenderer extends AbstractEntity { + + /** + * Renders one single entry according to the format of this sitemap. + * + * @param string $url URL of the entry + * @param string $title Title of the entry + * @param int $lastModification News publication time (Unix timestamp) + * @param string $changeFrequency Unused for news + * @param string $keywords Keywords for this entry + * @param mixed $priority Priority (numeric, 1-10, if passed) + * @param array|null $additionalParams Additional data + * @return string Generated entry content + * @see tx_ddgooglesitemap_abstract_renderer::renderEntry() + */ + abstract public function renderEntry($url, $title, $lastModification = 0, $changeFrequency = '', $keywords = '', $priority = '', $additionalParams = null); + + /** + * Renders alternative alternate href links + * + * @param array $linkItems + * @return string + */ + public function renderAlternateHrefLinks($linkItems) { + $hrefLangEntries = array(); + if (!empty($linkItems)) { + foreach($linkItems as $languageCode => $targetUrl) { + $hrefLangEntries[] = ''; + } + } + + return join('', $hrefLangEntries); + } +} + +/** @noinspection PhpUndefinedVariableInspection */ +if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/dd_googlesitemap/renderers/class.tx_ddgooglesitemap_abstract_extended_renderer.php']) { + /** @noinspection PhpIncludeInspection */ + include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/dd_googlesitemap/renderers/class.tx_ddgooglesitemap_abstract_extended_renderer.php']); +} diff --git a/Classes/Renderers/StandardSitemapRenderer.php b/Classes/Renderers/StandardSitemapRenderer.php index 2895db0..966795a 100644 --- a/Classes/Renderers/StandardSitemapRenderer.php +++ b/Classes/Renderers/StandardSitemapRenderer.php @@ -31,7 +31,7 @@ * @package TYPO3 * @subpackage tx_ddgooglesitemap */ -class StandardSitemapRenderer extends AbstractSitemapRenderer { +class StandardSitemapRenderer extends AbstractExtendedSitemapRenderer { /** * Creates end tags for this sitemap. @@ -51,7 +51,8 @@ public function getEndTags() { */ public function getStartTags() { return '' . chr(10) . - '' . chr(10); + '' . chr(10); } /** @@ -63,10 +64,11 @@ public function getStartTags() { * @param string $changeFrequency Unused for news * @param string $keywords Keywords for this entry * @param mixed $priority Priority (numeric, 1-10, if passed) + * @param array|null $additionalParams * @return string Generated entry content * @see tx_ddgooglesitemap_abstract_renderer::renderEntry() */ - public function renderEntry($url, $title, $lastModification = 0, $changeFrequency = '', $keywords = '', $priority = '') { + public function renderEntry($url, $title, $lastModification = 0, $changeFrequency = '', $keywords = '', $priority = '', $additionalParams = null) { $content = ''; $content .= '' . $url . ''; if ($lastModification) { @@ -78,6 +80,11 @@ public function renderEntry($url, $title, $lastModification = 0, $changeFrequenc if ($priority != '') { $content .= '' . sprintf('%0.1F', $priority/10) . ''; } + + if (isset($additionalParams['hreflangs'])) { + $content .= $this->renderAlternateHrefLinks($additionalParams['hreflangs']); + } + $content .= ''; return $content;