Insights spotlight: news from folders 11 + 442, source editable in the menu
- Megamenu entries with the catalog or spotlight layout get two editor fields, "Data source folders" (source_pages) and "Data source categories" (source_categories); the extension stores them, the site package's listener reads them from the entry row. - ProvideInsightsSpotlight reads its news from those folders (empty = 11 + 442; the newer posts live in 442, the query only read 11) and, when set, only from those categories (Insights: Blog Post + Press Release). Newest datetime first, three cards. - Pages carrying the menu are tagged with the news and event tables, so saving such a record refreshes the menu without a cache flush. - VITEC field descriptions via a TCA override; spec v1.31, README. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ad1f3c179c
commit
73e4298b75
7 files changed
+142
-18
No files matched your search
@@ -4,9 +4,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\EventListener;
|
||||
|
||||
use Doctrine\DBAL\ArrayParameterType;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Evomedien\EvoMegamenuJson\Event\BuildSpotlightEvent;
|
||||
use TYPO3\CMS\Core\Attribute\AsEventListener;
|
||||
use TYPO3\CMS\Core\Cache\CacheDataCollectorInterface;
|
||||
use TYPO3\CMS\Core\Cache\CacheTag;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
|
||||
use TYPO3\CMS\Core\Resource\FileRepository;
|
||||
@@ -19,17 +22,23 @@ use TYPO3\CMS\Extbase\Service\ImageService;
|
||||
* news and the next events as image cards, queried live - editors touch
|
||||
* news and event records, never the menu.
|
||||
*
|
||||
* Conventions mirrored from the site set: news live on pid 11 (type 0 only,
|
||||
* same filter as the Solr indexing TS), their details render on page 21
|
||||
* through the NewsDetail route enhancer (/news/article/<path_segment>).
|
||||
* Conventions mirrored from the site set: news are type 0 only (same filter
|
||||
* as the Solr indexing TS), their details render on page 21 through the
|
||||
* NewsDetail route enhancer (/news/article/<path_segment>).
|
||||
* Events come from the vitec event table; an event links to its own
|
||||
* eventurl when set, otherwise to the events page.
|
||||
*
|
||||
* News sources (2026-11-05, ticket 0046-FE: the newer posts sit in folder
|
||||
* 442, the query only read 11): the entry's "Data source folders" and
|
||||
* "Data source categories" decide, editable in the menu record. No folder
|
||||
* set = 11 + 442 (like the /insights/news plugin), no category = all.
|
||||
*/
|
||||
#[AsEventListener(identifier: 'vitec/megamenu-insights-spotlight')]
|
||||
final class ProvideInsightsSpotlight
|
||||
{
|
||||
private const NEWS_TABLE = 'tx_news_domain_model_news';
|
||||
private const NEWS_STORAGE_PID = 11;
|
||||
private const DEFAULT_NEWS_PIDS = [11, 442];
|
||||
private const ENTRY_TABLE = 'tx_evomegamenujson_entry';
|
||||
private const NEWS_DETAIL_PATH = '/news/article/';
|
||||
private const EVENT_TABLE = 'tx_vitec_domain_model_event';
|
||||
private const EVENTS_PAGE = '/insights/events';
|
||||
@@ -44,25 +53,83 @@ final class ProvideInsightsSpotlight
|
||||
public function __invoke(BuildSpotlightEvent $event): void
|
||||
{
|
||||
$stage = $event->getStage();
|
||||
$entry = $event->getEntry();
|
||||
// group field: "11,442" (older notation "pages_11" tolerated)
|
||||
$pids = array_values(array_filter(array_map(
|
||||
static fn(string $value): int => (int)preg_replace('/^\D+_/', '', trim($value)),
|
||||
explode(',', (string)($entry['source_pages'] ?? ''))
|
||||
)));
|
||||
$event->setSpotlight([
|
||||
'news' => $this->latestNews($stage),
|
||||
'news' => $this->latestNews($stage, $pids ?: self::DEFAULT_NEWS_PIDS, $this->entryCategories((int)($entry['uid'] ?? 0))),
|
||||
'events' => $this->upcomingEvents($stage),
|
||||
]);
|
||||
$this->tagPageCache();
|
||||
}
|
||||
|
||||
/** @return array<int,array<string,mixed>> */
|
||||
private function latestNews(int $stage): array
|
||||
/**
|
||||
* The menu sits in every cached page; tagging the page with the news and
|
||||
* event tables lets the DataHandler refresh those pages as soon as an
|
||||
* editor saves a news or event record (it flushes the tag `<table>`).
|
||||
*/
|
||||
private function tagPageCache(): void
|
||||
{
|
||||
try {
|
||||
$collector = ($GLOBALS['TYPO3_REQUEST'] ?? null)?->getAttribute('frontend.cache.collector');
|
||||
if ($collector instanceof CacheDataCollectorInterface) {
|
||||
$collector->addCacheTags(new CacheTag(self::NEWS_TABLE), new CacheTag(self::EVENT_TABLE));
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
// untagged, the menu only ages until the next flush
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Categories picked in the entry's "Data source categories".
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
private function entryCategories(int $entryUid): array
|
||||
{
|
||||
if ($entryUid <= 0) {
|
||||
return [];
|
||||
}
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('sys_category_record_mm');
|
||||
return array_map('intval', $queryBuilder->select('uid_local')->from('sys_category_record_mm')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('tablenames', $queryBuilder->createNamedParameter(self::ENTRY_TABLE)),
|
||||
$queryBuilder->expr()->eq('fieldname', $queryBuilder->createNamedParameter('source_categories')),
|
||||
$queryBuilder->expr()->eq('uid_foreign', $queryBuilder->createNamedParameter($entryUid, ParameterType::INTEGER))
|
||||
)
|
||||
->executeQuery()->fetchFirstColumn());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $pids news folders
|
||||
* @param int[] $categories only news in one of these; empty = all
|
||||
* @return array<int,array<string,mixed>>
|
||||
*/
|
||||
private function latestNews(int $stage, array $pids, array $categories): array
|
||||
{
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::NEWS_TABLE);
|
||||
$queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
|
||||
try {
|
||||
$rows = $queryBuilder->select('uid', 'title', 'teaser', 'datetime', 'path_segment')
|
||||
$queryBuilder->select('uid', 'title', 'teaser', 'datetime', 'path_segment')
|
||||
->from(self::NEWS_TABLE)
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter(self::NEWS_STORAGE_PID, ParameterType::INTEGER)),
|
||||
$queryBuilder->expr()->in('pid', $queryBuilder->createNamedParameter($pids, ArrayParameterType::INTEGER)),
|
||||
$queryBuilder->expr()->eq('type', $queryBuilder->createNamedParameter('0'))
|
||||
)
|
||||
->orderBy('datetime', 'DESC')
|
||||
);
|
||||
if ($categories !== []) {
|
||||
$mm = $this->connectionPool->getQueryBuilderForTable('sys_category_record_mm');
|
||||
$mm->select('uid_foreign')->from('sys_category_record_mm')
|
||||
->where(
|
||||
$mm->expr()->eq('tablenames', $queryBuilder->createNamedParameter(self::NEWS_TABLE)),
|
||||
$mm->expr()->eq('fieldname', $queryBuilder->createNamedParameter('categories')),
|
||||
$mm->expr()->in('uid_local', $queryBuilder->createNamedParameter($categories, ArrayParameterType::INTEGER))
|
||||
);
|
||||
$queryBuilder->andWhere($queryBuilder->expr()->in('uid', $mm->getSQL()));
|
||||
}
|
||||
$rows = $queryBuilder->orderBy('datetime', 'DESC')
|
||||
->setMaxResults(self::NEWS_COUNT)
|
||||
->executeQuery()->fetchAllAssociative();
|
||||
} catch (\Throwable) {
|
||||
|
||||
Reference in new issue
Block a user