diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 560f064..a8e46c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,15 +13,13 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: [ '7.4', '7.3', '7.2' ] - TYPO3: [ '10' ] + php: [ '7.4', '8.0', '8.1' ] + TYPO3: [ '11' ] include: - - TYPO3: '11' + - TYPO3: '12' php: '8.1' - - TYPO3: '11' - php: '8.0' - - TYPO3: '11' - php: '7.4' + - TYPO3: '13' + php: '8.3' steps: - name: Checkout uses: actions/checkout@v2 diff --git a/Build/phpstan10.neon b/Build/phpstan10.neon deleted file mode 100644 index 8bcb0ab..0000000 --- a/Build/phpstan10.neon +++ /dev/null @@ -1,14 +0,0 @@ -parameters: - level: 5 - - paths: - - %currentWorkingDirectory%/Classes - - %currentWorkingDirectory%/Tests - - ignoreErrors: - - - message: '#Constant ORIGINAL_ROOT not found.#' - path: %currentWorkingDirectory%/Tests - - - message: '#Call to an undefined method Prophecy\\Prophecy\\ObjectProphecy::.*#' - path: %currentWorkingDirectory%/Tests diff --git a/Build/phpstan11.neon b/Build/phpstan11.neon index a9bf0e4..8bf0294 100644 --- a/Build/phpstan11.neon +++ b/Build/phpstan11.neon @@ -4,14 +4,15 @@ parameters: paths: - %currentWorkingDirectory%/Classes - %currentWorkingDirectory%/Tests - + excludePaths: + - %currentWorkingDirectory%/Tests/Unit/Domain/Repository/MenuRepositoryTest.php ignoreErrors: - - message: '#.*TYPO3\\CMS\\Frontend\\Page\\PageRepository.*#' - path: %currentWorkingDirectory%/Classes/Domain/Repository/MenuRepository.php + message: '#Cannot call method getLanguageCode\(\) on string.#' + path: %currentWorkingDirectory%/Classes/Compiler/LanguageMenuCompiler.php - - message: '#Constant ORIGINAL_ROOT not found.#' - path: %currentWorkingDirectory%/Tests + message: '#Class TYPO3\\CMS\\Frontend\\Cache\\CacheLifetimeCalculator not found.#' + path: %currentWorkingDirectory%/Classes/CacheHelper.php - - message: '#Call to an undefined method Prophecy\\Prophecy\\ObjectProphecy::.*#' - path: %currentWorkingDirectory%/Tests + message: '#.*unknown class TYPO3\\CMS\\Core\\TypoScript\\FrontendTypoScript.#' + path: %currentWorkingDirectory%/Classes/CacheHelper.php diff --git a/Build/phpstan12.neon b/Build/phpstan12.neon new file mode 100644 index 0000000..e8f1027 --- /dev/null +++ b/Build/phpstan12.neon @@ -0,0 +1,18 @@ +parameters: + level: 5 + + paths: + - %currentWorkingDirectory%/Classes + - %currentWorkingDirectory%/Tests + excludePaths: + - %currentWorkingDirectory%/Tests/Unit/Domain/Repository/MenuRepositoryTest.php + ignoreErrors: + - + message: '#Call to an undefined static method TYPO3\\CMS\\Frontend\\ContentObject\\AbstractContentObject::__construct\(\).#' + path: %currentWorkingDirectory%/Classes/ContentObject/* + - + message: '#Property TYPO3\\CMS\\Frontend\\Controller\\TypoScriptFrontendController::\$id \(int\) does not accept string.#' + path: %currentWorkingDirectory%/Tests/Functional/Compiler/LanguageMenuCompilerTest.php + - + message: '#Call to an undefined method TYPO3\\CMS\\Core\\TypoScript\\FrontendTypoScript::getConfigArray\(\).#' + path: %currentWorkingDirectory%/Classes/CacheHelper.php diff --git a/Build/phpstan13.neon b/Build/phpstan13.neon new file mode 100644 index 0000000..e396ce1 --- /dev/null +++ b/Build/phpstan13.neon @@ -0,0 +1,27 @@ +parameters: + level: 5 + + paths: + - %currentWorkingDirectory%/Classes + - %currentWorkingDirectory%/Tests + excludePaths: + - %currentWorkingDirectory%/Tests/Unit/Domain/Repository/MenuRepositoryTest.php + ignoreErrors: + - + message: '#Call to an undefined method TYPO3\\CMS\\Core\\Site\\Entity\\SiteLanguage::getTwoLetterIsoCode\(\).#' + path: %currentWorkingDirectory%/Classes/Compiler/LanguageMenuCompiler.php + - + message: '#Access to undefined constant TYPO3\\CMS\\Core\\Domain\\Repository\\PageRepository::DOKTYPE_RECYCLER.#' + path: %currentWorkingDirectory%/Classes/Domain/Repository/MenuRepository.php + - + message: '#Access to undefined constant TYPO3\\CMS\\Core\\Domain\\Repository\\PageRepository::DOKTYPE_RECYCLER.#' + path: %currentWorkingDirectory%/Tests/Unit/Domain/Repository/MenuRepositoryTest.php + - + message: '#.*get_cache_timeout\(\).*#' + path: %currentWorkingDirectory%/Classes/CacheHelper.php + - + message: '#Call to an undefined static method TYPO3\\CMS\\Frontend\\ContentObject\\AbstractContentObject::__construct\(\).#' + path: %currentWorkingDirectory%/Classes/ContentObject/* + - + message: '#Property TYPO3\\CMS\\Frontend\\Controller\\TypoScriptFrontendController::\$id \(int\) does not accept string.#' + path: %currentWorkingDirectory%/Tests/Functional/Compiler/LanguageMenuCompilerTest.php diff --git a/Build/phpunit/FunctionalTests.xml b/Build/phpunit/FunctionalTests.xml index 17f81d2..ded3940 100644 --- a/Build/phpunit/FunctionalTests.xml +++ b/Build/phpunit/FunctionalTests.xml @@ -24,6 +24,5 @@ - diff --git a/Build/phpunit/UnitTestsBootstrap.php b/Build/phpunit/UnitTestsBootstrap.php index 9e9222f..a91159e 100644 --- a/Build/phpunit/UnitTestsBootstrap.php +++ b/Build/phpunit/UnitTestsBootstrap.php @@ -12,28 +12,9 @@ * The TYPO3 project - inspiring people to share! */ -use TYPO3\CMS\Core\Information\Typo3Version; - call_user_func(function () { - if (!class_exists(\TYPO3\CMS\Frontend\Page\PageRepository::class)) { - class_alias(\TYPO3\CMS\Core\Domain\Repository\PageRepository::class, \TYPO3\CMS\Frontend\Page\PageRepository::class); - } $testbase = new \TYPO3\TestingFramework\Core\Testbase(); - // These if's are for core testing (package typo3/cms) only. cms-composer-installer does - // not create the autoload-include.php file that sets these env vars and sets composer - // mode to true. testing-framework can not be used without composer anyway, so it is safe - // to do this here. This way it does not matter if 'bin/phpunit' or 'vendor/phpunit/phpunit/phpunit' - // is called to run the tests since the 'relative to entry script' path calculation within - // SystemEnvironmentBuilder is not used. However, the binary must be called from the document - // root since getWebRoot() uses 'getcwd()'. - if (!getenv('TYPO3_PATH_ROOT')) { - putenv('TYPO3_PATH_ROOT=' . rtrim($testbase->getWebRoot(), '/')); - } - if (!getenv('TYPO3_PATH_WEB')) { - putenv('TYPO3_PATH_WEB=' . rtrim($testbase->getWebRoot(), '/')); - } - $testbase->defineSitePath(); $requestType = \TYPO3\CMS\Core\Core\SystemEnvironmentBuilder::REQUESTTYPE_BE | \TYPO3\CMS\Core\Core\SystemEnvironmentBuilder::REQUESTTYPE_CLI; @@ -57,17 +38,10 @@ class_alias(\TYPO3\CMS\Core\Domain\Repository\PageRepository::class, \TYPO3\CMS\ new \TYPO3\CMS\Core\Cache\Backend\NullBackend('production', []) ); // Set all packages to active - if (version_compare((new Typo3Version())->getVersion(), '11.3.0', '>')) { - $packageManager = \TYPO3\CMS\Core\Core\Bootstrap::createPackageManager( - \TYPO3\CMS\Core\Package\UnitTestPackageManager::class, - \TYPO3\CMS\Core\Core\Bootstrap::createPackageCache($cache) - ); - } else { - $packageManager = \TYPO3\CMS\Core\Core\Bootstrap::createPackageManager( - \TYPO3\CMS\Core\Package\UnitTestPackageManager::class, - $cache - ); - } + $packageManager = \TYPO3\CMS\Core\Core\Bootstrap::createPackageManager( + \TYPO3\CMS\Core\Package\UnitTestPackageManager::class, + \TYPO3\CMS\Core\Core\Bootstrap::createPackageCache($cache) + ); \TYPO3\CMS\Core\Utility\GeneralUtility::setSingletonInstance(\TYPO3\CMS\Core\Package\PackageManager::class, $packageManager); \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::setPackageManager($packageManager); diff --git a/Build/testing-docker/docker-compose.yml b/Build/testing-docker/docker-compose.yml index f1b2d26..3cf5d8b 100644 --- a/Build/testing-docker/docker-compose.yml +++ b/Build/testing-docker/docker-compose.yml @@ -82,10 +82,15 @@ services: set -x fi php -v | grep '^PHP'; - if [ ${TYPO3} -eq 10 ]; then - composer install --no-progress --no-interaction; + if [ ${TYPO3} -eq 11 ]; then + composer require typo3/cms-install:^11.5 typo3/cms-fluid-styled-content:^11.5 --dev -W --no-progress --no-interaction + composer prepare-tests + elif [ ${TYPO3} -eq 12 ]; then + composer require typo3/cms-install:^12.4 typo3/cms-fluid-styled-content:^12.4 --dev -W --no-progress --no-interaction + composer prepare-tests else - composer remove typo3/cms* --dev --no-progress --no-interaction && composer require typo3/cms-install:^11.5 typo3/cms-fluid-styled-content:^11.5 --dev -W --no-progress --no-interaction + composer install --no-progress --no-interaction + composer prepare-tests fi " @@ -122,7 +127,7 @@ services: typo3DatabaseUsername: root typo3DatabasePassword: funcp typo3DatabaseHost: mariadb10 - working_dir: ${ROOT_DIR}/.Build + working_dir: ${ROOT_DIR} command: > /bin/sh -c " if [ ${SCRIPT_VERBOSE} -eq 1 ]; then @@ -136,13 +141,13 @@ services: php -v | grep '^PHP'; if [ ${PHP_XDEBUG_ON} -eq 0 ]; then export XDEBUG_MODE=\"off\" - bin/phpunit -c Web/typo3conf/ext/menus/Build/phpunit/FunctionalTests.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE}; + .Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE}; else DOCKER_HOST=`route -n | awk '/^0.0.0.0/ { print $$2 }'` export XDEBUG_MODE=\"debug,develop\" \ XDEBUG_TRIGGER=\"foo\" \ XDEBUG_CONFIG=\"client_port=${PHP_XDEBUG_PORT} client_host=$${DOCKER_HOST}\" - bin/phpunit -c Web/typo3conf/ext/menus/Build/phpunit/FunctionalTests.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE}; + .Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE}; fi " lint: @@ -188,7 +193,7 @@ services: - ${HOST_HOME}:${HOST_HOME} - /etc/passwd:/etc/passwd:ro - /etc/group:/etc/group:ro - working_dir: ${ROOT_DIR}/.Build + working_dir: ${ROOT_DIR} command: > /bin/sh -c " if [ ${SCRIPT_VERBOSE} -eq 1 ]; then @@ -197,12 +202,12 @@ services: php -v | grep '^PHP'; if [ ${PHP_XDEBUG_ON} -eq 0 ]; then XDEBUG_MODE=\"off\" \ - bin/phpunit -c Web/typo3conf/ext/menus/Build/phpunit/UnitTests.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE}; + .Build/bin/phpunit -c Build/phpunit/UnitTests.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE}; else DOCKER_HOST=`route -n | awk '/^0.0.0.0/ { print $$2 }'` XDEBUG_MODE=\"debug,develop\" \ XDEBUG_TRIGGER=\"foo\" \ XDEBUG_CONFIG=\"client_port=${PHP_XDEBUG_PORT} client_host=$${DOCKER_HOST}\" \ - bin/phpunit -c Web/typo3conf/ext/menus/Build/phpunit/UnitTests.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE}; + .Build/bin/phpunit -c menus/Build/phpunit/UnitTests.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE}; fi " diff --git a/Classes/CacheHelper.php b/Classes/CacheHelper.php index c9bb9bf..4fedd6e 100644 --- a/Classes/CacheHelper.php +++ b/Classes/CacheHelper.php @@ -11,13 +11,15 @@ * of the License, or any later version. */ -use TYPO3\CMS\Core\Cache\CacheManager; +use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Context\Exception\AspectNotFoundException; use TYPO3\CMS\Core\Information\Typo3Version; use TYPO3\CMS\Core\SingletonInterface; +use TYPO3\CMS\Core\TypoScript\FrontendTypoScript; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Frontend\Cache\CacheLifetimeCalculator; use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; /** @@ -28,23 +30,14 @@ */ class CacheHelper implements SingletonInterface { - /** - * @var FrontendInterface - */ - protected $cache; - - protected $disableCaching = false; + protected FrontendInterface $cache; + protected bool $disableCaching = false; + protected Context $context; - public function __construct(FrontendInterface $cache = null, Context $context = null) + public function __construct(FrontendInterface $cache, Context $context) { - if ((new Typo3Version())->getMajorVersion() < 10) { - $this->cache = $cache ?? GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash'); - } else { - $this->cache = $cache ?? GeneralUtility::makeInstance(CacheManager::class)->getCache('hash'); - } - if ($context === null) { - $context = GeneralUtility::makeInstance(Context::class); - } + $this->context = $context; + $this->cache = $cache; try { $this->disableCaching = $context->getPropertyFromAspect('workspace', 'id', 0) > 0; } catch (AspectNotFoundException $e) { @@ -60,10 +53,6 @@ public function __construct(FrontendInterface $cache = null, Context $context = /** * Looks up the items inside the cache, if it exists, takes the cached entry, otherwise computes the data * via the $loader(). - * - * @param string $cacheIdentifier - * @param callable $loader - * @return array */ public function get(string $cacheIdentifier, callable $loader): array { @@ -81,7 +70,8 @@ public function get(string $cacheIdentifier, callable $loader): array // Calculate tags + lifetime $tags = $this->buildTagsAndAddThemToPageCache($pages); - $maximumLifeTime = $this->getMaxLifetimeOfPages($pages, $this->getFrontendController()->get_cache_timeout()); + $defaultMaxLifeTime = $this->getDefaultMaxLifeTime(); + $maximumLifeTime = $this->getMaxLifetimeOfPages($pages, $defaultMaxLifeTime); $this->cache->set($cacheIdentifier, $pages, $tags, $maximumLifeTime); return $pages; } @@ -126,15 +116,58 @@ protected function getAllPageIdsFromItems(array $pages): array return $pageIds; } + protected function getDefaultMaxLifeTime(): int + { + if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() < 13) { + $maxLifetime = (int)$this->getFrontendController()->get_cache_timeout(); + } else { + $request = $this->getServerRequest(); + $pageInformation = $request->getAttribute('frontend.page.information'); + /** @var ?FrontendTypoScript $typoScript */ + $typoScript = $request->getAttribute('frontend.typoscript'); + if ($typoScript === null || $pageInformation === null) { + return 0; + } + $typoScriptConfigArray = $typoScript->getConfigArray(); + $maxLifetime = GeneralUtility::makeInstance(CacheLifetimeCalculator::class) + ->calculateLifetimeForPage( + $pageInformation->getId(), + $pageInformation->getPageRecord(), + $typoScriptConfigArray, + 0, + $this->context + ); + } + return $maxLifetime; + } + /** * pages.cache_timeout is not used here, as this is supposed to be relevant for content of a page, not the * metadata. */ - protected function getMaxLifetimeOfPages(array $pages, int $maxLifetime = null): ?int + protected function getMaxLifetimeOfPages(array $pages, int $maxLifetime): int { foreach ($pages as $page) { if (!empty($page['endtime'])) { - $maxLifetimeOfPage = $page['endtime'] - $GLOBALS['EXEC_TIME']; + $maxLifetimeOfPage = (int)$page['endtime'] - $GLOBALS['EXEC_TIME']; + if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() === 13) { + $request = $this->getServerRequest(); + /** @var ?FrontendTypoScript $typoScript */ + $typoScript = $request->getAttribute('frontend.typoscript'); + if ($typoScript === null) { + $typoScriptConfigArray = []; + } else { + $typoScriptConfigArray = $typoScript->getConfigArray(); + } + $maxLifetimeOfPage = GeneralUtility::makeInstance(CacheLifetimeCalculator::class) + ->calculateLifetimeForPage( + $page['uid'], + $page, + $typoScriptConfigArray, + 0, + $this->context + ); + } if ($maxLifetimeOfPage < $maxLifetime) { $maxLifetime = $maxLifetimeOfPage; } @@ -146,9 +179,11 @@ protected function getMaxLifetimeOfPages(array $pages, int $maxLifetime = null): return $maxLifetime; } - /** - * @return TypoScriptFrontendController - */ + protected function getServerRequest(): ServerRequestInterface + { + return $GLOBALS['TYPO3_REQUEST']; + } + protected function getFrontendController(): TypoScriptFrontendController { return $GLOBALS['TSFE']; diff --git a/Classes/Compiler/AbstractMenuCompiler.php b/Classes/Compiler/AbstractMenuCompiler.php index a55fa9b..850159c 100644 --- a/Classes/Compiler/AbstractMenuCompiler.php +++ b/Classes/Compiler/AbstractMenuCompiler.php @@ -17,6 +17,7 @@ use TYPO3\CMS\Core\Context\LanguageAspect; use TYPO3\CMS\Core\Context\UserAspect; use TYPO3\CMS\Core\Context\VisibilityAspect; +use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Site\Entity\SiteInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; @@ -24,36 +25,21 @@ /** * MenuCompiler sorts out all relevant parts in the constructor which most menu compilers need. */ -abstract class AbstractMenuCompiler +abstract class AbstractMenuCompiler implements SingletonInterface { - /** - * @var MenuRepository - */ - protected $menuRepository; - - /** - * @var CacheHelper - */ - protected $cache; + protected MenuRepository $menuRepository; + protected CacheHelper $cache; + protected Context $context; - /** - * @var Context - */ - protected $context; - - public function __construct(Context $context = null, CacheHelper $cache = null) + public function __construct(Context $context, CacheHelper $cache, MenuRepository $menuRepository) { - $this->context = $context ?? GeneralUtility::makeInstance(Context::class); - $this->menuRepository = GeneralUtility::makeInstance(MenuRepository::class, $this->context); - $this->cache = $cache ?? GeneralUtility::makeInstance(CacheHelper::class); + $this->context = $context; + $this->menuRepository = $menuRepository; + $this->cache = $cache; } /** * Fetch the related pages and caches it via the cache helper. - * - * @param ContentObjectRenderer $contentObjectRenderer - * @param array $configuration - * @return array */ abstract public function compile(ContentObjectRenderer $contentObjectRenderer, array $configuration): array; @@ -80,7 +66,7 @@ protected function generateCacheIdentifierForMenu(string $prefix, array $configu $visibilityAspect = $this->context->getAspect('visibility'); $visibility = $visibilityAspect->includeHiddenPages() ? '-with-hidden' : ''; $root = $this->getCurrentSite()->getRootPageId(); - $identifier = $prefix . '-root-' . $root . '-language-' . $language . '-groups-' . implode('_', $groupIds) . '-' . $visibility . '-' . substr(md5(json_encode($configuration)), 0, 10); + $identifier = $prefix . '-root-' . $root . '-language-' . $language . '-groups-' . md5(implode('_', $groupIds)) . '-' . $visibility . '-' . substr(md5(json_encode($configuration)), 0, 10); return $identifier; } @@ -91,12 +77,8 @@ protected function getCurrentSite(): ?SiteInterface /** * Function to parse typoscript config with stdWrap - * @param string $content - * @param string $configuration - * - * @return string */ - public function parseStdWrap($content, $configuration): string + public function parseStdWrap(string $content, array $configuration): string { $return = GeneralUtility::makeInstance(ContentObjectRenderer::class)->stdWrap($content, $configuration); if ($return !== null) { diff --git a/Classes/Compiler/LanguageMenuCompiler.php b/Classes/Compiler/LanguageMenuCompiler.php index efbe5a7..27c1725 100644 --- a/Classes/Compiler/LanguageMenuCompiler.php +++ b/Classes/Compiler/LanguageMenuCompiler.php @@ -13,6 +13,8 @@ use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Context\LanguageAspectFactory; +use TYPO3\CMS\Core\Information\Typo3Version; +use TYPO3\CMS\Core\Site\Entity\SiteLanguage; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; @@ -23,7 +25,7 @@ class LanguageMenuCompiler extends AbstractMenuCompiler */ public function compile(ContentObjectRenderer $contentObjectRenderer, array $configuration): array { - $cacheIdentifier = $this->generateCacheIdentifierForMenu('list', $configuration); + $cacheIdentifier = $this->generateCacheIdentifierForMenu('language', $configuration); $excludedLanguages = $contentObjectRenderer->stdWrap($configuration['excludeLanguages'] ?? '', $configuration['excludeLanguages.'] ?? []); $excludedLanguages = GeneralUtility::trimExplode(',', $excludedLanguages, true); @@ -38,7 +40,7 @@ public function compile(ContentObjectRenderer $contentObjectRenderer, array $con $context = clone GeneralUtility::makeInstance(Context::class); $pages = []; foreach ($site->getLanguages() as $language) { - if (in_array($language->getTwoLetterIsoCode(), $excludedLanguages, true)) { + if (in_array($this->getLanguageCode($language), $excludedLanguages, true)) { continue; } if (in_array((string)$language->getLanguageId(), $excludedLanguages, true)) { @@ -62,4 +64,12 @@ public function compile(ContentObjectRenderer $contentObjectRenderer, array $con return $pages; }); } + + protected function getLanguageCode(SiteLanguage $language): string + { + if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() === 11) { + return $language->getTwoLetterIsoCode(); + } + return $language->getLocale()->getLanguageCode(); + } } diff --git a/Classes/Compiler/ListMenuCompiler.php b/Classes/Compiler/ListMenuCompiler.php index 254f951..b4eabed 100644 --- a/Classes/Compiler/ListMenuCompiler.php +++ b/Classes/Compiler/ListMenuCompiler.php @@ -24,7 +24,7 @@ public function compile(ContentObjectRenderer $contentObjectRenderer, array $con $cacheIdentifier = $this->generateCacheIdentifierForMenu('list', $configuration); $pageIds = $contentObjectRenderer->stdWrap($configuration['pages'] ?? $this->getCurrentSite()->getRootPageId(), $configuration['pages.'] ?? []); - $pageIds = GeneralUtility::intExplode(',', $pageIds); + $pageIds = GeneralUtility::intExplode(',', (string)$pageIds); $cacheIdentifier .= '-' . substr(md5(json_encode([$pageIds])), 0, 10); diff --git a/Classes/Compiler/TreeMenuCompiler.php b/Classes/Compiler/TreeMenuCompiler.php index 2b70171..5e0117c 100644 --- a/Classes/Compiler/TreeMenuCompiler.php +++ b/Classes/Compiler/TreeMenuCompiler.php @@ -25,7 +25,7 @@ public function compile(ContentObjectRenderer $contentObjectRenderer, array $con $includeStartPageIds = $contentObjectRenderer->stdWrap($configuration['includeRootPages'] ?? false, $configuration['includeRootPages.'] ?? []); $startPageIds = $contentObjectRenderer->stdWrap($configuration['entryPoints'] ?? $this->getCurrentSite()->getRootPageId(), $configuration['entryPoints.'] ?? []); - $startPageIds = GeneralUtility::intExplode(',', $startPageIds); + $startPageIds = GeneralUtility::intExplode(',', (string)$startPageIds); $depth = (int)$contentObjectRenderer->stdWrap($configuration['depth'] ?? 1, $configuration['depth.'] ?? []); $excludePages = $this->parseStdWrap($configuration['excludePages'] ?? '', $configuration['excludePages.'] ?? []); $configuration['excludePages'] = $excludePages; diff --git a/Classes/ContentObject/BreadcrumbsContentObject.php b/Classes/ContentObject/BreadcrumbsContentObject.php index a514e84..5038c9c 100644 --- a/Classes/ContentObject/BreadcrumbsContentObject.php +++ b/Classes/ContentObject/BreadcrumbsContentObject.php @@ -13,6 +13,7 @@ use B13\Menus\Domain\Repository\MenuRepository; use B13\Menus\PageStateMarker; +use TYPO3\CMS\Core\Information\Typo3Version; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Frontend\ContentObject\AbstractContentObject; use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; @@ -22,23 +23,19 @@ */ class BreadcrumbsContentObject extends AbstractContentObject { - /** - * @var MenuRepository - */ - protected $menuRepository; + protected MenuRepository $menuRepository; - /** - * @param ContentObjectRenderer $cObj - */ public function __construct(ContentObjectRenderer $cObj) { - $this->menuRepository = GeneralUtility::makeInstance(MenuRepository::class); - parent::__construct($cObj); + if ((GeneralUtility::makeInstance(Typo3Version::class))->getMajorVersion() < 12) { + parent::__construct($cObj); + } + $this->menuRepository = (GeneralUtility::makeInstance(ContentObjectServiceContainer::class))->getMenuRepository(); } /** * @param array $conf - * @return string|void + * @return string */ public function render($conf = []) { @@ -49,7 +46,7 @@ public function render($conf = []) foreach ($pages as $page) { PageStateMarker::markStates($page, $rootLevelCount--); $cObjForItems->start($page, 'pages'); - $content .= $cObjForItems->cObjGetSingle($conf['renderObj'], $conf['renderObj.']); + $content .= $cObjForItems->cObjGetSingle($conf['renderObj'] ?? '', $conf['renderObj.'] ?? []); } return $this->cObj->stdWrap($content, $conf); } diff --git a/Classes/ContentObject/ContentObjectServiceContainer.php b/Classes/ContentObject/ContentObjectServiceContainer.php new file mode 100644 index 0000000..5f595f8 --- /dev/null +++ b/Classes/ContentObject/ContentObjectServiceContainer.php @@ -0,0 +1,62 @@ +languageMenuCompiler = $languageMenuCompiler; + $this->menuRepository = $menuRepository; + $this->listMenuCompiler = $listMenuCompiler; + $this->treeMenuCompiler = $treeMenuCompiler; + } + + public function getLanguageMenuCompiler(): LanguageMenuCompiler + { + return $this->languageMenuCompiler; + } + + public function getMenuRepository(): MenuRepository + { + return $this->menuRepository; + } + + public function getListMenuCompiler(): ListMenuCompiler + { + return $this->listMenuCompiler; + } + + public function getTreeMenuCompiler(): TreeMenuCompiler + { + return $this->treeMenuCompiler; + } +} diff --git a/Classes/ContentObject/LanguageMenuContentObject.php b/Classes/ContentObject/LanguageMenuContentObject.php index 24ed1a5..f4cd43d 100644 --- a/Classes/ContentObject/LanguageMenuContentObject.php +++ b/Classes/ContentObject/LanguageMenuContentObject.php @@ -13,6 +13,7 @@ use B13\Menus\Compiler\LanguageMenuCompiler; use B13\Menus\PageStateMarker; +use TYPO3\CMS\Core\Information\Typo3Version; use TYPO3\CMS\Core\Site\Entity\SiteLanguage; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Frontend\ContentObject\AbstractContentObject; @@ -23,13 +24,23 @@ */ class LanguageMenuContentObject extends AbstractContentObject { + protected LanguageMenuCompiler $languageMenuCompiler; + + public function __construct(ContentObjectRenderer $cObj) + { + if ((GeneralUtility::makeInstance(Typo3Version::class))->getMajorVersion() < 12) { + parent::__construct($cObj); + } + $this->languageMenuCompiler = (GeneralUtility::makeInstance(ContentObjectServiceContainer::class))->getLanguageMenuCompiler(); + } + /** * @param array $conf - * @return string|void + * @return string */ public function render($conf = []) { - $pages = GeneralUtility::makeInstance(LanguageMenuCompiler::class)->compile($this->cObj, $conf); + $pages = $this->languageMenuCompiler->compile($this->cObj, $conf); $content = $this->renderItems($pages, $conf); return $this->cObj->stdWrap($content, $conf); } @@ -47,7 +58,7 @@ protected function renderItems(array $pages, array $conf): string $page['isActiveLanguage'] = false; } $cObjForItems->start($page, 'pages'); - $content .= $cObjForItems->cObjGetSingle($conf['renderObj'], $conf['renderObj.']); + $content .= $cObjForItems->cObjGetSingle($conf['renderObj'] ?? '', $conf['renderObj.'] ?? []); } return $content; } diff --git a/Classes/ContentObject/ListMenuContentObject.php b/Classes/ContentObject/ListMenuContentObject.php index 44d732f..8d2433a 100644 --- a/Classes/ContentObject/ListMenuContentObject.php +++ b/Classes/ContentObject/ListMenuContentObject.php @@ -13,6 +13,7 @@ use B13\Menus\Compiler\ListMenuCompiler; use B13\Menus\PageStateMarker; +use TYPO3\CMS\Core\Information\Typo3Version; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Frontend\ContentObject\AbstractContentObject; use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; @@ -22,13 +23,23 @@ */ class ListMenuContentObject extends AbstractContentObject { + protected ListMenuCompiler $listMenuCompiler; + + public function __construct(ContentObjectRenderer $cObj) + { + if ((GeneralUtility::makeInstance(Typo3Version::class))->getMajorVersion() < 12) { + parent::__construct($cObj); + } + $this->listMenuCompiler = (GeneralUtility::makeInstance(ContentObjectServiceContainer::class))->getListMenuCompiler(); + } + /** * @param array $conf - * @return string|void + * @return string */ public function render($conf = []) { - $pages = GeneralUtility::makeInstance(ListMenuCompiler::class)->compile($this->cObj, $conf); + $pages = $this->listMenuCompiler->compile($this->cObj, $conf); $content = $this->renderItems($pages, $conf); return $this->cObj->stdWrap($content, $conf); } @@ -40,7 +51,7 @@ protected function renderItems(array $pages, array $conf): string foreach ($pages as $page) { PageStateMarker::markStates($page); $cObjForItems->start($page, 'pages'); - $content .= $cObjForItems->cObjGetSingle($conf['renderObj'], $conf['renderObj.']); + $content .= $cObjForItems->cObjGetSingle($conf['renderObj'] ?? '', $conf['renderObj.'] ?? []); } return $content; } diff --git a/Classes/ContentObject/TreeMenuContentObject.php b/Classes/ContentObject/TreeMenuContentObject.php index ea1d1ca..0e96274 100644 --- a/Classes/ContentObject/TreeMenuContentObject.php +++ b/Classes/ContentObject/TreeMenuContentObject.php @@ -13,6 +13,7 @@ use B13\Menus\Compiler\TreeMenuCompiler; use B13\Menus\PageStateMarker; +use TYPO3\CMS\Core\Information\Typo3Version; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Frontend\ContentObject\AbstractContentObject; use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; @@ -22,13 +23,23 @@ */ class TreeMenuContentObject extends AbstractContentObject { + protected TreeMenuCompiler $treeMenuCompiler; + + public function __construct(ContentObjectRenderer $cObj) + { + if ((GeneralUtility::makeInstance(Typo3Version::class))->getMajorVersion() < 12) { + parent::__construct($cObj); + } + $this->treeMenuCompiler = (GeneralUtility::makeInstance(ContentObjectServiceContainer::class))->getTreeMenuCompiler(); + } + /** * @param array $conf - * @return string|void + * @return string */ public function render($conf = []) { - $tree = GeneralUtility::makeInstance(TreeMenuCompiler::class)->compile($this->cObj, $conf); + $tree = $this->treeMenuCompiler->compile($this->cObj, $conf); $content = $this->renderItems($tree, 0, $conf['renderObj.'] ?? []); return $this->cObj->stdWrap($content, $conf); } @@ -47,7 +58,7 @@ protected function renderItems(array $pages, int $level, array $renderConfigurat $page['subpageContent'] = $this->renderItems($page['subpages'], $level++, $renderConfiguration); } $cObjForItems->start($page, 'pages'); - $content .= $cObjForItems->cObjGetSingle($renderConfiguration['level' . $level], $renderConfiguration['level' . $level . '.']); + $content .= $cObjForItems->cObjGetSingle($renderConfiguration['level' . $level] ?? '', $renderConfiguration['level' . $level . '.'] ?? []); } return $content; } diff --git a/Classes/DataProcessing/AbstractMenu.php b/Classes/DataProcessing/AbstractMenu.php index d995111..2d2e88c 100644 --- a/Classes/DataProcessing/AbstractMenu.php +++ b/Classes/DataProcessing/AbstractMenu.php @@ -21,28 +21,14 @@ */ abstract class AbstractMenu implements DataProcessorInterface { + protected ContentDataProcessor $contentDataProcessor; - /** - * @var ContentDataProcessor - */ - protected $contentDataProcessor; - - /** - * Constructor - */ - public function __construct(ContentDataProcessor $contentDataProcessor = null) + public function __construct(ContentDataProcessor $contentDataProcessor) { - $this->contentDataProcessor = $contentDataProcessor ?? GeneralUtility::makeInstance(ContentDataProcessor::class); + $this->contentDataProcessor = $contentDataProcessor; } - /** - * Process additional data processors - * - * @param array $page - * @param array $processorConfiguration - * @return array - */ - protected function processAdditionalDataProcessors(&$page, $processorConfiguration) + protected function processAdditionalDataProcessors(array &$page, array $processorConfiguration): array { if (isset($page['subpages']) && is_array($page['subpages'])) { foreach ($page['subpages'] as &$item) { diff --git a/Classes/DataProcessing/BreadcrumbsMenu.php b/Classes/DataProcessing/BreadcrumbsMenu.php index 6166d7a..ae5a85a 100644 --- a/Classes/DataProcessing/BreadcrumbsMenu.php +++ b/Classes/DataProcessing/BreadcrumbsMenu.php @@ -13,7 +13,6 @@ use B13\Menus\Domain\Repository\MenuRepository; use B13\Menus\PageStateMarker; -use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Frontend\ContentObject\ContentDataProcessor; use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; @@ -22,15 +21,12 @@ */ class BreadcrumbsMenu extends AbstractMenu { - /** - * @var MenuRepository - */ - protected $menuRepository; + protected MenuRepository $menuRepository; - public function __construct(ContentDataProcessor $contentDataProcessor = null, MenuRepository $menuRepository = null) + public function __construct(ContentDataProcessor $contentDataProcessor, MenuRepository $menuRepository) { parent::__construct($contentDataProcessor); - $this->menuRepository = $menuRepository ?? GeneralUtility::makeInstance(MenuRepository::class); + $this->menuRepository = $menuRepository; } /** diff --git a/Classes/DataProcessing/LanguageMenu.php b/Classes/DataProcessing/LanguageMenu.php index 44d93cd..754643a 100644 --- a/Classes/DataProcessing/LanguageMenu.php +++ b/Classes/DataProcessing/LanguageMenu.php @@ -14,7 +14,7 @@ use B13\Menus\Compiler\LanguageMenuCompiler; use B13\Menus\PageStateMarker; use TYPO3\CMS\Core\Site\Entity\SiteLanguage; -use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Frontend\ContentObject\ContentDataProcessor; use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; /** @@ -22,6 +22,14 @@ */ class LanguageMenu extends AbstractMenu { + protected $languageMenuCompliler; + + public function __construct(ContentDataProcessor $contentDataProcessor, LanguageMenuCompiler $languageMenuCompiler) + { + $this->languageMenuCompliler = $languageMenuCompiler; + parent::__construct($contentDataProcessor); + } + /** * @inheritDoc */ @@ -30,7 +38,7 @@ public function process(ContentObjectRenderer $cObj, array $contentObjectConfigu if (isset($processorConfiguration['if.']) && !$cObj->checkIf($processorConfiguration['if.'])) { return $processedData; } - $pages = GeneralUtility::makeInstance(LanguageMenuCompiler::class)->compile($cObj, $processorConfiguration); + $pages = $this->languageMenuCompliler->compile($cObj, $processorConfiguration); $currentLanguage = $this->getCurrentSiteLanguage(); foreach ($pages as &$page) { PageStateMarker::markStates($page); @@ -48,9 +56,6 @@ public function process(ContentObjectRenderer $cObj, array $contentObjectConfigu return $processedData; } - /** - * @return SiteLanguage|null - */ protected function getCurrentSiteLanguage(): ?SiteLanguage { return $GLOBALS['TYPO3_REQUEST']->getAttribute('language'); diff --git a/Classes/DataProcessing/ListMenu.php b/Classes/DataProcessing/ListMenu.php index 8e7c32e..0535eac 100644 --- a/Classes/DataProcessing/ListMenu.php +++ b/Classes/DataProcessing/ListMenu.php @@ -13,7 +13,7 @@ use B13\Menus\Compiler\ListMenuCompiler; use B13\Menus\PageStateMarker; -use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Frontend\ContentObject\ContentDataProcessor; use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; /** @@ -21,6 +21,14 @@ */ class ListMenu extends AbstractMenu { + protected ListMenuCompiler $listMenuCompiler; + + public function __construct(ContentDataProcessor $contentDataProcessor, ListMenuCompiler $listMenuCompiler) + { + $this->listMenuCompiler = $listMenuCompiler; + parent::__construct($contentDataProcessor); + } + /** * @inheritDoc */ @@ -30,7 +38,7 @@ public function process(ContentObjectRenderer $cObj, array $contentObjectConfigu return $processedData; } - $pages = GeneralUtility::makeInstance(ListMenuCompiler::class)->compile($cObj, $processorConfiguration); + $pages = $this->listMenuCompiler->compile($cObj, $processorConfiguration); foreach ($pages as &$page) { PageStateMarker::markStates($page); } diff --git a/Classes/DataProcessing/TreeMenu.php b/Classes/DataProcessing/TreeMenu.php index 9d7bfa3..1e2de85 100644 --- a/Classes/DataProcessing/TreeMenu.php +++ b/Classes/DataProcessing/TreeMenu.php @@ -13,7 +13,7 @@ use B13\Menus\Compiler\TreeMenuCompiler; use B13\Menus\PageStateMarker; -use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Frontend\ContentObject\ContentDataProcessor; use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; /** @@ -21,6 +21,13 @@ */ class TreeMenu extends AbstractMenu { + protected TreeMenuCompiler $treeMenuCompiler; + + public function __construct(ContentDataProcessor $contentDataProcessor, TreeMenuCompiler $treeMenuCompiler) + { + $this->treeMenuCompiler = $treeMenuCompiler; + parent::__construct($contentDataProcessor); + } /** * @inheritDoc @@ -30,7 +37,7 @@ public function process(ContentObjectRenderer $cObj, array $contentObjectConfigu if (isset($processorConfiguration['if.']) && !$cObj->checkIf($processorConfiguration['if.'])) { return $processedData; } - $pages = GeneralUtility::makeInstance(TreeMenuCompiler::class)->compile($cObj, $processorConfiguration); + $pages = $this->treeMenuCompiler->compile($cObj, $processorConfiguration); foreach ($pages as &$page) { PageStateMarker::markStatesRecursively($page, 1); } diff --git a/Classes/Domain/Repository/MenuRepository.php b/Classes/Domain/Repository/MenuRepository.php index 042a6b8..b9579a3 100644 --- a/Classes/Domain/Repository/MenuRepository.php +++ b/Classes/Domain/Repository/MenuRepository.php @@ -11,10 +11,13 @@ * of the License, or any later version. */ +use B13\Menus\Event\PopulatePageInformationEvent; +use Psr\EventDispatcher\EventDispatcherInterface; use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Context\LanguageAspect; +use TYPO3\CMS\Core\Domain\Repository\PageRepository; +use TYPO3\CMS\Core\Information\Typo3Version; use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Frontend\Page\PageRepository; /** * Responsible for interacting with the PageRepository class, in addition, should be responsible for overlays @@ -22,27 +25,24 @@ */ class MenuRepository { - /** - * @var Context - */ - protected $context; - - /** - * @var PageRepository - */ - protected $pageRepository; + protected Context $context; + protected PageRepository $pageRepository; + protected EventDispatcherInterface $eventDispatcher; // Never show or query them. protected $excludedDoktypes = [ PageRepository::DOKTYPE_BE_USER_SECTION, - PageRepository::DOKTYPE_RECYCLER, PageRepository::DOKTYPE_SYSFOLDER, ]; - public function __construct(Context $context = null, PageRepository $pageRepository = null) + public function __construct(Context $context, PageRepository $pageRepository, EventDispatcherInterface $eventDispatcher) { - $this->context = $context ?? GeneralUtility::makeInstance(Context::class); - $this->pageRepository = $pageRepository ?? GeneralUtility::makeInstance(PageRepository::class, $this->context); + $this->context = $context; + $this->pageRepository = $pageRepository; + $this->eventDispatcher = $eventDispatcher; + if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() < 13) { + $this->excludedDoktypes[] = PageRepository::DOKTYPE_RECYCLER; + } } public function getBreadcrumbsMenu(array $originalRootLine, array $configuration): array @@ -108,37 +108,25 @@ public function getPageTree(int $startPageId, int $depth, array $configuration): return $page; } - /** - * @param array $configuration - * @return array - */ protected function getExcludeDoktypes(array $configuration): array { if (!empty($configuration['excludeDoktypes'])) { - $excludedDoktypes = array_unique(array_merge($this->excludedDoktypes, GeneralUtility::intExplode(',', $configuration['excludeDoktypes']))); + $excludedDoktypes = array_unique(array_merge($this->excludedDoktypes, GeneralUtility::intExplode(',', (string)$configuration['excludeDoktypes']))); } else { $excludedDoktypes = $this->excludedDoktypes; } return $excludedDoktypes; } - /** - * @param array $configuration - * @return array - */ protected function getExcludePages(array $configuration): ?array { $excludePages = null; if (!empty($configuration['excludePages'])) { - $excludePages = array_unique(GeneralUtility::intExplode(',', $configuration['excludePages'])); + $excludePages = array_unique(GeneralUtility::intExplode(',', (string)$configuration['excludePages'])); } return empty($excludePages) ? null : $excludePages; } - /** - * @param array $configuration - * @return bool - */ protected function getIncludeNotInMenu(array $configuration): bool { return (int)($configuration['includeNotInMenu'] ?? 0) === 1; @@ -149,7 +137,7 @@ public function getSubPagesOfPage(int $pageId, int $depth, array $configuration) $whereClause = ''; if (!empty($configuration['excludePages'])) { - $excludedPagesArray = GeneralUtility::intExplode(',', $configuration['excludePages']); + $excludedPagesArray = GeneralUtility::intExplode(',', (string)$configuration['excludePages']); $whereClause .= ' AND uid NOT IN (' . implode(',', $excludedPagesArray) . ')'; } $excludedDoktypes = $this->getExcludeDoktypes($configuration); @@ -195,5 +183,9 @@ protected function populateAdditionalKeysForPage(array &$page): void $page['isSpacer'] = true; } $page['nav_title'] = $page['nav_title'] ?: $page['title']; + + $event = new PopulatePageInformationEvent($page); + $this->eventDispatcher->dispatch($event); + $page = $event->getPage(); } } diff --git a/Classes/Event/PopulatePageInformationEvent.php b/Classes/Event/PopulatePageInformationEvent.php new file mode 100644 index 0000000..cb9c9e5 --- /dev/null +++ b/Classes/Event/PopulatePageInformationEvent.php @@ -0,0 +1,25 @@ +page = $page; + } + + public function getPage(): array + { + return $this->page; + } + + public function setPage(array $page): void + { + $this->page = $page; + } +} diff --git a/Classes/Hooks/DataHandlerHook.php b/Classes/Hooks/DataHandlerHook.php index e5b8035..6e1e00f 100644 --- a/Classes/Hooks/DataHandlerHook.php +++ b/Classes/Hooks/DataHandlerHook.php @@ -14,7 +14,6 @@ use TYPO3\CMS\Core\Cache\CacheManager; use TYPO3\CMS\Core\DataHandling\DataHandler; -use TYPO3\CMS\Core\Utility\GeneralUtility; /** * This hook is triggered before caches for a page get flushed. @@ -23,6 +22,13 @@ */ class DataHandlerHook { + protected CacheManager $cacheManager; + + public function __construct(CacheManager $cacheManager) + { + $this->cacheManager = $cacheManager; + } + public function clearMenuCaches(array $params, DataHandler $dataHandler): void { $pageId = (int)($params['uid_page'] ?? 0); @@ -36,9 +42,6 @@ public function clearMenuCaches(array $params, DataHandler $dataHandler): void if ($parentPageId > 0) { $menuTags[] = 'menuId_' . $parentPageId; } - - /** @var CacheManager $cacheManager */ - $cacheManager = GeneralUtility::makeInstance(CacheManager::class); - $cacheManager->flushCachesByTags($menuTags); + $this->cacheManager->flushCachesByTags($menuTags); } } diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml new file mode 100644 index 0000000..cf0247c --- /dev/null +++ b/Configuration/Services.yaml @@ -0,0 +1,41 @@ +services: + _defaults: + autowire: true + autoconfigure: true + public: false + + B13\Menus\: + resource: '../Classes/*' + + B13\Menus\DataProcessing\BreadcrumbsMenu: + public: true + B13\Menus\DataProcessing\LanguageMenu: + public: true + B13\Menus\DataProcessing\ListMenu: + public: true + B13\Menus\DataProcessing\TreeMenu: + public: true + B13\Menus\ContentObject\ContentObjectServiceContainer: + public: true + B13\Menus\CacheHelper: + arguments: + $cache: '@cache.hash' + B13\Menus\Hooks\DataHandlerHook: + public: true + + B13\Menus\ContentObject\TreeMenuContentObject: + tags: + - name: frontend.contentobject + identifier: 'TREEMENU' + B13\Menus\ContentObject\ListMenuContentObject: + tags: + - name: frontend.contentobject + identifier: 'LISTMENU' + B13\Menus\ContentObject\LanguageMenuContentObject: + tags: + - name: frontend.contentobject + identifier: 'LANGUAGEMENU' + B13\Menus\ContentObject\BreadcrumbsContentObject: + tags: + - name: frontend.contentobject + identifier: 'BREADCRUMBS' diff --git a/README.md b/README.md index c003556..986f7d4 100644 --- a/README.md +++ b/README.md @@ -78,11 +78,11 @@ Pure TypoScript-based solution: page.10.excludePages = 4,51 # 0: default, 1 to include nav_hide = 1 pages page.10.includeNotInMenu = 0 - page.10.renderObj.level1 = TEXT - page.10.renderObj.level1.typolink.parameter.data = field:uid - page.10.renderObj.level1.typolink.ATagParams = class="active" - page.10.renderObj.level1.typolink.ATagParams.if.isTrue = field:isInRootLine - page.10.renderObj.level1.dataWrap =
  • |
      {field:subpageContent}
  • + page.10.renderObj.level0 = TEXT + page.10.renderObj.level0.typolink.parameter.data = field:uid + page.10.renderObj.level0.typolink.ATagParams = class="active" + page.10.renderObj.level0.typolink.ATagParams.if.isTrue.field = isInRootLine + page.10.renderObj.level0.dataWrap =
  • |
      {field:subpageContent}
  • Fluid-based solution: @@ -125,9 +125,11 @@ Pure TypoScript solution: # add all siteLanguages to menu even if page is not available in language (default 0) page.10.addAllSiteLanguages = 1 page.10.wrap =
      |
    - page.10.renderObj = TEXT page.10.renderObj.typolink.parameter.data = field:uid - page.10.renderObj.data = field:language_title // field:language_two_letter_iso_code + page.10.renderObj.typolink.additionalParams.data = field:language|languageId + page.10.renderObj.typolink.additionalParams.intval = 1 + page.10.renderObj.typolink.additionalParams.wrap = &L=| + page.10.renderObj.data = field:language|title // field:language|twoLetterIsoCode page.10.renderObj.wrap =
  • |
  • The stdWrap `data` is the information of the current page plus the information merged from the selected SiteLanguage. @@ -146,7 +148,7 @@ Fluid-based solution: Usage in Fluid: @@ -164,6 +166,10 @@ Pure TypoScript-based solution: page.10.pages = 13,14,15 # 0: default, 1 to include nav_hide = 1 pages page.10.includeNotInMenu = 0 + page.10.wrap =
      |
    + page.10.renderObj = TEXT + page.10.renderObj.typolink.parameter.data = field:uid + page.10.renderObj.wrap =
  • |
  • Fluid-based solution: @@ -199,7 +205,7 @@ Fluid-based solution: page.10 = FLUIDTEMPLATE page.10.dataProcessing.10 = B13\Menus\DataProcessing\BreadcrumbsMenu - page.10.dataProcessors.10.excludePages = 4,51 + page.10.dataProcessing.10.excludePages = 4,51 # 0: default, 1 to include nav_hide = 1 pages page.10.dataProcessing.10.includeNotInMenu = 0 page.10.dataProcessing.10.as = breadcrumbs @@ -223,7 +229,7 @@ If you want to get a menu of the direct siblings of a page, no matter what page as = listOfJobPages } -By using the `.data` property of the entryPointy attribute we can access each property of the currently build page. And so we can render the siblings of the page. +By using the `.data` property of the `entryPoints` attribute we can access each property of the currently build page. And so we can render the siblings of the page. ## Technical Details diff --git a/Tests/Functional/Compiler/LanguageMenuCompilerTest.php b/Tests/Functional/Compiler/LanguageMenuCompilerTest.php index d90b738..8869ac4 100644 --- a/Tests/Functional/Compiler/LanguageMenuCompilerTest.php +++ b/Tests/Functional/Compiler/LanguageMenuCompilerTest.php @@ -1,5 +1,7 @@ 'typo3conf/sites', ]; - protected $defaultPageDataSet = [ + protected array $defaultPageDataSet = [ 'defaultPage' => [ 'uid' => 1, 'pid' => 0, @@ -231,25 +239,43 @@ protected function compileMenu(array $pageDataset, array $configuration = []): a foreach ($pageDataset as $page) { $connection->insert('pages', $page); } - $controller = $this->getAccessibleMock( - TypoScriptFrontendController::class, - ['get_cache_timeout'], - [], - '', - false - ); + $controller = $this->getMockBuilder($this->buildAccessibleProxy(TypoScriptFrontendController::class)) + ->onlyMethods(['get_cache_timeout']) + ->disableOriginalConstructor() + ->getMock(); $GLOBALS['TSFE'] = $controller; - $GLOBALS['TSFE']->id = '1'; + if ((GeneralUtility::makeInstance(Typo3Version::class))->getMajorVersion() < 12) { + $GLOBALS['TSFE']->id = '1'; + } else { + $GLOBALS['TSFE']->id = 1; + } $contentObjectRenderer = new ContentObjectRenderer(); $siteFinder = GeneralUtility::makeInstance(SiteFinder::class); $site = $siteFinder->getSiteByIdentifier('main'); - $languageMenuCompiler = $this->getAccessibleMock( - LanguageMenuCompiler::class, - [ - 'generateCacheIdentifierForMenu', - 'getCurrentSite', - ] - ); + $context = $this->getMockBuilder(Context::class) + ->getMock(); + $pageRepository = GeneralUtility::makeInstance(PageRepository::class); + $menuRepository = GeneralUtility::makeInstance(MenuRepository::class, $context, $pageRepository, $this->createMock(EventDispatcherInterface::class)); + $cacheHelper = $this->getMockBuilder($this->buildAccessibleProxy(CacheHelper::class)) + ->onlyMethods([]) + ->disableOriginalConstructor() + ->getMock(); + $cacheHelper->_set('disableCaching', true); + $languageMenuCompiler = $this->getMockBuilder(LanguageMenuCompiler::class) + ->onlyMethods( + [ + 'generateCacheIdentifierForMenu', + 'getCurrentSite', + ] + ) + ->setConstructorArgs( + [ + $context, + $cacheHelper, + $menuRepository, + ] + ) + ->getMock(); $languageMenuCompiler->expects(self::any())->method('generateCacheIdentifierForMenu')->willReturn('foo'); $languageMenuCompiler->expects(self::any())->method('getCurrentSite')->willReturn($site); $menu = $languageMenuCompiler->compile($contentObjectRenderer, $configuration); diff --git a/Tests/Functional/DataProcessing/BreadcrumbsMenuTest.php b/Tests/Functional/DataProcessing/BreadcrumbsMenuTest.php index 1c635ff..f62373a 100644 --- a/Tests/Functional/DataProcessing/BreadcrumbsMenuTest.php +++ b/Tests/Functional/DataProcessing/BreadcrumbsMenuTest.php @@ -1,5 +1,7 @@ importDataSet(ORIGINAL_ROOT . 'typo3conf/ext/menus/Tests/Functional/Fixtures/pages.xml'); + $this->importCSVDataSet(__DIR__ . '/../Fixtures/pages.csv'); } protected function reduceResults(array $results): array @@ -65,33 +56,40 @@ protected function reduceResultsRecursive(array &$results): void $results = $this->reduceResults($results); } - protected function getTypoScriptFrontendController(SiteInterface $site, int $pageId): TypoScriptFrontendController + protected function getTypoScriptFrontendController(Site $site, int $pageId): TypoScriptFrontendController { if ((new Typo3Version())->getMajorVersion() < 11) { return GeneralUtility::makeInstance(TypoScriptFrontendController::class, null, $site, $site->getLanguageById(0)); } - $context = $this->prophesize(Context::class); - $context->hasAspect('frontend.preview')->willReturn(false); - $context->setAspect('frontend.preview', Argument::any()); - $siteLanguage = $this->prophesize(SiteLanguage::class); - $siteLanguage->getTypo3Language()->willReturn('default'); - $pageArguments = $this->prophesize(PageArguments::class); - $pageArguments->getPageid()->willReturn($pageId); - $pageArguments->getPageType()->willReturn(0); - $pageArguments->getArguments()->willReturn([]); - $frontendUserAuth = $this->prophesize(FrontendUserAuthentication::class); - - $controller = $this->getAccessibleMock( - TypoScriptFrontendController::class, - ['get_cache_timeout'], - [ - $context->reveal(), - $site, - $siteLanguage->reveal(), - $pageArguments->reveal(), - $frontendUserAuth->reveal(), - ] - ); + $context = $this->getMockBuilder(Context::class) + ->getMock(); + $context->expects(self::any())->method('hasAspect')->with('frontend.preview')->willReturn(false); + $context->expects(self::any())->method('setAspect'); + $siteLanguage = $this->getMockBuilder(SiteLanguage::class) + ->disableOriginalConstructor() + ->getMock(); + $siteLanguage->expects(self::any())->method('getTypo3Language')->willReturn('default'); + $pageArguments = $this->getMockBuilder(PageArguments::class) + ->disableOriginalConstructor() + ->getMock(); + $pageArguments->expects(self::any())->method('getPageId')->willReturn($pageId); + $pageArguments->expects(self::any())->method('getPageType')->willReturn('0'); + $pageArguments->expects(self::any())->method('getArguments')->willReturn([]); + $frontendUserAuth = $this->getMockBuilder(FrontendUserAuthentication::class) + ->disableOriginalConstructor() + ->getMock(); + $controller = $this->getMockBuilder($this->buildAccessibleProxy(TypoScriptFrontendController::class)) + ->onlyMethods(['get_cache_timeout']) + ->setConstructorArgs( + [ + $context, + $site, + $siteLanguage, + $pageArguments, + $frontendUserAuth, + ] + ) + ->getMock(); $controller->expects(self::any())->method('get_cache_timeout')->willReturn(1); return $controller; } diff --git a/Tests/Functional/DataProcessing/ListMenuProcessorTest.php b/Tests/Functional/DataProcessing/ListMenuProcessorTest.php index 8ba302e..5459d22 100644 --- a/Tests/Functional/DataProcessing/ListMenuProcessorTest.php +++ b/Tests/Functional/DataProcessing/ListMenuProcessorTest.php @@ -1,6 +1,8 @@ withAttribute('site', $site); $request = $request->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_FE); @@ -335,7 +335,7 @@ public function processTest(array $tsfe, array $configuration, array $expected) /** * @return array */ - public function cacheDataProvider() + public static function cacheDataProvider() { return [ [ @@ -367,7 +367,7 @@ public function cacheDataProvider() */ public function menuIdTagsAreAddedToPageCache(array $tsfe, array $configuration, array $expectedTags) { - $site = GeneralUtility::makeInstance(NullSite::class); + $site = GeneralUtility::makeInstance(Site::class, 'main', $tsfe['id'], []); $request = GeneralUtility::makeInstance(ServerRequest::class); $request = $request->withAttribute('site', $site); $request = $request->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_FE); diff --git a/Tests/Functional/DataProcessing/TreeMenuProcessorTest.php b/Tests/Functional/DataProcessing/TreeMenuProcessorTest.php index ca4df06..62d1b4f 100644 --- a/Tests/Functional/DataProcessing/TreeMenuProcessorTest.php +++ b/Tests/Functional/DataProcessing/TreeMenuProcessorTest.php @@ -1,5 +1,7 @@ withAttribute('site', $site); $request = $request->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_FE); @@ -415,7 +417,7 @@ public function processTest(array $tsfe, array $configuration, array $expected): /** * @return array */ - public function cacheDataProvider() + public static function cacheDataProvider() { return [ // entry point 2 @@ -460,7 +462,7 @@ public function cacheDataProvider() */ public function menuIdTagsAreAddedToPageCache(array $tsfe, int $entryPoints, array $expectedTags): void { - $site = GeneralUtility::makeInstance(NullSite::class); + $site = GeneralUtility::makeInstance(Site::class, 'main', $tsfe['id'], []); $request = GeneralUtility::makeInstance(ServerRequest::class); $request = $request->withAttribute('site', $site); $request = $request->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_FE); diff --git a/Tests/Functional/Domain/Repository/Fixtures/translated_page_with_nav_hide.csv b/Tests/Functional/Domain/Repository/Fixtures/translated_page_with_nav_hide.csv new file mode 100644 index 0000000..3db18cf --- /dev/null +++ b/Tests/Functional/Domain/Repository/Fixtures/translated_page_with_nav_hide.csv @@ -0,0 +1,4 @@ +"pages" +,"uid","pid","title","sys_language_uid",nav_hide,l10n_parent +,1,0,"page-1",0,0,0 +,2,0,"page-1",1,1,1 diff --git a/Tests/Functional/Domain/Repository/Fixtures/translated_page_with_nav_hide.xml b/Tests/Functional/Domain/Repository/Fixtures/translated_page_with_nav_hide.xml deleted file mode 100644 index 7e1c83b..0000000 --- a/Tests/Functional/Domain/Repository/Fixtures/translated_page_with_nav_hide.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - 1 - 0 - de - - - 1 - 0 - 0 - page-1 - 0 - 0 - - - 2 - 0 - 1 - page-1-de - 1 - 1 - - diff --git a/Tests/Functional/Domain/Repository/MenuRepositoryTest.php b/Tests/Functional/Domain/Repository/MenuRepositoryTest.php index d3689ec..d0aca6c 100644 --- a/Tests/Functional/Domain/Repository/MenuRepositoryTest.php +++ b/Tests/Functional/Domain/Repository/MenuRepositoryTest.php @@ -11,31 +11,28 @@ */ use B13\Menus\Domain\Repository\MenuRepository; +use Psr\EventDispatcher\EventDispatcherInterface; use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Context\LanguageAspect; +use TYPO3\CMS\Core\Domain\Repository\PageRepository; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; class MenuRepositoryTest extends FunctionalTestCase { - - /** - * @var array - */ - protected $testExtensionsToLoad = [ - 'typo3conf/ext/menus', - ]; + protected array $testExtensionsToLoad = ['typo3conf/ext/menus']; /** * @test */ public function translatedPageIsNotInMenuIfNavHideIsSet(): void { - $this->importDataSet(ORIGINAL_ROOT . 'typo3conf/ext/menus/Tests/Functional/Domain/Repository/Fixtures/translated_page_with_nav_hide.xml'); + $this->importCSVDataSet(__DIR__ . '/Fixtures/translated_page_with_nav_hide.csv'); $languageAspect = GeneralUtility::makeInstance(LanguageAspect::class, 1); $context = GeneralUtility::makeInstance(Context::class); $context->setAspect('language', $languageAspect); - $menuRepository = GeneralUtility::makeInstance(MenuRepository::class); + $pageRepository = GeneralUtility::makeInstance(PageRepository::class); + $menuRepository = GeneralUtility::makeInstance(MenuRepository::class, $context, $pageRepository, $this->createMock(EventDispatcherInterface::class)); $page = $menuRepository->getPage(1, []); $pageInLanguage = $menuRepository->getPageInLanguage(1, $context, []); self::assertSame([], $page); @@ -47,11 +44,12 @@ public function translatedPageIsNotInMenuIfNavHideIsSet(): void */ public function translatedPageIsInMenuIfNavHideAndIgnoreNavHideIsSet(): void { - $this->importDataSet(ORIGINAL_ROOT . 'typo3conf/ext/menus/Tests/Functional/Domain/Repository/Fixtures/translated_page_with_nav_hide.xml'); + $this->importCSVDataSet(__DIR__ . '/Fixtures/translated_page_with_nav_hide.csv'); $languageAspect = GeneralUtility::makeInstance(LanguageAspect::class, 1); $context = GeneralUtility::makeInstance(Context::class); $context->setAspect('language', $languageAspect); - $menuRepository = GeneralUtility::makeInstance(MenuRepository::class); + $pageRepository = GeneralUtility::makeInstance(PageRepository::class); + $menuRepository = GeneralUtility::makeInstance(MenuRepository::class, $context, $pageRepository, $this->createMock(EventDispatcherInterface::class)); $page = $menuRepository->getPage(1, ['includeNotInMenu' => 1]); $pageInLanguage = $menuRepository->getPageInLanguage(1, $context, ['includeNotInMenu' => 1]); $page = $this->reduceResults($page); diff --git a/Tests/Functional/Fixtures/be_users.csv b/Tests/Functional/Fixtures/be_users.csv new file mode 100644 index 0000000..bfc3c6b --- /dev/null +++ b/Tests/Functional/Fixtures/be_users.csv @@ -0,0 +1,4 @@ +"be_users" +,"uid","pid","tstamp","username","password","admin","disable","starttime","endtime","options","crdate","workspace_perms","deleted","TSconfig","lastlogin","workspace_id" +# The password is "password" +,1,0,1366642540,"admin","$1$tCrlLajZ$C0sikFQQ3SWaFAZ1Me0Z/1",1,0,0,0,0,1366642540,1,0,,1371033743,0 \ No newline at end of file diff --git a/Tests/Functional/Fixtures/caches.csv b/Tests/Functional/Fixtures/caches.csv new file mode 100644 index 0000000..1984f52 --- /dev/null +++ b/Tests/Functional/Fixtures/caches.csv @@ -0,0 +1,7 @@ +"cache_pages" +,"identifier" +,"foo" +"cache_pages_tags" +,"identifier","tag" +,"foo","pageId_1" +,"foo","menuId_2" diff --git a/Tests/Functional/Fixtures/caches.xml b/Tests/Functional/Fixtures/caches.xml deleted file mode 100644 index a39244f..0000000 --- a/Tests/Functional/Fixtures/caches.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - foo - - - foo - pageId_1 - - - foo - menuId_2 - - diff --git a/Tests/Functional/Fixtures/pages.csv b/Tests/Functional/Fixtures/pages.csv new file mode 100644 index 0000000..ffdef0f --- /dev/null +++ b/Tests/Functional/Fixtures/pages.csv @@ -0,0 +1,8 @@ +"pages" +,"uid","pid","title","slug",nav_hide,doktype +,1,0,"page-1","/page-1",0,0 +,2,1,"page-2","/page-1/page-2",0,99 +,3,2,"page-3","/page-1/page-2/page-3",0,0 +,4,1,"page-4","/page-1/page-4",0,0 +,5,2,"page-5","/page-1/page-2/page-5",1,0 +,6,1,"page-6","/page-1/page-6",1,0 diff --git a/Tests/Functional/Fixtures/pages.xml b/Tests/Functional/Fixtures/pages.xml deleted file mode 100644 index 11d1eb3..0000000 --- a/Tests/Functional/Fixtures/pages.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - 1 - 0 - page-1 - /page-1 - 0 - - - 2 - 1 - 99 - page-2 - /page-1/page-2 - 0 - - - 3 - 2 - page-3 - /page-1/page-2/page-3 - 0 - - - 4 - 1 - page-4 - /page-1/page-4 - 0 - - - 5 - 2 - page-5 - /page-1/page-2/page-5 - 1 - - - 6 - 1 - page-6 - /page-1/page-6 - 1 - - diff --git a/Tests/Functional/Frontend/BreadcrumbMenuContentObjectTest.php b/Tests/Functional/Frontend/BreadcrumbMenuContentObjectTest.php new file mode 100644 index 0000000..72f153f --- /dev/null +++ b/Tests/Functional/Frontend/BreadcrumbMenuContentObjectTest.php @@ -0,0 +1,47 @@ + 'typo3conf/sites']; + + /** + * @test + */ + public function menuOnRootPage(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/pages.csv'); + $this->importCSVDataSet(__DIR__ . '/Fixtures/breadcrumb_menu_content_object_typoscript.csv'); + $response = $this->executeFrontendSubRequest(new InternalRequest('http://localhost/')); + $expected = 'root'; + $body = (string)$response->getBody(); + self::assertStringContainsString($expected, $body); + } + + /** + * @test + */ + public function menuOnSubpage(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/pages.csv'); + $this->importCSVDataSet(__DIR__ . '/Fixtures/breadcrumb_menu_content_object_typoscript.csv'); + $response = $this->executeFrontendSubRequest(new InternalRequest('http://localhost/page-1')); + $expected = 'rootpage-1'; + $body = (string)$response->getBody(); + self::assertStringContainsString($expected, $body); + } +} diff --git a/Tests/Functional/Frontend/BreadcrumbMenuFluidTest.php b/Tests/Functional/Frontend/BreadcrumbMenuFluidTest.php new file mode 100644 index 0000000..a028ab9 --- /dev/null +++ b/Tests/Functional/Frontend/BreadcrumbMenuFluidTest.php @@ -0,0 +1,47 @@ + 'typo3conf/sites']; + + /** + * @test + */ + public function menuOnRootPage(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/pages.csv'); + $this->importCSVDataSet(__DIR__ . '/Fixtures/breadcrumb_menu_fluid_typoscript.csv'); + $response = $this->executeFrontendSubRequest(new InternalRequest('http://localhost/')); + $expected = 'root'; + $body = (string)$response->getBody(); + self::assertStringContainsString($expected, $body); + } + + /** + * @test + */ + public function menuOnSubpage(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/pages.csv'); + $this->importCSVDataSet(__DIR__ . '/Fixtures/breadcrumb_menu_fluid_typoscript.csv'); + $response = $this->executeFrontendSubRequest(new InternalRequest('http://localhost/page-1')); + $expected = 'rootpage-1'; + $body = (string)$response->getBody(); + self::assertStringContainsString($expected, $body); + } +} diff --git a/Tests/Functional/Frontend/Fixtures/Templates/Breadcrumbs.html b/Tests/Functional/Frontend/Fixtures/Templates/Breadcrumbs.html new file mode 100644 index 0000000..a5df2bf --- /dev/null +++ b/Tests/Functional/Frontend/Fixtures/Templates/Breadcrumbs.html @@ -0,0 +1,7 @@ + + + + {page.title} + + + diff --git a/Tests/Functional/Frontend/Fixtures/Templates/LanguageMenu.html b/Tests/Functional/Frontend/Fixtures/Templates/LanguageMenu.html new file mode 100644 index 0000000..dc81751 --- /dev/null +++ b/Tests/Functional/Frontend/Fixtures/Templates/LanguageMenu.html @@ -0,0 +1,7 @@ + + + + {item.language.title} + + + diff --git a/Tests/Functional/Frontend/Fixtures/Templates/ListMenu.html b/Tests/Functional/Frontend/Fixtures/Templates/ListMenu.html new file mode 100644 index 0000000..243f308 --- /dev/null +++ b/Tests/Functional/Frontend/Fixtures/Templates/ListMenu.html @@ -0,0 +1,7 @@ + + + + {page.title} + + + diff --git a/Tests/Functional/Frontend/Fixtures/Templates/TreeMenu.html b/Tests/Functional/Frontend/Fixtures/Templates/TreeMenu.html new file mode 100644 index 0000000..fea496d --- /dev/null +++ b/Tests/Functional/Frontend/Fixtures/Templates/TreeMenu.html @@ -0,0 +1,7 @@ + + + + {page.title} + + + diff --git a/Tests/Functional/Frontend/Fixtures/access_restriction.csv b/Tests/Functional/Frontend/Fixtures/access_restriction.csv new file mode 100644 index 0000000..0830e1f --- /dev/null +++ b/Tests/Functional/Frontend/Fixtures/access_restriction.csv @@ -0,0 +1,11 @@ +"pages" +,"uid","pid","title","slug","fe_group" +,1,0,"root","/","" +,2,1,"page-1","/page-1","" +,3,1,"page-2","/page-2","1" +"fe_groups" +,"uid","pid" +,1,1 +"fe_users" +,"uid","pid","usergroup" +,1,1,"1" diff --git a/Tests/Functional/Frontend/Fixtures/breadcrumb_menu_content_object_typoscript.csv b/Tests/Functional/Frontend/Fixtures/breadcrumb_menu_content_object_typoscript.csv new file mode 100644 index 0000000..8c34e8e --- /dev/null +++ b/Tests/Functional/Frontend/Fixtures/breadcrumb_menu_content_object_typoscript.csv @@ -0,0 +1,8 @@ +"sys_template" +,"uid","pid","root","config" +,1,1,1,"page = PAGE +page.config.disableAllHeaderCode = 1 +page.20 = BREADCRUMBS +page.20.renderObj = TEXT +page.20.renderObj.typolink.parameter.data = field:uid +" diff --git a/Tests/Functional/Frontend/Fixtures/breadcrumb_menu_fluid_typoscript.csv b/Tests/Functional/Frontend/Fixtures/breadcrumb_menu_fluid_typoscript.csv new file mode 100644 index 0000000..495eb3b --- /dev/null +++ b/Tests/Functional/Frontend/Fixtures/breadcrumb_menu_fluid_typoscript.csv @@ -0,0 +1,9 @@ +"sys_template" +,"uid","pid","root","config" +,1,1,1,"page = PAGE +page.config.disableAllHeaderCode = 1 +page.10 = FLUIDTEMPLATE +page.10.templateRootPaths.10 = EXT:menus/Tests/Functional/Frontend/Fixtures/Templates +page.10.templateName = Breadcrumbs.html +page.10.dataProcessing.10 = B13\Menus\DataProcessing\BreadcrumbsMenu +" diff --git a/Tests/Functional/Frontend/Fixtures/language_menu_content_object_typoscript.csv b/Tests/Functional/Frontend/Fixtures/language_menu_content_object_typoscript.csv new file mode 100644 index 0000000..4e3f4d8 --- /dev/null +++ b/Tests/Functional/Frontend/Fixtures/language_menu_content_object_typoscript.csv @@ -0,0 +1,14 @@ +"sys_template" +,"uid","pid","root","config" +,1,1,1,"page = PAGE +page.config.disableAllHeaderCode = 1 +page.20 = LANGUAGEMENU +page.20.renderObj = TEXT +page.20.renderObj.typolink.parameter.data = field:uid +page.20.renderObj.typolink.ATagParams = class='active' +page.20.renderObj.typolink.ATagParams.if.isTrue.field = isActiveLanguage +page.20.renderObj.typolink.additionalParams.data = field:language|languageId +page.20.renderObj.typolink.additionalParams.intval = 1 +page.20.renderObj.typolink.additionalParams.wrap = &L=| +page.20.renderObj.data = field:language|title // field:language|twoLetterIsoCode +" diff --git a/Tests/Functional/Frontend/Fixtures/language_menu_fluid_typoscript.csv b/Tests/Functional/Frontend/Fixtures/language_menu_fluid_typoscript.csv new file mode 100644 index 0000000..beb6fe6 --- /dev/null +++ b/Tests/Functional/Frontend/Fixtures/language_menu_fluid_typoscript.csv @@ -0,0 +1,10 @@ +"sys_template" +,"uid","pid","root","config" +,1,1,1,"page = PAGE +page.config.disableAllHeaderCode = 1 +page.10 = FLUIDTEMPLATE +page.10.templateRootPaths.10 = EXT:menus/Tests/Functional/Frontend/Fixtures/Templates +page.10.templateName = LanguageMenu.html +page.10.dataProcessing.10 = B13\Menus\DataProcessing\LanguageMenu +page.10.dataProcessing.10.as = menu +" diff --git a/Tests/Functional/Frontend/Fixtures/list_menu_content_object_typoscript.csv b/Tests/Functional/Frontend/Fixtures/list_menu_content_object_typoscript.csv new file mode 100644 index 0000000..6fd3927 --- /dev/null +++ b/Tests/Functional/Frontend/Fixtures/list_menu_content_object_typoscript.csv @@ -0,0 +1,9 @@ +"sys_template" +,"uid","pid","root","config" +,1,1,1,"page = PAGE +page.config.disableAllHeaderCode = 1 +page.20 = LISTMENU +page.20.pages = 2 +page.20.renderObj = TEXT +page.20.renderObj.typolink.parameter.data = field:uid +" diff --git a/Tests/Functional/Frontend/Fixtures/list_menu_fluid_typoscript.csv b/Tests/Functional/Frontend/Fixtures/list_menu_fluid_typoscript.csv new file mode 100644 index 0000000..b45e981 --- /dev/null +++ b/Tests/Functional/Frontend/Fixtures/list_menu_fluid_typoscript.csv @@ -0,0 +1,11 @@ +"sys_template" +,"uid","pid","root","config" +,1,1,1,"page = PAGE +page.config.disableAllHeaderCode = 1 +page.10 = FLUIDTEMPLATE +page.10.templateRootPaths.10 = EXT:menus/Tests/Functional/Frontend/Fixtures/Templates +page.10.templateName = ListMenu.html +page.10.dataProcessing.10 = B13\Menus\DataProcessing\ListMenu +page.10.dataProcessing.10.pages = 2 +page.10.dataProcessing.10.as = menu +" diff --git a/Tests/Functional/Frontend/Fixtures/pages.csv b/Tests/Functional/Frontend/Fixtures/pages.csv new file mode 100644 index 0000000..f2918cf --- /dev/null +++ b/Tests/Functional/Frontend/Fixtures/pages.csv @@ -0,0 +1,5 @@ +"pages" +,"uid","pid","title","slug" +,1,0,"root","/" +,2,1,"page-1","/page-1" +,3,1,"page-2","/page-2" diff --git a/Tests/Functional/Frontend/Fixtures/translated_pages.csv b/Tests/Functional/Frontend/Fixtures/translated_pages.csv new file mode 100644 index 0000000..ae3a9b6 --- /dev/null +++ b/Tests/Functional/Frontend/Fixtures/translated_pages.csv @@ -0,0 +1,5 @@ +"pages" +,"uid","pid","title","slug","l10n_parent","sys_language_uid" +,11,0,"root-de","/",1,1 +,12,1,"page-de-1","/page-1",2,1 +,13,1,"page-de-2","/page-2",3,1 diff --git a/Tests/Functional/Frontend/Fixtures/tree_menu_content_object_typoscript.csv b/Tests/Functional/Frontend/Fixtures/tree_menu_content_object_typoscript.csv new file mode 100644 index 0000000..8a37b2d --- /dev/null +++ b/Tests/Functional/Frontend/Fixtures/tree_menu_content_object_typoscript.csv @@ -0,0 +1,10 @@ +"sys_template" +,"uid","pid","root","config" +,1,1,1,"page = PAGE +page.config.disableAllHeaderCode = 1 +page.20 = TREEMENU +page.20.renderObj.level0 = TEXT +page.20.renderObj.level0.typolink.parameter.data = field:uid +page.20.renderObj.level0.typolink.ATagParams = class='active' +page.20.renderObj.level0.typolink.ATagParams.if.isTrue.field = isInRootLine +" diff --git a/Tests/Functional/Frontend/Fixtures/tree_menu_fluid_typoscript.csv b/Tests/Functional/Frontend/Fixtures/tree_menu_fluid_typoscript.csv new file mode 100644 index 0000000..58e1bf9 --- /dev/null +++ b/Tests/Functional/Frontend/Fixtures/tree_menu_fluid_typoscript.csv @@ -0,0 +1,10 @@ +"sys_template" +,"uid","pid","root","config" +,1,1,1,"page = PAGE +page.config.disableAllHeaderCode = 1 +page.10 = FLUIDTEMPLATE +page.10.templateRootPaths.10 = EXT:menus/Tests/Functional/Frontend/Fixtures/Templates +page.10.templateName = TreeMenu.html +page.10.dataProcessing.10 = B13\Menus\DataProcessing\TreeMenu +page.10.dataProcessing.10.as = menu +" diff --git a/Tests/Functional/Frontend/LanguageMenuContentObjectTest.php b/Tests/Functional/Frontend/LanguageMenuContentObjectTest.php new file mode 100644 index 0000000..23fac1c --- /dev/null +++ b/Tests/Functional/Frontend/LanguageMenuContentObjectTest.php @@ -0,0 +1,49 @@ + 'typo3conf/sites']; + + /** + * @test + */ + public function menuOnRootPage(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/pages.csv'); + $this->importCSVDataSet(__DIR__ . '/Fixtures/translated_pages.csv'); + $this->importCSVDataSet(__DIR__ . '/Fixtures/language_menu_content_object_typoscript.csv'); + $response = $this->executeFrontendSubRequest(new InternalRequest('http://localhost/')); + $expected = 'englishgerman'; + $body = (string)$response->getBody(); + self::assertStringContainsString($expected, $body); + } + + /** + * @test + */ + public function menuOnSubpage(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/pages.csv'); + $this->importCSVDataSet(__DIR__ . '/Fixtures/translated_pages.csv'); + $this->importCSVDataSet(__DIR__ . '/Fixtures/language_menu_content_object_typoscript.csv'); + $response = $this->executeFrontendSubRequest(new InternalRequest('http://localhost/de/')); + $expected = 'englishgerman'; + $body = (string)$response->getBody(); + self::assertStringContainsString($expected, $body); + } +} diff --git a/Tests/Functional/Frontend/LanguageMenuFluidTest.php b/Tests/Functional/Frontend/LanguageMenuFluidTest.php new file mode 100644 index 0000000..181a0f7 --- /dev/null +++ b/Tests/Functional/Frontend/LanguageMenuFluidTest.php @@ -0,0 +1,49 @@ + 'typo3conf/sites']; + + /** + * @test + */ + public function menuOnRootPage(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/pages.csv'); + $this->importCSVDataSet(__DIR__ . '/Fixtures/translated_pages.csv'); + $this->importCSVDataSet(__DIR__ . '/Fixtures/language_menu_fluid_typoscript.csv'); + $response = $this->executeFrontendSubRequest(new InternalRequest('http://localhost/')); + $expected = 'englishgerman'; + $body = (string)$response->getBody(); + self::assertStringContainsString($expected, $body); + } + + /** + * @test + */ + public function menuOnSubpage(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/pages.csv'); + $this->importCSVDataSet(__DIR__ . '/Fixtures/translated_pages.csv'); + $this->importCSVDataSet(__DIR__ . '/Fixtures/language_menu_fluid_typoscript.csv'); + $response = $this->executeFrontendSubRequest(new InternalRequest('http://localhost/de/')); + $expected = 'englishgerman'; + $body = (string)$response->getBody(); + self::assertStringContainsString($expected, $body); + } +} diff --git a/Tests/Functional/Frontend/ListMenuContentObjectTest.php b/Tests/Functional/Frontend/ListMenuContentObjectTest.php new file mode 100644 index 0000000..2f8cf88 --- /dev/null +++ b/Tests/Functional/Frontend/ListMenuContentObjectTest.php @@ -0,0 +1,47 @@ + 'typo3conf/sites']; + + /** + * @test + */ + public function menuOnRootPage(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/pages.csv'); + $this->importCSVDataSet(__DIR__ . '/Fixtures/list_menu_content_object_typoscript.csv'); + $response = $this->executeFrontendSubRequest(new InternalRequest('http://localhost/')); + $expected = 'page-1'; + $body = (string)$response->getBody(); + self::assertStringContainsString($expected, $body); + } + + /** + * @test + */ + public function menuOnSubpage(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/pages.csv'); + $this->importCSVDataSet(__DIR__ . '/Fixtures/list_menu_content_object_typoscript.csv'); + $response = $this->executeFrontendSubRequest(new InternalRequest('http://localhost/page-1')); + $expected = 'page-1'; + $body = (string)$response->getBody(); + self::assertStringContainsString($expected, $body); + } +} diff --git a/Tests/Functional/Frontend/ListMenuFluidTest.php b/Tests/Functional/Frontend/ListMenuFluidTest.php new file mode 100644 index 0000000..270851d --- /dev/null +++ b/Tests/Functional/Frontend/ListMenuFluidTest.php @@ -0,0 +1,47 @@ + 'typo3conf/sites']; + + /** + * @test + */ + public function menuOnRootPage(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/pages.csv'); + $this->importCSVDataSet(__DIR__ . '/Fixtures/list_menu_fluid_typoscript.csv'); + $response = $this->executeFrontendSubRequest(new InternalRequest('http://localhost/')); + $expected = 'page-1'; + $body = (string)$response->getBody(); + self::assertStringContainsString($expected, $body); + } + + /** + * @test + */ + public function menuOnSubpage(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/pages.csv'); + $this->importCSVDataSet(__DIR__ . '/Fixtures/list_menu_fluid_typoscript.csv'); + $response = $this->executeFrontendSubRequest(new InternalRequest('http://localhost/page-1')); + $expected = 'page-1'; + $body = (string)$response->getBody(); + self::assertStringContainsString($expected, $body); + } +} diff --git a/Tests/Functional/Frontend/TreeMenuContentObjectTest.php b/Tests/Functional/Frontend/TreeMenuContentObjectTest.php new file mode 100644 index 0000000..0a979f9 --- /dev/null +++ b/Tests/Functional/Frontend/TreeMenuContentObjectTest.php @@ -0,0 +1,47 @@ + 'typo3conf/sites']; + + /** + * @test + */ + public function menuOnRootPage(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/pages.csv'); + $this->importCSVDataSet(__DIR__ . '/Fixtures/tree_menu_content_object_typoscript.csv'); + $response = $this->executeFrontendSubRequest(new InternalRequest('http://localhost/')); + $expected = 'page-1page-2'; + $body = (string)$response->getBody(); + self::assertStringContainsString($expected, $body); + } + + /** + * @test + */ + public function menuOnSubpage(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/pages.csv'); + $this->importCSVDataSet(__DIR__ . '/Fixtures/tree_menu_content_object_typoscript.csv'); + $response = $this->executeFrontendSubRequest(new InternalRequest('http://localhost/page-1')); + $expected = 'page-1page-2'; + $body = (string)$response->getBody(); + self::assertStringContainsString($expected, $body); + } +} diff --git a/Tests/Functional/Frontend/TreeMenuFluidTest.php b/Tests/Functional/Frontend/TreeMenuFluidTest.php new file mode 100644 index 0000000..c1bf894 --- /dev/null +++ b/Tests/Functional/Frontend/TreeMenuFluidTest.php @@ -0,0 +1,76 @@ + 'typo3conf/sites']; + + /** + * @test + */ + public function menuOnRootPage(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/pages.csv'); + $this->importCSVDataSet(__DIR__ . '/Fixtures/tree_menu_fluid_typoscript.csv'); + $response = $this->executeFrontendSubRequest(new InternalRequest('http://localhost/')); + $expected = 'page-1page-2'; + $body = (string)$response->getBody(); + self::assertStringContainsString($expected, $body); + } + + /** + * @test + */ + public function menuOnSubpage(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/pages.csv'); + $this->importCSVDataSet(__DIR__ . '/Fixtures/tree_menu_fluid_typoscript.csv'); + $response = $this->executeFrontendSubRequest(new InternalRequest('http://localhost/page-1')); + $expected = 'page-1page-2'; + $body = (string)$response->getBody(); + self::assertStringContainsString($expected, $body); + } + + /** + * @test + */ + public function menuWithAccessRestrictionForNotLoggedinUser(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/access_restriction.csv'); + $this->importCSVDataSet(__DIR__ . '/Fixtures/tree_menu_fluid_typoscript.csv'); + $response = $this->executeFrontendSubRequest(new InternalRequest('http://localhost/')); + $body = (string)$response->getBody(); + self::assertStringContainsString('page-1', $body); + self::assertStringNotContainsString('page-2', $body); + } + + /** + * @test + */ + public function menuWithAccessRestrictionForLoggedinUser(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/access_restriction.csv'); + $this->importCSVDataSet(__DIR__ . '/Fixtures/tree_menu_fluid_typoscript.csv'); + $context = (new InternalRequestContext())->withFrontendUserId(1); + $request = new InternalRequest('http://localhost/'); + $response = $this->executeFrontendSubRequest($request, $context); + $body = (string)$response->getBody(); + self::assertStringContainsString('page-1', $body); + self::assertStringContainsString('page-2', $body); + } +} diff --git a/Tests/Functional/Hooks/DataHandlerTest.php b/Tests/Functional/Hooks/DataHandlerTest.php index bc18e6e..3887bc1 100644 --- a/Tests/Functional/Hooks/DataHandlerTest.php +++ b/Tests/Functional/Hooks/DataHandlerTest.php @@ -11,51 +11,44 @@ */ use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; -use TYPO3\CMS\Core\Core\Bootstrap; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\DataHandling\DataHandler; use TYPO3\CMS\Core\Information\Typo3Version; +use TYPO3\CMS\Core\Localization\LanguageServiceFactory; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; class DataHandlerTest extends FunctionalTestCase { + protected DataHandler $dataHandler; + protected BackendUserAuthentication $backendUser; - /** - * @var DataHandler - */ - protected $dataHandler; + protected array $testExtensionsToLoad = ['typo3conf/ext/menus']; - /** - * @var BackendUserAuthentication - */ - protected $backendUser; - - /** - * @var array - */ - protected $testExtensionsToLoad = [ - 'typo3conf/ext/menus', + protected array $configurationToUseInTestInstance = [ + 'SYS' => [ + 'caching' => [ + 'cacheConfigurations' => [ + 'pages' => [ + 'backend' => \TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend::class, + ], + ], + ], + ], ]; - /** - * @throws \Doctrine\DBAL\DBALException - * @throws \TYPO3\TestingFramework\Core\Exception - */ protected function setUp(): void { parent::setUp(); - $this->importDataSet(ORIGINAL_ROOT . 'typo3conf/ext/menus/Tests/Functional/Fixtures/pages.xml'); - $this->importDataSet(ORIGINAL_ROOT . 'typo3conf/ext/menus/Tests/Functional/Fixtures/caches.xml'); - $this->backendUser = $this->setUpBackendUserFromFixture(1); - Bootstrap::initializeLanguageObject(); + $this->importCSVDataSet(__DIR__ . '/../Fixtures/pages.csv'); + $this->importCSVDataSet(__DIR__ . '/../Fixtures/caches.csv'); + $this->importCSVDataSet(__DIR__ . '/../Fixtures/be_users.csv'); + $this->backendUser = $GLOBALS['BE_USER'] = $this->setUpBackendUser(1); + $GLOBALS['LANG'] = GeneralUtility::makeInstance(LanguageServiceFactory::class)->createFromUserPreferences($GLOBALS['BE_USER']); $this->dataHandler = GeneralUtility::makeInstance(DataHandler::class); } - /** - * @return array - */ - public function cmdmapDataProvider() + public static function cmdmapDataProvider(): array { return [ 'copy page' => ['cmdmap' => ['pages' => [ @@ -111,8 +104,8 @@ protected function assertCacheIsEmpty(): void } $rows = $queryBuilder->select('*') ->from('cache_pages') - ->execute() - ->fetchAll(); + ->executeQuery() + ->fetchAllAssociative(); self::assertSame(0, count($rows)); } } diff --git a/Tests/Unit/DataProcessing/BreadcrumbsMenuTest.php b/Tests/Unit/DataProcessing/BreadcrumbsMenuTest.php index e1d51d8..0b3ec7b 100644 --- a/Tests/Unit/DataProcessing/BreadcrumbsMenuTest.php +++ b/Tests/Unit/DataProcessing/BreadcrumbsMenuTest.php @@ -1,5 +1,7 @@ 2], ]; $GLOBALS['TSFE']->id = 2; - $menuRepository = $this->prophesize(MenuRepository::class); - $menuRepository->getBreadcrumbsMenu($GLOBALS['TSFE']->rootLine, [])->willReturn($pages); - $contentDataProcessor = $this->prophesize(ContentDataProcessor::class); - $contentObjectRenderer = $this->prophesize(ContentObjectRenderer::class); + $menuRepository = $this->getMockBuilder(MenuRepository::class) + ->disableOriginalConstructor() + ->getMock(); + $menuRepository->expects(self::once())->method('getBreadcrumbsMenu')->with($GLOBALS['TSFE']->rootLine, [])->willReturn($pages); + $contentDataProcessor = $this->getMockBuilder(ContentDataProcessor::class) + ->disableOriginalConstructor() + ->getMock(); + $contentObjectRenderer = $this->getMockBuilder(ContentObjectRenderer::class) + ->disableOriginalConstructor() + ->getMock(); + $contentObjectRenderer->expects(self::once())->method('stdWrapValue')->with('as', [], 'breadcrumbs')->willReturn('breadcrumbs'); $breadcrumbsMenuDataProcessor = $this->getMockBuilder(BreadcrumbsMenu::class) - ->setMethods(['processAdditionalDataProcessors']) - ->setConstructorArgs([$contentDataProcessor->reveal(), $menuRepository->reveal()]) + ->onlyMethods(['processAdditionalDataProcessors']) + ->setConstructorArgs([$contentDataProcessor, $menuRepository]) ->getMock(); - $contentObjectRenderer->stdWrapValue('as', [], 'breadcrumbs')->willReturn('breadcrumbs'); - $processedData = $breadcrumbsMenuDataProcessor->process($contentObjectRenderer->reveal(), [], [], []); + $processedData = $breadcrumbsMenuDataProcessor->process($contentObjectRenderer, [], [], []); self::assertTrue($processedData['breadcrumbs'][0]['isInRootLine']); self::assertTrue($processedData['breadcrumbs'][1]['isInRootLine']); self::assertFalse($processedData['breadcrumbs'][0]['isCurrentPage']); diff --git a/Tests/Unit/Domain/Repository/MenuRepositoryTest.php b/Tests/Unit/Domain/Repository/MenuRepositoryTest.php index 36d635a..4c384ed 100644 --- a/Tests/Unit/Domain/Repository/MenuRepositoryTest.php +++ b/Tests/Unit/Domain/Repository/MenuRepositoryTest.php @@ -1,5 +1,7 @@ prophesize(Context::class); - $context->getAspect('language')->willReturn($this->prophesize(LanguageAspect::class)->reveal()); - $pageRepository = $this->prophesize(PageRepository::class); + $languageAspect = $this->getMockBuilder(LanguageAspect::class) + ->getMock(); + $context = $this->getMockBuilder(Context::class) + ->getMock(); + $context->expects(self::once())->method('getAspect')->with('language')->willReturn($languageAspect); + $pageRepository = $this->getMockBuilder(PageRepository::class) + ->disableOriginalConstructor() + ->getMock(); $excludedDoktypes = [ PageRepository::DOKTYPE_BE_USER_SECTION, - PageRepository::DOKTYPE_RECYCLER, PageRepository::DOKTYPE_SYSFOLDER, ]; - $pageRepository->getMenu(1, '*', 'sorting', Argument::any(), false)->willReturn([]); - $pageRepository->getMenu(1, '*', 'sorting', 'AND doktype NOT IN (' . implode(',', $excludedDoktypes) . ') ', false)->shouldBeCalled()->willReturn([]); - - $menuRepository = $this->getMockBuilder(MenuRepository::class) - ->setMethods(['foo']) - ->setConstructorArgs([$context->reveal(), $pageRepository->reveal()]) - ->getMock(); + if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() < 13) { + $excludedDoktypes[] = PageRepository::DOKTYPE_RECYCLER; + } + $pageRepository->expects(self::once())->method('getMenu') + ->with(1, '*', 'sorting', 'AND doktype NOT IN (' . implode(',', $excludedDoktypes) . ') ', false) + ->willReturn([]); + $menuRepository = new MenuRepository($context, $pageRepository, $this->createMock(EventDispatcherInterface::class)); $menuRepository->getSubPagesOfPage(1, 1, []); } @@ -48,21 +57,25 @@ public function getSubPagesOfPageRestrictQueryToExcludeDoktypes(): void */ public function getSubPagesOfPageMergeExcludeDoktypesFromConfiguration(): void { - $context = $this->prophesize(Context::class); - $context->getAspect('language')->willReturn($this->prophesize(LanguageAspect::class)->reveal()); - $pageRepository = $this->prophesize(PageRepository::class); + $languageAspect = $this->getMockBuilder(LanguageAspect::class) + ->getMock(); + $context = $this->getMockBuilder(Context::class) + ->getMock(); + $context->expects(self::once())->method('getAspect')->with('language')->willReturn($languageAspect); + $pageRepository = $this->getMockBuilder(PageRepository::class) + ->disableOriginalConstructor() + ->getMock(); $excludedDoktypes = [ PageRepository::DOKTYPE_BE_USER_SECTION, - PageRepository::DOKTYPE_RECYCLER, PageRepository::DOKTYPE_SYSFOLDER, ]; - $pageRepository->getMenu(1, '*', 'sorting', Argument::any(), false)->willReturn([]); - $pageRepository->getMenu(1, '*', 'sorting', 'AND doktype NOT IN (' . implode(',', $excludedDoktypes) . ',99) ', false)->shouldBeCalled()->willReturn([]); - - $menuRepository = $this->getMockBuilder(MenuRepository::class) - ->setMethods(['foo']) - ->setConstructorArgs([$context->reveal(), $pageRepository->reveal()]) - ->getMock(); + if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() < 13) { + $excludedDoktypes[] = PageRepository::DOKTYPE_RECYCLER; + } + $pageRepository->expects(self::once())->method('getMenu') + ->with(1, '*', 'sorting', 'AND doktype NOT IN (' . implode(',', $excludedDoktypes) . ',99) ', false) + ->willReturn([]); + $menuRepository = new MenuRepository($context, $pageRepository, $this->createMock(EventDispatcherInterface::class)); $menuRepository->getSubPagesOfPage(1, 1, ['excludeDoktypes' => 99]); } @@ -75,16 +88,52 @@ public function getBreadcrumbsMenuRespectConfiguredExcludeDoktypes(): void ['uid' => 1, 'doktype' => 99, 'nav_hide'=> 0], ['uid' => 2, 'doktype' => 98, 'nav_hide'=> 0], ]; - $context = $this->prophesize(Context::class); - $context->getAspect('language')->willReturn($this->prophesize(LanguageAspect::class)->reveal()); - $pageRepository = $this->prophesize(PageRepository::class); - $pageRepository->getPage(1)->willReturn($rootLine[0]); - $pageRepository->getPage(2)->willReturn($rootLine[1]); - $pageRepository->isPageSuitableForLanguage(Argument::any(), Argument::any())->willReturn(true); + $languageAspect = $this->getMockBuilder(LanguageAspect::class) + ->getMock(); + $context = $this->getMockBuilder(Context::class) + ->getMock(); + $context->expects(self::once())->method('getAspect')->with('language')->willReturn($languageAspect); + + if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() < 13) { + $pageRepository = new class() extends PageRepository { + public function getPage($uid, $disableGroupAccessCheck = false) + { + if ($uid === 1) { + // $rootLine[0] + return ['uid' => 1, 'doktype' => 99, 'nav_hide'=> 0]; + } + if ($uid === 2) { + // $rootLine[0] + return ['uid' => 2, 'doktype' => 98, 'nav_hide'=> 0]; + } + return []; + } + }; + } else { + $pageRepository = new class() extends PageRepository { + public function getPage(int $uid, bool $disableGroupAccessCheck = false): array + { + if ($uid === 1) { + // $rootLine[0] + return ['uid' => 1, 'doktype' => 99, 'nav_hide'=> 0]; + } + if ($uid === 2) { + // $rootLine[0] + return ['uid' => 2, 'doktype' => 98, 'nav_hide'=> 0]; + } + return []; + } + protected function init(): void + { + } + }; + } + $menuRepository = $this->getMockBuilder(MenuRepository::class) - ->setMethods(['populateAdditionalKeysForPage']) - ->setConstructorArgs([$context->reveal(), $pageRepository->reveal()]) + ->onlyMethods(['populateAdditionalKeysForPage', 'isPageSuitableForLanguage']) + ->setConstructorArgs([$context, $pageRepository, $this->createMock(EventDispatcherInterface::class)]) ->getMock(); + $menuRepository->expects(self::any())->method('isPageSuitableForLanguage')->willReturn(true); $breadcrumbs = $menuRepository->getBreadcrumbsMenu($rootLine, ['excludeDoktypes' => 99]); self::assertSame(1, count($breadcrumbs)); } diff --git a/Tests/Unit/PageStateMarkerTest.php b/Tests/Unit/PageStateMarkerTest.php index 2a3d6b2..fc3ed41 100644 --- a/Tests/Unit/PageStateMarkerTest.php +++ b/Tests/Unit/PageStateMarkerTest.php @@ -1,5 +1,7 @@ 'Menus', 'description' => 'Various Menu functionality for TYPO3 Frontend', 'category' => 'fe', - 'version' => '0.5.1', + 'version' => '1.0.3', 'state' => 'stable', 'author' => 'Benni Mack', 'author_email' => 'typo3@b13.com', 'author_company' => 'b13 GmbH', 'constraints' => [ 'depends' => [ - 'typo3' => '9.5.0-11.99.99', + 'typo3' => '10.4.0-12.99.99', ], ], ]; diff --git a/ext_localconf.php b/ext_localconf.php index e86ab26..830c563 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -1,17 +1,15 @@ \B13\Menus\ContentObject\TreeMenuContentObject::class, - 'LISTMENU' => \B13\Menus\ContentObject\ListMenuContentObject::class, - 'LANGUAGEMENU' => \B13\Menus\ContentObject\LanguageMenuContentObject::class, - 'BREADCRUMBS' => \B13\Menus\ContentObject\BreadcrumbsContentObject::class, -]); +defined('TYPO3') or die(); + +if ((\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Information\Typo3Version::class))->getMajorVersion() < 12) { + $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'] = array_merge($GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'], [ + 'TREEMENU' => \B13\Menus\ContentObject\TreeMenuContentObject::class, + 'LISTMENU' => \B13\Menus\ContentObject\ListMenuContentObject::class, + 'LANGUAGEMENU' => \B13\Menus\ContentObject\LanguageMenuContentObject::class, + 'BREADCRUMBS' => \B13\Menus\ContentObject\BreadcrumbsContentObject::class, + ]); +} $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearCachePostProc']['tx-menus'] = \B13\Menus\Hooks\DataHandlerHook::class . '->clearMenuCaches'; - -if (!class_exists(\TYPO3\CMS\Frontend\Page\PageRepository::class)) { - class_alias(\TYPO3\CMS\Core\Domain\Repository\PageRepository::class, \TYPO3\CMS\Frontend\Page\PageRepository::class); -}