Skip to content

Commit

Permalink
[Feature] Add alternate langhref links to sitemap
Browse files Browse the repository at this point in the history
  • Loading branch information
jwundrak committed May 22, 2018
1 parent 2bf69a2 commit 96dfdd7
Show file tree
Hide file tree
Showing 8 changed files with 438 additions and 19 deletions.
10 changes: 10 additions & 0 deletions Classes/Generator/AbstractSitemapGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,14 @@ protected function createRenderer() {
* @return void
*/
abstract protected function generateSitemapContent();

/**
* @return array
* @throws \InvalidArgumentException
*/
protected function getAlternateSysLanguageIds() {
/** @var \DmitryDulepov\DdGooglesitemap\Helper\SysLanguageHelper $instance */
$instance = GeneralUtility::makeInstance('DmitryDulepov\\DdGooglesitemap\\Helper\\SysLanguageHelper');
return $instance->getSysLanguages();
}
}
94 changes: 79 additions & 15 deletions Classes/Generator/PagesSitemapGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
}

/**
Expand All @@ -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
Expand Down Expand Up @@ -280,18 +311,51 @@ protected function calculateChangeFrequency(array $pageInfo) {
($average <= 14*24*60*60 ? 'weekly' : 'monthly'))));
}

/**
* @param array $pageInfo
* @return array
*/
protected function getAlternateLinks($pageInfo) {
$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));
}
}
139 changes: 139 additions & 0 deletions Classes/Helper/SysLanguageHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?php
/***************************************************************
* Copyright notice
*
* (c) 2008-2014 Dmitry Dulepov <dmitry.dulepov@gmail.com>
* 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\Helper;

use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;

/**
* Class SysLanguageHelper
*
* @package DmitryDulepov\DdGooglesitemap\Helper
*/
class SysLanguageHelper implements SingletonInterface {

/**
* @var array of sys languages [uid => languageCode]
*/
protected $sysLanguages = null;

/**
* @var \TYPO3\CMS\Extbase\Object\ObjectManager
* @inject
*/
protected $objectManager;

/**
* @return \TYPO3\CMS\Frontend\Page\PageRepository
* @throws \InvalidArgumentException
*/
protected function getPageRepository() {
if(TYPO3_MODE === 'BE') {
\TYPO3\CMS\Frontend\Utility\EidUtility::initTCA();
if (!is_object($GLOBALS['TT'])) {
$GLOBALS['TT'] = new \TYPO3\CMS\Core\TimeTracker\NullTimeTracker;
$GLOBALS['TT']->start();
}

$GLOBALS['TSFE'] = GeneralUtility::makeInstance('TYPO3\\CMS\\Frontend\\Controller\\TypoScriptFrontendController', $GLOBALS['TYPO3_CONF_VARS'], GeneralUtility::_GP('id'), '');
$GLOBALS['TSFE']->sys_page = GeneralUtility::makeInstance('TYPO3\\CMS\\Frontend\\Page\\PageRepository');
$GLOBALS['TSFE']->sys_page->init(TRUE);
$GLOBALS['TSFE']->connectToDB();
$GLOBALS['TSFE']->initFEuser();
$GLOBALS['TSFE']->determineId();
$GLOBALS['TSFE']->initTemplate();
$GLOBALS['TSFE']->getConfigArray();
}

return $GLOBALS['TSFE']->sys_page;
}

/**
* @return array
* @throws \InvalidArgumentException
*/
public function getSysLanguages() {
if ($this->sysLanguages === null) {
$this->sysLanguages = array();
$sys_languages = $this->getPageRepository()->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();
if(TYPO3_MODE === 'FE') {
$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;
}
}
78 changes: 78 additions & 0 deletions Classes/Renderers/AbstractExtendedSitemapRenderer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php
/***************************************************************
* Copyright notice
*
* (c) 2009-2014 Dmitry Dulepov <dmitry.dulepov@gmail.com>
* 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 <dmitry.dulepov@gmail.com>
* @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[] = '<xhtml:link rel="alternate" hreflang="' . $languageCode . '" href="' . $targetUrl . '"/>';
}
}

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']);
}
Loading

0 comments on commit 96dfdd7

Please sign in to comment.