getSiteLanguage()->getLanguageId(); $cachedFetchedContentRecords = $this->runtimeCache->get('ContentFetcher_fetchedContentRecords') ?: []; if (empty($cachedFetchedContentRecords)) { $fetchedContentRecords = []; $isLanguageComparisonMode = $pageLayoutContext->getDrawingConfiguration()->isLanguageComparisonMode(); $queryBuilder = $this->getQueryBuilder($pageLayoutContext); $result = $queryBuilder->executeQuery(); $records = $this->getResult($result); foreach ($records as $record) { $recordLanguage = $record['language_tag'] ?? ''; $recordColumnNumber = (int)$record['colPos']; if ($recordLanguage === -1) { // Record is set to "all languages", place it according to view mode. if ($isLanguageComparisonMode) { // Force the record to only be shown in default language in "Languages" view mode. $recordLanguage = 0; } else { // Force the record to be shown in the currently active language in "Columns" view mode. $recordLanguage = $languageId; } } $fetchedContentRecords[$recordLanguage][$recordColumnNumber][] = $record; } $this->runtimeCache->set('ContentFetcher_fetchedContentRecords', $fetchedContentRecords); } else { $fetchedContentRecords = $cachedFetchedContentRecords; } $contentByLanguage = $fetchedContentRecords[$languageId] ?? []; if ($columnNumber === null) { return $contentByLanguage; } return $contentByLanguage[$columnNumber] ?? []; } public function getFlatContentRecords(PageLayoutContext $pageLayoutContext, int $languageId): iterable { $contentRecords = $this->getContentRecordsPerColumn($pageLayoutContext, null, $languageId); return empty($contentRecords) ? [] : array_merge(...$contentRecords); } /** * Allows to decide via an Event whether a custom type has children which were rendered or should not be rendered. */ public function getUnusedRecords(PageLayoutContext $pageLayoutContext): iterable { $unrendered = []; $recordIdentityMap = $pageLayoutContext->getRecordIdentityMap(); $languageId = $pageLayoutContext->getDrawingConfiguration()->getPrimaryLanguageId(); // @todo consider to invoke the identity-map much earlier (to avoid fetching database records again) foreach ($this->getContentRecordsPerColumn($pageLayoutContext, null, $languageId) as $contentRecordsInColumn) { foreach ($contentRecordsInColumn as $contentRecord) { $used = $recordIdentityMap->hasIdentifier('tt_content', (int)$contentRecord['uid']); // A hook mentioned that this record is used somewhere, so this is in fact "rendered" already $event = new IsContentUsedOnPageLayoutEvent($contentRecord, $used, $pageLayoutContext); $event = $this->eventDispatcher->dispatch($event); if (!$event->isRecordUsed()) { $unrendered[] = $contentRecord; } } } return $unrendered; } public function getTranslationData(PageLayoutContext $pageLayoutContext, iterable $contentElements, int $language): array { if ($language === 0) { return []; } $languageTranslationInfo = $this->runtimeCache->get('ContentFetcher_TranslationInfo_' . $language) ?: []; if (empty($languageTranslationInfo)) { $contentRecordsInDefaultLanguage = $this->getContentRecordsPerColumn($pageLayoutContext, null, 0); if (!empty($contentRecordsInDefaultLanguage)) { $contentRecordsInDefaultLanguage = array_merge(...$contentRecordsInDefaultLanguage); } $untranslatedRecordUids = array_flip( array_column( // Eliminate records with "-1" as sys_language_uid since they can not be translated array_filter($contentRecordsInDefaultLanguage, static function (array $record): bool { return true; }), 'uid' ) ); foreach ($contentElements as $contentElement) { if (($contentElement['language_tag'] ?? '') === '') { continue; } if ((int)$contentElement['l18n_parent'] === 0) { $languageTranslationInfo['hasStandAloneContent'] = true; $languageTranslationInfo['mode'] = 'free'; } if ((int)$contentElement['l18n_parent'] > 0) { $languageTranslationInfo['hasTranslations'] = true; $languageTranslationInfo['mode'] = 'connected'; } if ((int)$contentElement['l10n_source'] > 0) { unset($untranslatedRecordUids[(int)$contentElement['l10n_source']]); } } if (!isset($languageTranslationInfo['hasTranslations'])) { $languageTranslationInfo['hasTranslations'] = false; } foreach ($untranslatedRecordUids as $uid => $index) { $contentElementInDefaultLanguage = $contentRecordsInDefaultLanguage[$index]; if (!$this->backendLayoutView->isCTypeAllowedInColPosByPage($contentElementInDefaultLanguage['CType'], $contentElementInDefaultLanguage['colPos'], $contentElementInDefaultLanguage['pid'])) { unset($untranslatedRecordUids[$uid]); } } $untranslatedRecordUidsWithoutWorkspaceDeletedRecords = $this->removeWorkspaceDeletedPlaceholdersUidsFromUntranslatedRecordUids($pageLayoutContext, array_keys($untranslatedRecordUids), $language); $languageTranslationInfo['untranslatedRecordUids'] = $untranslatedRecordUidsWithoutWorkspaceDeletedRecords; if (array_keys($untranslatedRecordUids) !== $untranslatedRecordUidsWithoutWorkspaceDeletedRecords) { $languageTranslationInfo['hasElementsWithWorkspaceDeletePlaceholders'] = true; } // Check for inconsistent translations, force "mixed" mode and dispatch a FlashMessage to user if such a case is encountered. if (isset($languageTranslationInfo['hasStandAloneContent']) && $languageTranslationInfo['hasTranslations'] ) { $languageTranslationInfo['mode'] = 'mixed'; // We do not want to show the staleTranslationWarning if allowInconsistentLanguageHandling is enabled if (!$pageLayoutContext->getDrawingConfiguration()->getAllowInconsistentLanguageHandling()) { $siteLanguage = $pageLayoutContext->getSiteLanguage($language); $languageService = $this->getLanguageService(); $message = FlashMessage::createFromArray([ 'message' => $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:staleTranslationWarning'), 'title' => sprintf($languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:staleTranslationWarningTitle'), $siteLanguage->getTitle()), 'severity' => ContextualFeedbackSeverity::WARNING->value, ]); $queue = $this->flashMessageService->getMessageQueueByIdentifier(); $queue->addMessage($message); } } $this->runtimeCache->set('ContentFetcher_TranslationInfo_' . $language, $languageTranslationInfo); } return $languageTranslationInfo; } protected function removeWorkspaceDeletedPlaceholdersUidsFromUntranslatedRecordUids(PageLayoutContext $pageLayoutContext, array $untranslatedRecordUids, int $language): array { if ($this->getBackendUser()->workspace <= 0) { // Early return if we're not in a workspace to suppress some queries. return $untranslatedRecordUids; } $queryBuilder = $this->getQueryBuilder($pageLayoutContext); $queryBuilder->andWhere( $queryBuilder->expr()->and( $queryBuilder->expr()->in( 'l18n_parent', $queryBuilder->createNamedParameter($untranslatedRecordUids, Connection::PARAM_INT_ARRAY) ), $queryBuilder->expr()->eq( 'sys_language_uid', $queryBuilder->createNamedParameter($language, Connection::PARAM_INT) ) ) ); $result = $queryBuilder->executeQuery(); $uidsToRemoveFromUntranslatedRecordUids = []; while ($row = $result->fetchAssociative()) { BackendUtility::workspaceOL('tt_content', $row, -99, true); if ($row && VersionState::tryFrom($row['t3ver_state'] ?? 0) === VersionState::DELETE_PLACEHOLDER) { $uidsToRemoveFromUntranslatedRecordUids[] = $row['l18n_parent']; } } return array_diff($untranslatedRecordUids, $uidsToRemoveFromUntranslatedRecordUids); } protected function getQueryBuilder(PageLayoutContext $pageLayoutContext): QueryBuilder { $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tt_content'); $queryBuilder->getRestrictions() ->removeAll() ->add(GeneralUtility::makeInstance(DeletedRestriction::class)) ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->getBackendUser()->workspace)); $queryBuilder ->select('*') ->from('tt_content'); $queryBuilder->andWhere( $queryBuilder->expr()->eq( 'tt_content.pid', $queryBuilder->createNamedParameter($pageLayoutContext->getPageId(), Connection::PARAM_INT) ) ); $schema = $this->tcaSchemaFactory->get('tt_content'); $sortBy = $schema->hasCapability(TcaSchemaCapability::SortByField) ? (string)$schema->getCapability(TcaSchemaCapability::SortByField) : ''; if ($sortBy === '' && $schema->hasCapability(TcaSchemaCapability::DefaultSorting)) { $sortBy = (string)$schema->getCapability(TcaSchemaCapability::DefaultSorting)->getValue(); } foreach (QueryHelper::parseOrderBy($sortBy) as $orderBy) { $queryBuilder->addOrderBy($orderBy[0], $orderBy[1]); } $event = new ModifyDatabaseQueryForContentEvent($queryBuilder, 'tt_content', $pageLayoutContext->getPageId()); $event = $this->eventDispatcher->dispatch($event); return $event->getQueryBuilder(); } protected function getResult($result): array { $output = []; while ($row = $result->fetchAssociative()) { BackendUtility::workspaceOL('tt_content', $row, -99, true); if ($row && VersionState::tryFrom($row['t3ver_state'] ?? 0) !== VersionState::DELETE_PLACEHOLDER) { $output[] = $row; } } return $output; } protected function getLanguageService(): LanguageService { return $GLOBALS['LANG']; } public function getBackendUser(): BackendUserAuthentication { return $GLOBALS['BE_USER']; } }