‪TYPO3CMS  ‪main
ContentFetcher.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Psr\EventDispatcher\EventDispatcherInterface;
21 use TYPO3\CMS\Backend\Utility\BackendUtility;
30 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
40 
53 {
55 
56  protected array ‪$fetchedContentRecords = [];
57 
58  protected EventDispatcherInterface ‪$eventDispatcher;
59 
60  public function ‪__construct(‪PageLayoutContext $pageLayoutContext)
61  {
62  $this->context = $pageLayoutContext;
63  $this->fetchedContentRecords = $this->‪getRuntimeCache()->get('ContentFetcher_fetchedContentRecords') ?: [];
64  $this->eventDispatcher = GeneralUtility::makeInstance(EventDispatcherInterface::class);
65  }
66 
73  public function ‪getContentRecordsPerColumn(?int $columnNumber = null, ?int $languageId = null): array
74  {
75  $languageId = $languageId ?? $this->context->getSiteLanguage()->getLanguageId();
76 
77  if (empty($this->fetchedContentRecords)) {
78  $isLanguageComparisonMode = $this->context->getDrawingConfiguration()->isLanguageComparisonMode();
79  $queryBuilder = $this->‪getQueryBuilder();
80  $result = $queryBuilder->executeQuery();
81  $records = $this->‪getResult($result);
82  foreach ($records as ‪$record) {
83  $recordLanguage = (int)‪$record['sys_language_uid'];
84  $recordColumnNumber = (int)‪$record['colPos'];
85  if ($recordLanguage === -1) {
86  // Record is set to "all languages", place it according to view mode.
87  if ($isLanguageComparisonMode) {
88  // Force the record to only be shown in default language in "Languages" view mode.
89  $recordLanguage = 0;
90  } else {
91  // Force the record to be shown in the currently active language in "Columns" view mode.
92  $recordLanguage = $languageId;
93  }
94  }
95  $this->fetchedContentRecords[$recordLanguage][$recordColumnNumber][] = ‪$record;
96  }
97  $this->‪getRuntimeCache()->set('ContentFetcher_fetchedContentRecords', $this->fetchedContentRecords);
98  }
99 
100  $contentByLanguage = &$this->fetchedContentRecords[$languageId];
101 
102  if ($columnNumber === null) {
103  return $contentByLanguage ?? [];
104  }
105 
106  return $contentByLanguage[$columnNumber] ?? [];
107  }
108 
109  public function ‪getFlatContentRecords(int $languageId): iterable
110  {
111  $contentRecords = $this->‪getContentRecordsPerColumn(null, $languageId);
112  return empty($contentRecords) ? [] : array_merge(...$contentRecords);
113  }
114 
118  public function ‪getUnusedRecords(): iterable
119  {
120  $unrendered = [];
121  $recordIdentityMap = $this->context->getRecordIdentityMap();
122  $languageId = $this->context->getDrawingConfiguration()->getSelectedLanguageId();
123  // @todo consider to invoke the identity-map much earlier (to avoid fetching database records again)
124  foreach ($this->‪getContentRecordsPerColumn(null, $languageId) as $contentRecordsInColumn) {
125  foreach ($contentRecordsInColumn as $contentRecord) {
126  $used = $recordIdentityMap->hasIdentifier('tt_content', (int)$contentRecord['uid']);
127  // A hook mentioned that this record is used somewhere, so this is in fact "rendered" already
128  $event = new ‪IsContentUsedOnPageLayoutEvent($contentRecord, $used, $this->context);
129  $event = $this->eventDispatcher->dispatch($event);
130  if (!$event->isRecordUsed()) {
131  $unrendered[] = $contentRecord;
132  }
133  }
134  }
135  return $unrendered;
136  }
137 
138  public function ‪getTranslationData(iterable $contentElements, int $language): array
139  {
140  if ($language === 0) {
141  return [];
142  }
143 
144  $languageTranslationInfo = $this->‪getRuntimeCache()->get('ContentFetcher_TranslationInfo_' . $language) ?: [];
145  if (empty($languageTranslationInfo)) {
146  $contentRecordsInDefaultLanguage = $this->‪getContentRecordsPerColumn(null, 0);
147  if (!empty($contentRecordsInDefaultLanguage)) {
148  $contentRecordsInDefaultLanguage = array_merge(...$contentRecordsInDefaultLanguage);
149  }
150  $untranslatedRecordUids = array_flip(
151  array_column(
152  // Eliminate records with "-1" as sys_language_uid since they can not be translated
153  array_filter($contentRecordsInDefaultLanguage, static function (array ‪$record): bool {
154  return (int)(‪$record['sys_language_uid'] ?? 0) !== -1;
155  }),
156  'uid'
157  )
158  );
159 
160  foreach ($contentElements as $contentElement) {
161  if ((int)$contentElement['sys_language_uid'] === -1) {
162  continue;
163  }
164  if ((int)$contentElement['l18n_parent'] === 0) {
165  $languageTranslationInfo['hasStandAloneContent'] = true;
166  $languageTranslationInfo['mode'] = 'free';
167  }
168  if ((int)$contentElement['l18n_parent'] > 0) {
169  $languageTranslationInfo['hasTranslations'] = true;
170  $languageTranslationInfo['mode'] = 'connected';
171  }
172  if ((int)$contentElement['l10n_source'] > 0) {
173  unset($untranslatedRecordUids[(int)$contentElement['l10n_source']]);
174  }
175  }
176  if (!isset($languageTranslationInfo['hasTranslations'])) {
177  $languageTranslationInfo['hasTranslations'] = false;
178  }
179  $languageTranslationInfo['untranslatedRecordUids'] = array_keys($untranslatedRecordUids);
180 
181  // Check for inconsistent translations, force "mixed" mode and dispatch a FlashMessage to user if such a case is encountered.
182  if (isset($languageTranslationInfo['hasStandAloneContent'])
183  && $languageTranslationInfo['hasTranslations']
184  ) {
185  $languageTranslationInfo['mode'] = 'mixed';
186 
187  // We do not want to show the staleTranslationWarning if allowInconsistentLanguageHandling is enabled
188  if (!$this->context->getDrawingConfiguration()->getAllowInconsistentLanguageHandling()) {
189  $siteLanguage = $this->context->getSiteLanguage($language);
190  $message = GeneralUtility::makeInstance(
191  FlashMessage::class,
192  $this->‪getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:staleTranslationWarning'),
193  sprintf($this->‪getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:staleTranslationWarningTitle'), $siteLanguage->getTitle()),
194  ContextualFeedbackSeverity::WARNING
195  );
196  $service = GeneralUtility::makeInstance(FlashMessageService::class);
197  $queue = $service->getMessageQueueByIdentifier();
198  $queue->addMessage($message);
199  }
200  }
201 
202  $this->‪getRuntimeCache()->set('ContentFetcher_TranslationInfo_' . $language, $languageTranslationInfo);
203  }
204  return $languageTranslationInfo;
205  }
206 
207  protected function ‪getQueryBuilder(): QueryBuilder
208  {
209  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
210  ->getQueryBuilderForTable('tt_content');
211  $queryBuilder->getRestrictions()
212  ->removeAll()
213  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
214  ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->‪getBackendUser()->workspace));
215  $queryBuilder
216  ->select('*')
217  ->from('tt_content');
218 
219  $queryBuilder->andWhere(
220  $queryBuilder->expr()->eq(
221  'tt_content.pid',
222  $queryBuilder->createNamedParameter($this->context->getPageId(), ‪Connection::PARAM_INT)
223  )
224  );
225 
226  $sortBy = (string)(‪$GLOBALS['TCA']['tt_content']['ctrl']['sortby'] ?: ‪$GLOBALS['TCA']['tt_content']['ctrl']['default_sortby']);
227  foreach (‪QueryHelper::parseOrderBy($sortBy) as $orderBy) {
228  $queryBuilder->addOrderBy($orderBy[0], $orderBy[1]);
229  }
230 
231  $event = new ‪ModifyDatabaseQueryForContentEvent($queryBuilder, 'tt_content', $this->context->getPageId());
232  $event = $this->eventDispatcher->dispatch($event);
233  return $event->getQueryBuilder();
234  }
235 
236  protected function ‪getResult($result): array
237  {
238  $output = [];
239  while ($row = $result->fetchAssociative()) {
240  BackendUtility::workspaceOL('tt_content', $row, -99, true);
241  if ($row && ‪VersionState::tryFrom($row['t3ver_state'] ?? 0) !== VersionState::DELETE_PLACEHOLDER) {
242  $output[] = $row;
243  }
244  }
245  return $output;
246  }
247 
249  {
250  return ‪$GLOBALS['LANG'];
251  }
252 
254  {
255  return GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');
256  }
257 
259  {
260  return ‪$GLOBALS['BE_USER'];
261  }
262 }
‪TYPO3\CMS\Core\Database\Query\QueryHelper\parseOrderBy
‪static array array[] parseOrderBy(string $input)
Definition: QueryHelper.php:44
‪TYPO3\CMS\Backend\View\Event\ModifyDatabaseQueryForContentEvent
Definition: ModifyDatabaseQueryForContentEvent.php:26
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:52
‪TYPO3\CMS\Backend\View\BackendLayout\ContentFetcher\getLanguageService
‪getLanguageService()
Definition: ContentFetcher.php:248
‪TYPO3\CMS\Core\Versioning\VersionState
‪VersionState
Definition: VersionState.php:22
‪TYPO3\CMS\Backend\View\BackendLayout\ContentFetcher\getRuntimeCache
‪getRuntimeCache()
Definition: ContentFetcher.php:253
‪TYPO3\CMS\Backend\View\BackendLayout\ContentFetcher\$context
‪PageLayoutContext $context
Definition: ContentFetcher.php:54
‪TYPO3\CMS\Backend\View\BackendLayout\ContentFetcher\__construct
‪__construct(PageLayoutContext $pageLayoutContext)
Definition: ContentFetcher.php:60
‪TYPO3\CMS\Backend\View\BackendLayout\ContentFetcher\getUnusedRecords
‪getUnusedRecords()
Definition: ContentFetcher.php:118
‪TYPO3\CMS\Backend\View\BackendLayout\ContentFetcher
Definition: ContentFetcher.php:53
‪TYPO3\CMS\Frontend\Content\tryFrom
‪@ tryFrom
Definition: ContentSlideMode.php:27
‪TYPO3\CMS\Core\Type\ContextualFeedbackSeverity
‪ContextualFeedbackSeverity
Definition: ContextualFeedbackSeverity.php:25
‪TYPO3\CMS\Backend\View\BackendLayout\ContentFetcher\getQueryBuilder
‪getQueryBuilder()
Definition: ContentFetcher.php:207
‪TYPO3\CMS\Backend\View\BackendLayout\ContentFetcher\$fetchedContentRecords
‪array $fetchedContentRecords
Definition: ContentFetcher.php:56
‪TYPO3\CMS\Core\Database\Query\QueryHelper
Definition: QueryHelper.php:32
‪TYPO3\CMS\Backend\View\Event\IsContentUsedOnPageLayoutEvent
Definition: IsContentUsedOnPageLayoutEvent.php:26
‪TYPO3\CMS\Webhooks\Message\$record
‪identifier readonly int readonly array $record
Definition: PageModificationMessage.php:36
‪TYPO3\CMS\Core\Cache\CacheManager
Definition: CacheManager.php:36
‪TYPO3\CMS\Backend\View\BackendLayout\ContentFetcher\getBackendUser
‪getBackendUser()
Definition: ContentFetcher.php:258
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:62
‪TYPO3\CMS\Backend\View\BackendLayout
Definition: BackendLayout.php:16
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:41
‪TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
Definition: FrontendInterface.php:22
‪TYPO3\CMS\Core\Messaging\FlashMessage
Definition: FlashMessage.php:27
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Backend\View\PageLayoutContext
Definition: PageLayoutContext.php:43
‪TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction
Definition: DeletedRestriction.php:28
‪TYPO3\CMS\Backend\View\BackendLayout\ContentFetcher\getTranslationData
‪getTranslationData(iterable $contentElements, int $language)
Definition: ContentFetcher.php:138
‪TYPO3\CMS\Backend\View\BackendLayout\ContentFetcher\getFlatContentRecords
‪getFlatContentRecords(int $languageId)
Definition: ContentFetcher.php:109
‪TYPO3\CMS\Backend\View\BackendLayout\ContentFetcher\getResult
‪getResult($result)
Definition: ContentFetcher.php:236
‪TYPO3\CMS\Backend\View\BackendLayout\ContentFetcher\$eventDispatcher
‪EventDispatcherInterface $eventDispatcher
Definition: ContentFetcher.php:58
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:46
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Messaging\FlashMessageService
Definition: FlashMessageService.php:27
‪TYPO3\CMS\Backend\View\BackendLayout\ContentFetcher\getContentRecordsPerColumn
‪array getContentRecordsPerColumn(?int $columnNumber=null, ?int $languageId=null)
Definition: ContentFetcher.php:73
‪TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction
Definition: WorkspaceRestriction.php:39