Megamenu follow-ups: nav_hide honoured, entries insertable, page tiles
- getMenu() treats hidden/time/fe_group as enable fields but NOT
nav_hide - the treeColumns doc comment claimed otherwise, and the
news detail page ("article") stayed in the new spotlight rail after
the editor hid it in menus. All three getMenu() calls (spotlight nav,
pagetree columns and their items) now pass 'AND nav_hide = 0';
verified live (6 rail items)
- all four tx_evomegamenujson_* tables get
security.ignorePageTypeRestriction: they are inline children of the
menu plugin and live on whatever page the plugin sits on - creating
a new entry on the standard page failed with "table is not allowed"
- new vitec:attach-page-media --root=<slug> (dry-run by default):
attaches the numbered placeholder pages/<pid>-08.png (slot 08 = page
media / menu tile, the number doubles as the image wish list) as page
media to every child page without one; existing media is never
touched. Run for /support (8 pages) so the planned Support menu entry
renders as two 4-tile rows via the pagetree layout's column images
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6423b7c7f8
commit
8497dad06a
6 files changed
+153
-7
No files matched your search
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\Command;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use TYPO3\CMS\Core\Core\Bootstrap;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\DataHandling\DataHandler;
|
||||
use TYPO3\CMS\Core\Resource\StorageRepository;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Attaches a page-media image to every child page of a root that has none
|
||||
* yet. The menu uses page media as tile/card visuals (megamenu `pagetree`
|
||||
* column images and `spotlight` nav images, 2026-10-27), so a branch
|
||||
* becomes a visual panel the moment its pages carry an image.
|
||||
*
|
||||
* Image per page: the numbered placeholder
|
||||
* `fileadmin/placeholders/pages/<pid>-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('<error>Root page ' . $rootSlug . ' not found.</error>');
|
||||
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(' <comment>! #%d "%s": no placeholder file - skipped</comment>', $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('<error>DataHandler: ' . implode(' | ', $dataHandler->errorLog) . '</error>');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
$output->writeln('<info>Done. Flush the frontend cache; the menu picks the images up.</info>');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user