Skip to content

Instantly share code, notes, and snippets.

@xperseguers
Last active November 12, 2019 16:37
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xperseguers/5c099f9b24a5427a79c09fd480da1cf8 to your computer and use it in GitHub Desktop.
Save xperseguers/5c099f9b24a5427a79c09fd480da1cf8 to your computer and use it in GitHub Desktop.
TypoScript-based language menu only showing available single news localizations
<?php
declare(strict_types=1);
/*
* 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 TYPO3 source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace Causal\LbTemplate\ContentObject\Menu;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject;
use TYPO3\CMS\Frontend\ContentObject\Menu\TextMenuContentObject;
/**
* Class SingleRecordLocalizationMenu
* @package Causal\LbTemplate\ContentObject\Menu
*
* Sample Usage (for a menu of localizations from a single EXT:news article):
*
* lib.menus.newsLanguages = HMENU
* lib.menus.newsLanguages {
* special = userfunction
* special {
* userFunc = Causal\LbTemplate\ContentObject\Menu\SingleRecordLocalizationMenu->prepareLanguageMenu
*
* # configure how to fetch the single element of this page
* table = tx_news_domain_model_news
* record = TEXT
* record.data = GP:tx_news_pi1|news
*
* # same meaning as when using "special = language"
* value = 0,3,1,2
*
* # default language is German (sys_language_uid = 0), but it could be another uid if you have a site with
* # various default languages and one of the available translations is the possible default default itself
* # for that subsite. In that case, the trick for such case (generally-speaking, not particularly for this
* # menu) is to avoid using "0" in the list of values above and replace it with the corresponding "translation"
* defaultLanguage = 0
*
* # syntax is the one (option split) typically used for a language menu
* overrideTitles = DE || EN || FR || IT
* }
*
* wrap = <ul class="list-inline"> | </ul>
*
* 1 = TMENU
* 1 {
* NO = 1
* NO {
* allWrap = <li> | </li>
* ATagParams = class="btn btn-default"
* }
*
* ACT < .NO
* ACT.ATagParams = class="btn btn-default active"
* }
* }
*
*/
class SingleRecordLocalizationMenu /* trick to invoke protected methods */ extends AbstractMenuContentObject
{
/**
* @var ContentObjectRenderer
*/
public $cObj;
/**
* Rendering the cObject, HMENU
*
* @param string $content
* @param array $conf Array of TypoScript properties
* @return array
*/
public function prepareLanguageMenu(string $content, array $conf): array
{
if (empty($conf['table'])) {
throw new \RuntimeException('No table specified', 1571397821);
}
$recordUid = $conf['record'];
if (!empty($conf['record.'])) {
$recordUid = $this->cObj->cObjGetSingle($conf['record'], $conf['record.']);
}
$recordUid = (int)$recordUid;
if (empty($recordUid)) {
// No menu available
return [];
}
// First of all, we want TYPO3 to prepare the links to the various localizations of current page
// "->prepareMenuItemsForLanguageMenu" is the protected method which we cannot invoke without the trick of the
// class inheritance (or Reflection which is less-efficient)
$menuObject = GeneralUtility::makeInstance(TextMenuContentObject::class);
$menuObject->parent_cObj = $this->cObj;
$fakeConf = [
'addQueryString' => '1',
'addQueryString.' => [
'exclude' => 'id,L,cHash',
],
'1' => 'TMENU',
];
$menuObject->start($GLOBALS['TSFE']->tmpl, $GLOBALS['TSFE']->sys_page, '', $fakeConf, 1);
$pageMenu = $menuObject->prepareMenuItemsForLanguageMenu($conf['value']);
// Then we look for available translations of the record
$languageUids = $this->getAvailableRecordLocalizations($conf['table'], $recordUid);
$languageUids[] = (int)$conf['defaultLanguage'];
// We want an associative array of $language => $position in the menu
// and their corresponding language codes to be shown
$orderedLanguages = array_flip(GeneralUtility::intExplode(',', $conf['value'], true));
$languageTitles = GeneralUtility::trimExplode('||', $conf['overrideTitles']);
// We need to override the page titles with the language code
foreach ($pageMenu as $key => $_) {
$pageMenu[$key]['title'] = $languageTitles[$key];
}
// Figure out which languages should get removed
$removeLanguageUids = array_diff(array_keys($orderedLanguages), $languageUids);
// Remove unwanted links
foreach ($removeLanguageUids as $languageUid) {
$pos = $orderedLanguages[$languageUid];
unset($pageMenu[$pos]);
}
// Ensure one of the pages is "active"
$hasActiveLanguage = false;
foreach ($pageMenu as $key => $page) {
$hasActiveLanguage |= $page['ITEM_STATE'] === 'ACT';
}
if (!$hasActiveLanguage) {
$pos = $orderedLanguages[(int)$conf['defaultLanguage']];
$pageMenu[$pos]['ITEM_STATE'] = 'ACT';
}
// Reindex the trimmed-down menu
$pageMenu = array_values($pageMenu);
// No need for a menu if we have no other language
if (count($pageMenu) === 1) {
$pageMenu = [];
}
return $pageMenu;
}
/**
* @param string $table
* @param int $uid
* @return array
*/
protected function getAvailableRecordLocalizations(string $table, int $uid): array
{
$rows = GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionForTable($table)
->select(
['sys_language_uid'],
$table,
[
'l10n_parent' => $uid,
]
)
->fetchAll();
$languageUids = [];
foreach ($rows as $row) {
$languageUids[] = $row['sys_language_uid'];
}
return $languageUids;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment