diff --git a/packages/evo_megamenu_json/Classes/Service/MenuBuilder.php b/packages/evo_megamenu_json/Classes/Service/MenuBuilder.php index 5708b35..6879920 100644 --- a/packages/evo_megamenu_json/Classes/Service/MenuBuilder.php +++ b/packages/evo_megamenu_json/Classes/Service/MenuBuilder.php @@ -39,6 +39,9 @@ final class MenuBuilder private const TABLE_ITEM = 'tx_evomegamenujson_item'; private const TABLE_TEASER = 'tx_evomegamenujson_teaser'; + /** getMenu() does not filter "hide in menus" by itself. */ + private const NAV_WHERE = 'AND nav_hide = 0'; + private const DEFAULT_SETTINGS = [ 'stage' => 3, 'pendingBehaviour' => 'fallback', @@ -208,8 +211,11 @@ final class MenuBuilder /** * Columns from the page tree below the entry's linked page. getMenu() - * applies the frontend restrictions (hidden, time, nav_hide, fe_group), - * so an editor hides a page and it leaves the menu with the next flush. + * applies the frontend restrictions (hidden, time, fe_group); nav_hide + * is NOT an enable field and goes through the additional where clause + * (found 2026-10-27 - the news detail page stayed in the spotlight + * rail). An editor hides a page and it leaves the menu with the next + * cache flush. * * @return array> */ @@ -220,9 +226,9 @@ final class MenuBuilder return []; } $columns = []; - foreach ($this->pageRepository->getMenu($rootUid) as $child) { + foreach ($this->pageRepository->getMenu($rootUid, '*', 'sorting', self::NAV_WHERE) as $child) { $items = []; - foreach ($this->pageRepository->getMenu((int)$child['uid']) as $grandchild) { + foreach ($this->pageRepository->getMenu((int)$child['uid'], '*', 'sorting', self::NAV_WHERE) as $grandchild) { $items[] = [ 'title' => (string)(($grandchild['nav_title'] ?? '') !== '' ? $grandchild['nav_title'] : $grandchild['title']), 'link' => $this->url('t3://page?uid=' . (int)$grandchild['uid'], $cObj), @@ -250,8 +256,7 @@ final class MenuBuilder /** * Flat left-rail nav for the spotlight layout: the linked page's children * with abstract and page media, so the front end can render the rail and - * swap the visual area on hover. Same visibility rules as treeColumns - * (getMenu applies hidden, time, nav_hide and fe_group). + * swap the visual area on hover. Same visibility rules as treeColumns. * * @return array> */ @@ -262,7 +267,7 @@ final class MenuBuilder return []; } $items = []; - foreach ($this->pageRepository->getMenu($rootUid) as $child) { + foreach ($this->pageRepository->getMenu($rootUid, '*', 'sorting', self::NAV_WHERE) as $child) { $item = [ 'title' => (string)(($child['nav_title'] ?? '') !== '' ? $child['nav_title'] : $child['title']), 'link' => $this->url('t3://page?uid=' . (int)$child['uid'], $cObj), diff --git a/packages/evo_megamenu_json/Configuration/TCA/tx_evomegamenujson_column.php b/packages/evo_megamenu_json/Configuration/TCA/tx_evomegamenujson_column.php index 914689f..df0979d 100644 --- a/packages/evo_megamenu_json/Configuration/TCA/tx_evomegamenujson_column.php +++ b/packages/evo_megamenu_json/Configuration/TCA/tx_evomegamenujson_column.php @@ -18,6 +18,11 @@ return [ 'delete' => 'deleted', 'sortby' => 'sorting', 'hideTable' => true, + 'security' => [ + // inline children of the menu plugin - they live on whatever + // page the plugin sits on, not only on sysfolders + 'ignorePageTypeRestriction' => true, + ], 'versioningWS' => true, 'languageField' => 'sys_language_uid', 'transOrigPointerField' => 'l10n_parent', diff --git a/packages/evo_megamenu_json/Configuration/TCA/tx_evomegamenujson_entry.php b/packages/evo_megamenu_json/Configuration/TCA/tx_evomegamenujson_entry.php index f41d582..5b25977 100644 --- a/packages/evo_megamenu_json/Configuration/TCA/tx_evomegamenujson_entry.php +++ b/packages/evo_megamenu_json/Configuration/TCA/tx_evomegamenujson_entry.php @@ -24,6 +24,11 @@ return [ 'delete' => 'deleted', 'sortby' => 'sorting', 'hideTable' => true, + 'security' => [ + // inline children of the menu plugin - they live on whatever + // page the plugin sits on, not only on sysfolders + 'ignorePageTypeRestriction' => true, + ], 'versioningWS' => true, 'languageField' => 'sys_language_uid', 'transOrigPointerField' => 'l10n_parent', diff --git a/packages/evo_megamenu_json/Configuration/TCA/tx_evomegamenujson_item.php b/packages/evo_megamenu_json/Configuration/TCA/tx_evomegamenujson_item.php index 386ba87..a4ee659 100644 --- a/packages/evo_megamenu_json/Configuration/TCA/tx_evomegamenujson_item.php +++ b/packages/evo_megamenu_json/Configuration/TCA/tx_evomegamenujson_item.php @@ -21,6 +21,11 @@ return [ 'delete' => 'deleted', 'sortby' => 'sorting', 'hideTable' => true, + 'security' => [ + // inline children of the menu plugin - they live on whatever + // page the plugin sits on, not only on sysfolders + 'ignorePageTypeRestriction' => true, + ], 'versioningWS' => true, 'languageField' => 'sys_language_uid', 'transOrigPointerField' => 'l10n_parent', diff --git a/packages/evo_megamenu_json/Configuration/TCA/tx_evomegamenujson_teaser.php b/packages/evo_megamenu_json/Configuration/TCA/tx_evomegamenujson_teaser.php index 6c4f1d5..38bee70 100644 --- a/packages/evo_megamenu_json/Configuration/TCA/tx_evomegamenujson_teaser.php +++ b/packages/evo_megamenu_json/Configuration/TCA/tx_evomegamenujson_teaser.php @@ -19,6 +19,11 @@ return [ 'delete' => 'deleted', 'sortby' => 'sorting', 'hideTable' => true, + 'security' => [ + // inline children of the menu plugin - they live on whatever + // page the plugin sits on, not only on sysfolders + 'ignorePageTypeRestriction' => true, + ], 'versioningWS' => true, 'languageField' => 'sys_language_uid', 'transOrigPointerField' => 'l10n_parent', diff --git a/packages/vitec/Classes/Command/AttachPageMediaCommand.php b/packages/vitec/Classes/Command/AttachPageMediaCommand.php new file mode 100644 index 0000000..6bd6ce3 --- /dev/null +++ b/packages/vitec/Classes/Command/AttachPageMediaCommand.php @@ -0,0 +1,121 @@ +-08.png` when it exists (slot 08 = + * page media / menu tile; the number in the picture doubles as the image + * wish list towards the agency), otherwise the generic wide grey + * placeholder. A page that already has a media reference is NEVER touched - + * editors swap the placeholder for the real image in the page properties. + * + * vendor/bin/typo3 vitec:attach-page-media --root=/support + * vendor/bin/typo3 vitec:attach-page-media --root=/support --apply + */ +#[AsCommand( + name: 'vitec:attach-page-media', + description: 'Attach placeholder page media to the children of a root page (dry-run by default)' +)] +final class AttachPageMediaCommand extends Command +{ + protected function configure(): void + { + $this->addOption('root', null, InputOption::VALUE_REQUIRED, 'Slug of the root page, e.g. /support'); + $this->addOption('apply', null, InputOption::VALUE_NONE, 'Write changes (default: dry run)'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + Bootstrap::initializeBackendAuthentication(); + $apply = (bool)$input->getOption('apply'); + $rootSlug = '/' . trim((string)$input->getOption('root'), '/ '); + $pool = GeneralUtility::makeInstance(ConnectionPool::class); + $pages = $pool->getConnectionForTable('pages'); + + $root = $pages->fetchAssociative('SELECT uid, title FROM pages WHERE deleted = 0 AND slug = ? AND sys_language_uid IN (0,-1)', [$rootSlug]); + if ($root === null || $root === false) { + $output->writeln('Root page ' . $rootSlug . ' not found.'); + return Command::FAILURE; + } + $children = $pages->fetchAllAssociative( + 'SELECT uid, title FROM pages WHERE deleted = 0 AND hidden = 0 AND doktype = 1 AND pid = ? AND sys_language_uid IN (0,-1) ORDER BY sorting', + [(int)$root['uid']] + ); + $output->writeln(($apply ? 'APPLY' : 'DRY-RUN') . sprintf(' · %s #%d · %d child page(s)', $rootSlug, (int)$root['uid'], count($children))); + + $storage = GeneralUtility::makeInstance(StorageRepository::class)->getDefaultStorage(); + $refs = $pool->getConnectionForTable('sys_file_reference'); + $datamap = []; + $newIndex = 0; + foreach ($children as $child) { + $uid = (int)$child['uid']; + $existing = (int)$refs->fetchOne( + "SELECT COUNT(*) FROM sys_file_reference WHERE deleted = 0 AND tablenames = 'pages' AND fieldname = 'media' AND uid_foreign = ?", + [$uid] + ); + if ($existing > 0) { + $output->writeln(sprintf(' = #%d "%s": has media (%d) - untouched', $uid, (string)$child['title'], $existing)); + continue; + } + $identifier = 'placeholders/pages/' . $uid . '-08.png'; + if (!$storage->hasFile($identifier)) { + $identifier = 'placeholders/placeholder-grey-wide.png'; + if (!$storage->hasFile($identifier)) { + $output->writeln(sprintf(' ! #%d "%s": no placeholder file - skipped', $uid, (string)$child['title'])); + continue; + } + } + $output->writeln(sprintf(' + #%d "%s": attach %s', $uid, (string)$child['title'], $identifier)); + if (!$apply) { + continue; + } + $file = $storage->getFile($identifier); + $datamap['sys_file_reference']['NEWPM' . (++$newIndex)] = [ + 'pid' => $uid, + 'table_local' => 'sys_file', + 'uid_local' => $file->getUid(), + 'tablenames' => 'pages', + 'uid_foreign' => $uid, + 'fieldname' => 'media', + ]; + $datamap['pages'][$uid] = ['media' => 1]; + } + + if (!$apply) { + $output->writeln("\nDRY RUN - nothing written. Add --apply to write."); + return Command::SUCCESS; + } + if ($datamap === []) { + $output->writeln('Nothing to attach.'); + return Command::SUCCESS; + } + $dataHandler = GeneralUtility::makeInstance(DataHandler::class); + $dataHandler->start($datamap, []); + $dataHandler->process_datamap(); + if ($dataHandler->errorLog !== []) { + $output->writeln('DataHandler: ' . implode(' | ', $dataHandler->errorLog) . ''); + return Command::FAILURE; + } + $output->writeln('Done. Flush the frontend cache; the menu picks the images up.'); + return Command::SUCCESS; + } +}