TYPO3 v15 dev-main snapshot ()
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Frontend\ContentObject\Menu;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\Category\Collection\CategoryCollection;
|
||||
|
||||
/**
|
||||
* Utility class for menus based on category collections of pages.
|
||||
*
|
||||
* Returns all the relevant pages for rendering with a menu content object.
|
||||
* @internal this is only used for internal purposes and solely used for EXT:frontend and not part of TYPO3's Core API.
|
||||
*/
|
||||
class CategoryMenuUtility
|
||||
{
|
||||
/**
|
||||
* @var string Name of the field used for sorting the pages
|
||||
*/
|
||||
protected static $sortingField;
|
||||
|
||||
/**
|
||||
* Collects all pages for the selected categories, sorted according to configuration.
|
||||
*
|
||||
* @param string $selectedCategories Comma-separated list of system categories primary keys
|
||||
* @param array|null $configuration TypoScript configuration for the "special." keyword
|
||||
* @param AbstractMenuContentObject $parentObject Back-reference to the calling object
|
||||
* @return array List of selected pages
|
||||
*/
|
||||
public function collectPages($selectedCategories, $configuration, $parentObject)
|
||||
{
|
||||
$selectedPages = [];
|
||||
$categoriesPerPage = [];
|
||||
// Determine the name of the relation field
|
||||
$relationField = (string)$parentObject->getParentContentObject()->stdWrapValue('relation', $configuration ?? []);
|
||||
// Get the pages for each selected category
|
||||
$selectedCategories = GeneralUtility::intExplode(',', $selectedCategories, true);
|
||||
foreach ($selectedCategories as $aCategory) {
|
||||
$collection = CategoryCollection::load(
|
||||
$aCategory,
|
||||
true,
|
||||
'pages',
|
||||
$relationField
|
||||
);
|
||||
$categoryUid = $collection->getUid();
|
||||
// Loop on the results, overlay each page record found
|
||||
foreach ($collection as $pageItem) {
|
||||
$parentObject->getSysPage()->versionOL('pages', $pageItem, true);
|
||||
if (is_array($pageItem)) {
|
||||
$selectedPages[$pageItem['uid']] = $parentObject->getSysPage()->getLanguageOverlay('pages', $pageItem);
|
||||
// Keep a list of the categories each page belongs to
|
||||
if (!isset($categoriesPerPage[$pageItem['uid']])) {
|
||||
$categoriesPerPage[$pageItem['uid']] = [];
|
||||
}
|
||||
$categoriesPerPage[$pageItem['uid']][] = $categoryUid;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Loop on the selected pages to add the categories they belong to, as comma-separated list of category uid's)
|
||||
// (this makes them available for rendering, if needed)
|
||||
foreach ($selectedPages as $uid => $pageRecord) {
|
||||
$selectedPages[$uid]['_categories'] = implode(',', $categoriesPerPage[$uid]);
|
||||
}
|
||||
|
||||
// Sort the pages according to the sorting property
|
||||
self::$sortingField = (string)$parentObject->getParentContentObject()->stdWrapValue('sorting', $configuration ?? []);
|
||||
$order = (string)$parentObject->getParentContentObject()->stdWrapValue('order', $configuration ?? []);
|
||||
$selectedPages = $this->sortPages($selectedPages, $order);
|
||||
|
||||
return $selectedPages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the selected pages
|
||||
*
|
||||
* If the sorting field is not defined or does not corresponding to an existing field
|
||||
* of the "pages" tables, the list of pages will remain unchanged.
|
||||
*
|
||||
* @param array $pages List of selected pages
|
||||
* @param string $order Order for sorting (should "asc" or "desc")
|
||||
* @return array Sorted list of pages
|
||||
*/
|
||||
protected function sortPages($pages, $order)
|
||||
{
|
||||
// Perform the sorting only if a criterion was actually defined
|
||||
if (!empty(self::$sortingField)) {
|
||||
// Check that the sorting field exists (checking the first record is enough)
|
||||
$firstPage = current($pages);
|
||||
if (isset($firstPage[self::$sortingField])) {
|
||||
// Make sure the order property is either "asc" or "desc" (default is "asc")
|
||||
if (!empty($order)) {
|
||||
$order = strtolower($order);
|
||||
if ($order !== 'desc') {
|
||||
$order = 'asc';
|
||||
}
|
||||
}
|
||||
$sortMultiplier = $order === 'asc' ? 1 : -1;
|
||||
uasort($pages, static function (array $pageA, array $pageB) use ($sortMultiplier): int {
|
||||
return strnatcasecmp($pageA[self::$sortingField], $pageB[self::$sortingField]) * $sortMultiplier;
|
||||
});
|
||||
}
|
||||
}
|
||||
return $pages;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Frontend\ContentObject\Menu\Exception;
|
||||
|
||||
use TYPO3\CMS\Frontend\Exception;
|
||||
|
||||
/**
|
||||
* No such menu type exception
|
||||
*/
|
||||
class NoSuchMenuTypeException extends Exception {}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?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 this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Frontend\ContentObject\Menu;
|
||||
|
||||
use TYPO3\CMS\Core\SingletonInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\ContentObject\Menu\Exception\NoSuchMenuTypeException;
|
||||
|
||||
/**
|
||||
* Factory for menu content objects. Allows overriding the default
|
||||
* types like 'TMENU' with an own implementation (only one possible)
|
||||
* and new types can be registered.
|
||||
* @internal this is only used for internal purposes and solely used for EXT:frontend and not part of TYPO3's Core API.
|
||||
*/
|
||||
class MenuContentObjectFactory implements SingletonInterface
|
||||
{
|
||||
/**
|
||||
* Register of TypoScript keys to according render class
|
||||
*/
|
||||
protected array $menuTypeToClassMapping = [
|
||||
'TMENU' => TextMenuContentObject::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* Gets a typo script string like 'TMENU' and returns an object of this type
|
||||
*
|
||||
* @throws Exception\NoSuchMenuTypeException
|
||||
*/
|
||||
public function getMenuObjectByType(string $type = ''): AbstractMenuContentObject
|
||||
{
|
||||
$upperCasedClassName = strtoupper($type);
|
||||
if (array_key_exists($upperCasedClassName, $this->menuTypeToClassMapping)) {
|
||||
/** @var AbstractMenuContentObject $object */
|
||||
$object = GeneralUtility::makeInstance($this->menuTypeToClassMapping[$upperCasedClassName]);
|
||||
return $object;
|
||||
}
|
||||
throw new NoSuchMenuTypeException(
|
||||
'Menu type ' . (string)$type . ' has no implementing class.',
|
||||
1363278130
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register new menu type or override existing type
|
||||
*
|
||||
* @param string $type Menu type to be used in TypoScript
|
||||
* @param string $className Class rendering the menu
|
||||
*/
|
||||
public function registerMenuType(string $type, string $className)
|
||||
{
|
||||
$this->menuTypeToClassMapping[strtoupper($type)] = $className;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Frontend\ContentObject\Menu;
|
||||
|
||||
use TYPO3\CMS\Core\TypoScript\TypoScriptService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
|
||||
/**
|
||||
* Extension class creating text based menus
|
||||
*/
|
||||
class TextMenuContentObject extends AbstractMenuContentObject
|
||||
{
|
||||
/**
|
||||
* Traverses the ->result array of menu items configuration (made by ->generate()) and renders each item.
|
||||
* An instance of ContentObjectRenderer is also made and for each menu item rendered it is loaded with
|
||||
* the record for that page so that any stdWrap properties that applies will have the current menu items record available.
|
||||
*
|
||||
* @return string The HTML for the menu including submenus
|
||||
*/
|
||||
public function writeMenu()
|
||||
{
|
||||
if (empty($this->result)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$register = $this->request->getAttribute('frontend.register.stack')->current();
|
||||
$cObjectForCurrentMenu = GeneralUtility::makeInstance(ContentObjectRenderer::class);
|
||||
$menuContent = [];
|
||||
$typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
|
||||
$subMenuObjSuffixes = $typoScriptService->explodeConfigurationForOptionSplit(['sOSuffix' => $this->mconf['submenuObjSuffixes'] ?? null], count($this->result));
|
||||
$explicitSpacerRenderingEnabled = ($this->mconf['SPC'] ?? false);
|
||||
foreach ($this->result as $key => $val) {
|
||||
$register->set('count_HMENU_MENUOBJ', (int)$register->get('count_HMENU_MENUOBJ', 0) + 1);
|
||||
$register->set('count_MENUOBJ', (int)$register->get('count_MENUOBJ', 0) + 1);
|
||||
|
||||
// Initialize the cObj with the page record of the menu item
|
||||
$cObjectForCurrentMenu->setRequest($this->request);
|
||||
$cObjectForCurrentMenu->start($this->menuArr[$key], 'pages');
|
||||
$this->I = [];
|
||||
$this->I['key'] = $key;
|
||||
$this->I['val'] = $val;
|
||||
$this->I['title'] = $this->getPageTitle($this->menuArr[$key]['title'] ?? '', $this->menuArr[$key]['nav_title'] ?? '');
|
||||
$this->I['title.'] = $this->I['val']['stdWrap.'] ?? [];
|
||||
$this->I['title'] = $cObjectForCurrentMenu->stdWrapValue('title', $this->I);
|
||||
$this->I['uid'] = $this->menuArr[$key]['uid'] ?? 0;
|
||||
$this->I['mount_pid'] = $this->menuArr[$key]['mount_pid'] ?? 0;
|
||||
$this->I['pid'] = $this->menuArr[$key]['pid'] ?? 0;
|
||||
$this->I['spacer'] = $this->menuArr[$key]['isSpacer'] ?? false;
|
||||
// Make link tag
|
||||
$this->I['val']['additionalParams'] = $cObjectForCurrentMenu->stdWrapValue('additionalParams', $this->I['val']);
|
||||
$linkResult = $this->link((int)$key, (string)($this->I['val']['altTarget'] ?? ''), ($this->mconf['forceTypeValue'] ?? ''));
|
||||
if ($linkResult === null) {
|
||||
$this->I['val']['doNotLinkIt'] = 1;
|
||||
}
|
||||
// Title attribute of links
|
||||
$titleAttrValue = $cObjectForCurrentMenu->stdWrapValue('ATagTitle', $this->I['val']);
|
||||
if ($linkResult && $titleAttrValue !== '') {
|
||||
$linkResult = $linkResult->withAttribute('title', $titleAttrValue);
|
||||
}
|
||||
$this->I['linkHREF'] = $linkResult;
|
||||
$this->I['val']['doNotLinkIt'] = (bool)$cObjectForCurrentMenu->stdWrapValue('doNotLinkIt', $this->I['val']);
|
||||
// Compile link tag
|
||||
if (!$this->I['spacer'] && !$this->I['val']['doNotLinkIt']) {
|
||||
$this->setATagParts($linkResult);
|
||||
} else {
|
||||
$this->I['A1'] = '';
|
||||
$this->I['A2'] = '';
|
||||
}
|
||||
// ATagBeforeWrap processing:
|
||||
if ($this->I['val']['ATagBeforeWrap'] ?? false) {
|
||||
$wrapPartsBefore = explode('|', $this->I['val']['linkWrap'] ?? '');
|
||||
$wrapPartsAfter = ['', ''];
|
||||
} else {
|
||||
$wrapPartsBefore = ['', ''];
|
||||
$wrapPartsAfter = explode('|', $this->I['val']['linkWrap'] ?? '');
|
||||
}
|
||||
if (($this->I['val']['stdWrap2'] ?? false) || isset($this->I['val']['stdWrap2.'])) {
|
||||
$stdWrap2 = (string)(isset($this->I['val']['stdWrap2.']) ? $cObjectForCurrentMenu->stdWrap('|', $this->I['val']['stdWrap2.']) : '|');
|
||||
$stdWrap2Value = (string)($this->I['val']['stdWrap2'] ?? '|');
|
||||
$stdWrap2Value = $stdWrap2Value !== '' ? $stdWrap2Value : '|';
|
||||
$wrapPartsStdWrap = explode($stdWrap2Value, $stdWrap2);
|
||||
} else {
|
||||
$wrapPartsStdWrap = ['', ''];
|
||||
}
|
||||
// Make before, middle and after parts
|
||||
$this->I['parts'] = [];
|
||||
$this->I['parts']['before'] = $this->getBeforeAfter('before', $cObjectForCurrentMenu);
|
||||
$this->I['parts']['stdWrap2_begin'] = $wrapPartsStdWrap[0];
|
||||
// stdWrap for doNotShowLink
|
||||
$this->I['val']['doNotShowLink'] = $cObjectForCurrentMenu->stdWrapValue('doNotShowLink', $this->I['val']);
|
||||
if (!$this->I['val']['doNotShowLink']) {
|
||||
$this->I['parts']['notATagBeforeWrap_begin'] = $wrapPartsAfter[0];
|
||||
$this->I['parts']['ATag_begin'] = $this->I['A1'];
|
||||
$this->I['parts']['ATagBeforeWrap_begin'] = $wrapPartsBefore[0];
|
||||
$this->I['parts']['title'] = $this->I['title'];
|
||||
$this->I['parts']['ATagBeforeWrap_end'] = $wrapPartsBefore[1] ?? '';
|
||||
$this->I['parts']['ATag_end'] = $this->I['A2'];
|
||||
$this->I['parts']['notATagBeforeWrap_end'] = $wrapPartsAfter[1] ?? '';
|
||||
}
|
||||
$this->I['parts']['stdWrap2_end'] = $wrapPartsStdWrap[1] ?? '';
|
||||
$this->I['parts']['after'] = $this->getBeforeAfter('after', $cObjectForCurrentMenu);
|
||||
// Passing I to a user function
|
||||
if ($this->mconf['IProcFunc'] ?? false) {
|
||||
$this->I = $this->userProcess('IProcFunc', $this->I);
|
||||
}
|
||||
// Merge parts + beforeAllWrap
|
||||
$this->I['theItem'] = implode('', $this->I['parts']);
|
||||
$allWrap = $cObjectForCurrentMenu->stdWrapValue('allWrap', $this->I['val']);
|
||||
$this->I['theItem'] = $cObjectForCurrentMenu->wrap($this->I['theItem'], $allWrap);
|
||||
if ($this->I['val']['subst_elementUid'] ?? false) {
|
||||
$this->I['theItem'] = str_replace('{elementUid}', (string)$this->I['uid'], $this->I['theItem']);
|
||||
}
|
||||
if (is_array($this->I['val']['allStdWrap.'] ?? null)) {
|
||||
$this->I['theItem'] = $cObjectForCurrentMenu->stdWrap($this->I['theItem'], $this->I['val']['allStdWrap.']);
|
||||
}
|
||||
$isSpacerPage = $this->I['spacer'] ?? false;
|
||||
// If rendering of SPACERs is enabled, also allow rendering submenus with Spacers
|
||||
if (!$isSpacerPage || $explicitSpacerRenderingEnabled) {
|
||||
// Add part to the accumulated result + fetch submenus
|
||||
$this->I['theItem'] .= $this->subMenu($this->I['uid'], $subMenuObjSuffixes[$key]['sOSuffix'] ?? '', $key);
|
||||
}
|
||||
$part = $cObjectForCurrentMenu->stdWrapValue('wrapItemAndSub', $this->I['val']);
|
||||
$menuContent[] = $part ? $cObjectForCurrentMenu->wrap($this->I['theItem'], $part) : $this->I['theItem'];
|
||||
}
|
||||
|
||||
$menuContent = implode('', $menuContent);
|
||||
if (is_array($this->mconf['stdWrap.'] ?? null)) {
|
||||
$menuContent = (string)$cObjectForCurrentMenu->stdWrap($menuContent, $this->mconf['stdWrap.']);
|
||||
}
|
||||
return $cObjectForCurrentMenu->wrap($menuContent, $this->mconf['wrap'] ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the before* and after* stdWrap for TMENUs
|
||||
* Evaluates:
|
||||
* - before.stdWrap*
|
||||
* - beforeWrap
|
||||
* - after.stdWrap*
|
||||
* - afterWrap
|
||||
*
|
||||
* @param string $pref Can be "before" or "after" and determines which kind of stdWrap to process (basically this is the prefix of the TypoScript properties that are read from the ->I['val'] array
|
||||
* @return string The resulting HTML
|
||||
*/
|
||||
protected function getBeforeAfter(string $pref, ContentObjectRenderer $cObjectForCurrentMenu): string
|
||||
{
|
||||
$processedPref = $cObjectForCurrentMenu->stdWrapValue($pref, $this->I['val']);
|
||||
if (isset($this->I['val'][$pref . 'Wrap'])) {
|
||||
return $cObjectForCurrentMenu->wrap($processedPref, $this->I['val'][$pref . 'Wrap']);
|
||||
}
|
||||
return $processedPref;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user