‪TYPO3CMS  ‪main
PageLayoutView.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
18 use Doctrine\DBAL\Result;
19 use Psr\Log\LoggerAwareInterface;
20 use Psr\Log\LoggerAwareTrait;
24 use TYPO3\CMS\Backend\Utility\BackendUtility;
29 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
41 use TYPO3\CMS\Core\Page\PageRenderer;
52 
58 class ‪PageLayoutView implements LoggerAwareInterface
59 {
60  use LoggerAwareTrait;
61 
67  public ‪$doEdit = true;
68 
75  public ‪$defLangBinding = false;
76 
82  public ‪$tt_contentConfig = [
83  'languageCols' => 0,
84  'languageMode' => 0,
85  'languageColsPointer' => 0,
86  // Displays hidden records as well
87  'showHidden' => 1,
88  // Which language
89  'sys_language_uid' => 0,
90  'cols' => '1,0,2,3',
91  // Which columns can be accessed by current BE user
92  'activeCols' => '1,0,2,3',
93  ];
94 
99  public ‪$tt_contentData = [
100  'prev' => [],
101  'next' => [],
102  ];
103 
109  public ‪$CType_labels = [];
110 
116  public ‪$itemLabels = [];
117 
123  public ‪$id;
124 
130  public ‪$pageRecord = [];
131 
137  protected ‪$siteLanguages = [];
138 
144  protected ‪$pageinfo;
145 
152  protected ‪$contentElementCache = [];
153 
157  protected ‪$iconFactory;
158 
164  protected ‪$languageHasTranslationsCache = [];
165 
169  protected ‪$localizationController;
170 
176  protected ‪$referenceCount = [];
177 
181  protected ‪$uriBuilder;
182 
183  public function ‪__construct()
184  {
185  $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
186  $this->uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
187  $this->localizationController = GeneralUtility::makeInstance(LocalizationController::class);
188  }
189 
196  {
197  $drawingConfiguration = $context->‪getDrawingConfiguration();
198  $languageId = $drawingConfiguration->getSelectedLanguageId();
199  $pageLayoutView = GeneralUtility::makeInstance(self::class);
200  $pageLayoutView->id = $context->‪getPageId();
201  $pageLayoutView->pageinfo = BackendUtility::readPageAccess($pageLayoutView->id, '') ?: [];
202  $pageLayoutView->pageRecord = $context->‪getPageRecord();
203  $pageLayoutView->defLangBinding = $drawingConfiguration->getDefaultLanguageBinding();
204  $pageLayoutView->tt_contentConfig['cols'] = implode(',', $drawingConfiguration->getActiveColumns());
205  $pageLayoutView->tt_contentConfig['activeCols'] = implode(',', $drawingConfiguration->getActiveColumns());
206  $pageLayoutView->tt_contentConfig['showHidden'] = $drawingConfiguration->getShowHidden();
207  $pageLayoutView->tt_contentConfig['sys_language_uid'] = $languageId;
208  if ($drawingConfiguration->getLanguageMode()) {
209  $pageLayoutView->tt_contentConfig['languageMode'] = 1;
210  $pageLayoutView->tt_contentConfig['languageCols'] = $drawingConfiguration->getLanguageColumns();
211  $pageLayoutView->tt_contentConfig['languageColsPointer'] = $languageId;
212  }
213  $pageLayoutView->doEdit = $pageLayoutView->isContentEditable($languageId);
214  $pageLayoutView->CType_labels = $context->‪getContentTypeLabels();
215  $pageLayoutView->itemLabels = $context->‪getItemLabels();
216  return $pageLayoutView;
217  }
218 
219  protected function ‪initialize()
220  {
221  $this->‪resolveSiteLanguages($this->id);
222  $this->pageRecord = BackendUtility::getRecordWSOL('pages', $this->id);
223  $this->pageinfo = BackendUtility::readPageAccess($this->id, '') ?: [];
224  $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
225 
226  $pageActionsInstruction = ‪JavaScriptModuleInstruction::create('@typo3/backend/page-actions.js');
227  if ($this->‪isPageEditable()) {
228  $languageOverlayId = 0;
229  $pageLocalizationRecord = BackendUtility::getRecordLocalization('pages', $this->id, (int)$this->tt_contentConfig['sys_language_uid']);
230  if (is_array($pageLocalizationRecord)) {
231  $pageLocalizationRecord = reset($pageLocalizationRecord);
232  }
233  if (!empty($pageLocalizationRecord['uid'])) {
234  $languageOverlayId = $pageLocalizationRecord['uid'];
235  }
236  $pageActionsInstruction
237  ->invoke('setPageId', (int)$this->id)
238  ->invoke('setLanguageOverlayId', $languageOverlayId);
239  }
240  $pageRenderer->getJavaScriptRenderer()->addJavaScriptModuleInstruction($pageActionsInstruction);
241  // Get labels for CTypes and tt_content element fields in general:
242  $this->CType_labels = [];
243  foreach (‪$GLOBALS['TCA']['tt_content']['columns']['CType']['config']['items'] as $val) {
244  $this->CType_labels[$val[1]] = $this->‪getLanguageService()->sL($val[0]);
245  }
246 
247  $this->itemLabels = [];
248  foreach (‪$GLOBALS['TCA']['tt_content']['columns'] as $name => $val) {
249  $this->itemLabels[$name] = $this->‪getLanguageService()->sL($val['label']);
250  }
251  }
252 
257  protected function ‪getSelectedLanguages(): array
258  {
259  $langList = (string)($this->tt_contentConfig['sys_language_uid'] ?? '');
260  if ($this->tt_contentConfig['languageMode']) {
261  if ($this->tt_contentConfig['languageColsPointer']) {
262  $langList = '0,' . $this->tt_contentConfig['languageColsPointer'];
263  } else {
264  $langList = implode(',', array_keys($this->tt_contentConfig['languageCols']));
265  }
266  }
267  return ‪GeneralUtility::intExplode(',', $langList);
268  }
269 
276  public function ‪getTable_tt_content(‪$id)
277  {
278  $this->id = (int)‪$id;
279  $this->‪initialize();
280  $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
281  ->getConnectionForTable('tt_content')
282  ->getExpressionBuilder();
283 
284  $languageColumn = [];
285  $out = '';
286  $tcaItems = GeneralUtility::makeInstance(BackendLayoutView::class)->getColPosListItemsParsed($this->id);
287  $languageIds = $this->‪getSelectedLanguages();
288  $defaultLanguageElementsByColumn = [];
289  ‪$defLangBinding = [];
290  // For each languages...
291  // If not languageMode, then we'll only be through this once.
292  foreach ($languageIds as $lP) {
293  if (!isset($this->contentElementCache[$lP])) {
294  $this->contentElementCache[$lP] = [];
295  }
296 
297  if (count($languageIds) === 1 || $lP === 0) {
298  $showLanguage = $expressionBuilder->in('sys_language_uid', [$lP, -1]);
299  } else {
300  $showLanguage = $expressionBuilder->eq('sys_language_uid', $lP);
301  }
302  $content = [];
303  $head = [];
304 
305  $backendLayout = $this->‪getBackendLayoutView()->‪getSelectedBackendLayout($this->id);
306  $columns = $backendLayout['__colPosList'];
307  // Select content records per column
308  $contentRecordsPerColumn = $this->‪getContentRecordsPerColumn('tt_content', ‪$id, $columns, $showLanguage);
309  $cList = array_keys($contentRecordsPerColumn);
310  // For each column, render the content into a variable:
311  foreach ($cList as $columnId) {
312  if (!isset($this->contentElementCache[$lP])) {
313  $this->contentElementCache[$lP] = [];
314  }
315 
316  if (!$lP) {
317  $defaultLanguageElementsByColumn[$columnId] = [];
318  }
319 
320  // Start wrapping div
321  $content[$columnId] .= '<div data-colpos="' . $columnId . '" data-language-uid="' . $lP . '" class="t3js-sortable t3js-sortable-lang t3js-sortable-lang-' . $lP . ' t3-page-ce-wrapper';
322  if (empty($contentRecordsPerColumn[$columnId])) {
323  $content[$columnId] .= ' t3-page-ce-empty';
324  }
325  $content[$columnId] .= '">';
326  // Add new content at the top most position
327  $link = '';
328  if ($this->‪isContentEditable()
329  && (!$this->‪checkIfTranslationsExistInLanguage($contentRecordsPerColumn, $lP))
330  ) {
331  $url = (string)$this->uriBuilder->buildUriFromRoute('new_content_element_wizard', [
332  'id' => ‪$id,
333  'sys_language_uid' => $lP,
334  'colPos' => $columnId,
335  'uid_pid' => ‪$id,
336  'returnUrl' => ‪$GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri(),
337  ]);
338  $title = htmlspecialchars($this->‪getLanguageService()->getLL('newContentElement'));
339  $link = '<a href="' . htmlspecialchars($url) . '" '
340  . 'title="' . $title . '"'
341  . 'data-title="' . $title . '"'
342  . 'class="btn btn-default btn-sm t3js-toggle-new-content-element-wizard disabled">'
343  . $this->iconFactory->getIcon('actions-add', ‪Icon::SIZE_SMALL)->render()
344  . ' '
345  . htmlspecialchars($this->‪getLanguageService()->getLL('content')) . '</a>';
346  }
347  if ($this->‪getBackendUser()->checkLanguageAccess($lP) && $columnId !== 'unused') {
348  $content[$columnId] .= '
349  <div class="t3-page-ce t3js-page-ce" data-page="' . (int)‪$id . '" id="' . ‪StringUtility::getUniqueId() . '">
350  <div class="t3-page-ce-actions t3js-page-new-ce" id="colpos-' . $columnId . '-page-' . ‪$id . '-' . ‪StringUtility::getUniqueId() . '">'
351  . $link
352  . '</div>
353  <div class="t3-page-ce-dropzone t3js-page-ce-dropzone-available"></div>
354  </div>
355  ';
356  }
357  $editUidList = '';
358  if (!isset($contentRecordsPerColumn[$columnId]) || !is_array($contentRecordsPerColumn[$columnId])) {
359  $message = GeneralUtility::makeInstance(
360  FlashMessage::class,
361  $this->‪getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:error.invalidBackendLayout'),
362  '',
363  ContextualFeedbackSeverity::WARNING
364  );
365  $service = GeneralUtility::makeInstance(FlashMessageService::class);
366  $queue = $service->getMessageQueueByIdentifier();
367  $queue->addMessage($message);
368  } else {
369  $rowArr = $contentRecordsPerColumn[$columnId];
370  $this->‪generateTtContentDataArray($rowArr);
371 
372  foreach ((array)$rowArr as $rKey => $row) {
373  $this->contentElementCache[$lP][$columnId][$row['uid']] = $row;
374  if ($this->tt_contentConfig['languageMode']) {
375  $languageColumn[$columnId][$lP] = $head[$columnId] . $content[$columnId];
376  }
377  if (is_array($row) && !‪VersionState::cast($row['t3ver_state'])->equals(‪VersionState::DELETE_PLACEHOLDER)) {
378  $singleElementHTML = '<div class="t3-page-ce-element t3-page-ce-dragitem t3js-page-ce-dragitem" id="' . ‪StringUtility::getUniqueId() . '">';
379  if (!$lP && ($this->defLangBinding || $row['sys_language_uid'] != -1)) {
380  $defaultLanguageElementsByColumn[$columnId][] = ($row['_ORIG_uid'] ?? $row['uid']);
381  }
382  $editUidList .= $row['uid'] . ',';
383  $disableMoveAndNewButtons = $this->defLangBinding && $lP > 0 && $this->‪checkIfTranslationsExistInLanguage($contentRecordsPerColumn, $lP);
384  $singleElementHTML .= $this->‪tt_content_drawHeader(
385  $row,
386  0,
387  $disableMoveAndNewButtons,
388  true,
390  );
391  $singleElementHTML .= '<div class="t3-page-ce-body">' . $this->‪tt_content_drawItem($row) . '</div>' . $this->‪tt_content_drawFooter($row);
392  $isDisabled = $this->‪isDisabled('tt_content', $row);
393  $statusHidden = $isDisabled ? ' t3-page-ce-hidden t3js-hidden-record' : '';
394  $displayNone = !$this->tt_contentConfig['showHidden'] && $isDisabled ? ' style="display: none;"' : '';
395  $highlightHeader = '';
396  if ($this->‪checkIfTranslationsExistInLanguage([], (int)$row['sys_language_uid']) && (int)$row['l18n_parent'] === 0) {
397  $highlightHeader = ' t3-page-ce-danger';
398  } elseif ($columnId === 'unused') {
399  $highlightHeader = ' t3-page-ce-warning';
400  }
401  $singleElementHTML = '<div class="t3-page-ce' . $highlightHeader . ' t3js-page-ce t3js-page-ce-sortable ' . $statusHidden . '" id="element-tt_content-'
402  . $row['uid'] . '" data-table="tt_content" data-uid="' . $row['uid'] . '" data-language-uid="'
403  . $row['sys_language_uid'] . '"' . $displayNone . '>' . $singleElementHTML . '</div>';
404 
405  $singleElementHTML .= '<div class="t3-page-ce" data-colpos="' . $columnId . '">';
406  $singleElementHTML .= '<div class="t3-page-ce-actions t3js-page-new-ce" id="colpos-' . $columnId . '-page-' . ‪$id .
407  '-' . ‪StringUtility::getUniqueId() . '">';
408  // Add icon "new content element below"
409  if (!$disableMoveAndNewButtons
410  && $this->‪isContentEditable($lP)
411  && (!$this->‪checkIfTranslationsExistInLanguage($contentRecordsPerColumn, $lP))
412  && $columnId !== 'unused'
413  ) {
414  // New content element
415  $url = (string)$this->uriBuilder->buildUriFromRoute('new_content_element_wizard', [
416  'id' => $row['pid'],
417  'sys_language_uid' => $row['sys_language_uid'],
418  'colPos' => $row['colPos'],
419  'uid_pid' => -$row['uid'],
420  'returnUrl' => ‪$GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri(),
421  ]);
422  $title = htmlspecialchars($this->‪getLanguageService()->getLL('newContentElement'));
423  $singleElementHTML .= '<a href="' . htmlspecialchars($url) . '" '
424  . 'title="' . $title . '"'
425  . 'data-title="' . $title . '"'
426  . 'class="btn btn-default btn-sm t3js-toggle-new-content-element-wizard disabled">'
427  . $this->iconFactory->getIcon('actions-add', ‪Icon::SIZE_SMALL)->render()
428  . ' '
429  . htmlspecialchars($this->‪getLanguageService()->getLL('content')) . '</a>';
430  }
431  $singleElementHTML .= '</div></div><div class="t3-page-ce-dropzone t3js-page-ce-dropzone-available"></div></div>';
432  if ($this->defLangBinding && $this->tt_contentConfig['languageMode']) {
433  ‪$defLangBinding[$columnId][$lP][$row[$lP ? 'l18n_parent' : 'uid'] ?: $row['uid']] = $singleElementHTML;
434  } else {
435  $content[$columnId] .= $singleElementHTML;
436  }
437  } else {
438  unset($rowArr[$rKey]);
439  }
440  }
441  $content[$columnId] .= '</div>';
442  if ($columnId === 'unused') {
443  if (empty($unusedElementsMessage)) {
444  $unusedElementsMessage = GeneralUtility::makeInstance(
445  FlashMessage::class,
446  $this->‪getLanguageService()->getLL('staleUnusedElementsWarning'),
447  $this->‪getLanguageService()->getLL('staleUnusedElementsWarningTitle'),
448  ContextualFeedbackSeverity::WARNING
449  );
450  $service = GeneralUtility::makeInstance(FlashMessageService::class);
451  $queue = $service->getMessageQueueByIdentifier();
452  $queue->addMessage($unusedElementsMessage);
453  }
454  $colTitle = $this->‪getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:unusedColPos');
455  $editParam = '';
456  } else {
457  $colTitle = '';
458  foreach ($tcaItems as $item) {
459  if ($item[1] == $columnId) {
460  $colTitle = $this->‪getLanguageService()->sL($item[0]);
461  }
462  }
463  if (empty($colTitle)) {
464  $colTitle = BackendUtility::getProcessedValue('tt_content', 'colPos', (string)$columnId) ?? '';
465  }
466  $editParam = $this->doEdit && !empty($rowArr)
467  ? '&edit[tt_content][' . $editUidList . ']=edit&recTitle=' . rawurlencode(BackendUtility::getRecordTitle('pages', $this->pageRecord, true))
468  : '';
469  }
470  $head[$columnId] .= $this->‪tt_content_drawColHeader($colTitle, $editParam);
471  }
472  }
473  // For each column, fit the rendered content into a table cell:
474  $out = '';
475  if ($this->tt_contentConfig['languageMode']) {
476  // in language mode process the content elements, but only fill $languageColumn. output will be generated later
477  $sortedLanguageColumn = [];
478  foreach ($cList as $columnId) {
479  if (‪GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnId) || $columnId === 'unused') {
480  $languageColumn[$columnId][$lP] = $head[$columnId] . $content[$columnId];
481 
482  // We sort $languageColumn again according to $cList as it may contain data already from above.
483  $sortedLanguageColumn[$columnId] = $languageColumn[$columnId];
484  }
485  }
486  if (!empty($languageColumn['unused'])) {
487  $sortedLanguageColumn['unused'] = $languageColumn['unused'];
488  }
489  $languageColumn = $sortedLanguageColumn;
490  } else {
491  // GRID VIEW:
492  $grid = '<div class="t3-grid-container"><table class="t3-page-columns t3-grid-table t3js-page-columns">';
493  // Add colgroups
494  $colCount = (int)$backendLayout['__config']['backend_layout.']['colCount'];
495  $rowCount = (int)$backendLayout['__config']['backend_layout.']['rowCount'];
496  $colSpan = 0;
497  $rowSpan = 0;
498  $grid .= '<colgroup>';
499  for ($i = 0; $i < $colCount; $i++) {
500  $grid .= '<col />';
501  }
502  $grid .= '</colgroup>';
503 
504  // Check how to handle restricted columns
505  $hideRestrictedCols = (bool)(BackendUtility::getPagesTSconfig(‪$id)['mod.']['web_layout.']['hideRestrictedCols'] ?? false);
506 
507  // Cycle through rows
508  for ($row = 1; $row <= $rowCount; $row++) {
509  $rowConfig = $backendLayout['__config']['backend_layout.']['rows.'][$row . '.'];
510  if (!isset($rowConfig)) {
511  continue;
512  }
513  $grid .= '<tr>';
514  for ($col = 1; $col <= $colCount; $col++) {
515  $columnConfig = $rowConfig['columns.'][$col . '.'];
516  if (!isset($columnConfig)) {
517  continue;
518  }
519  // Which tt_content colPos should be displayed inside this cell
520  $columnKey = (int)$columnConfig['colPos'];
521  // Render the grid cell
522  $colSpan = (int)$columnConfig['colspan'];
523  $rowSpan = (int)$columnConfig['rowspan'];
524  $grid .= '<td valign="top"' .
525  ($colSpan > 0 ? ' colspan="' . $colSpan . '"' : '') .
526  ($rowSpan > 0 ? ' rowspan="' . $rowSpan . '"' : '') .
527  ' data-colpos="' . (int)$columnConfig['colPos'] . '" data-language-uid="' . $lP . '" class="t3js-page-lang-column-' . $lP . ' t3js-page-column t3-grid-cell t3-page-column t3-page-column-' . $columnKey .
528  ((!isset($columnConfig['colPos']) || $columnConfig['colPos'] === '') ? ' t3-grid-cell-unassigned' : '') .
529  ((isset($columnConfig['colPos']) && $columnConfig['colPos'] !== '' && !$head[$columnKey]) || !‪GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos']) ? ($hideRestrictedCols ? ' t3-grid-cell-restricted t3-grid-cell-hidden' : ' t3-grid-cell-restricted') : '') .
530  ($colSpan > 0 ? ' t3-gridCell-width' . $colSpan : '') .
531  ($rowSpan > 0 ? ' t3-gridCell-height' . $rowSpan : '') . '">';
532 
533  // Draw the pre-generated header with edit and new buttons if a colPos is assigned.
534  // If not, a new header without any buttons will be generated.
535  if (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== '' && $head[$columnKey]
536  && ‪GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos'])
537  ) {
538  $grid .= $head[$columnKey];
539  $grid .= $content[$columnKey];
540  } elseif (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== ''
541  && ‪GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos'])
542  ) {
543  if (!$hideRestrictedCols) {
544  $grid .= $this->‪tt_content_drawColHeader($this->‪getLanguageService()->getLL('noAccess'));
545  }
546  } elseif (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== ''
547  && !‪GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos'])
548  ) {
549  if (!$hideRestrictedCols) {
550  $grid .= $this->‪tt_content_drawColHeader($this->‪getLanguageService()->sL($columnConfig['name']) .
551  ' (' . $this->‪getLanguageService()->getLL('noAccess') . ')');
552  }
553  } elseif (isset($columnConfig['name']) && $columnConfig['name'] !== '') {
554  $grid .= $this->‪tt_content_drawColHeader($this->‪getLanguageService()->sL($columnConfig['name'])
555  . ' (' . $this->‪getLanguageService()->getLL('notAssigned') . ')');
556  } else {
557  $grid .= $this->‪tt_content_drawColHeader($this->‪getLanguageService()->getLL('notAssigned'));
558  }
559 
560  $grid .= '</td>';
561  }
562  $grid .= '</tr>';
563  }
564  if (!empty($content['unused'])) {
565  $grid .= '<tr>';
566  // Which tt_content colPos should be displayed inside this cell
567  $columnKey = 'unused';
568  // Render the grid cell
569  $colSpan = (int)$backendLayout['__config']['backend_layout.']['colCount'];
570  $grid .= '<td valign="top"' .
571  ($colSpan > 0 ? ' colspan="' . $colSpan . '"' : '') .
572  ($rowSpan > 0 ? ' rowspan="' . $rowSpan . '"' : '') .
573  ' data-colpos="unused" data-language-uid="' . $lP . '" class="t3js-page-lang-column-' . $lP . ' t3js-page-column t3-grid-cell t3-page-column t3-page-column-' . $columnKey .
574  ($colSpan > 0 ? ' t3-gridCell-width' . $colSpan : '') . '">';
575 
576  // Draw the pre-generated header with edit and new buttons if a colPos is assigned.
577  // If not, a new header without any buttons will be generated.
578  $grid .= $head[$columnKey] . $content[$columnKey];
579  $grid .= '</td></tr>';
580  }
581  $out .= $grid . '</table></div>';
582  }
583  }
584  // If language mode, then make another presentation:
585  // Notice that THIS presentation will override the value of $out!
586  // But it needs the code above to execute since $languageColumn is filled with content we need!
587  if ($this->tt_contentConfig['languageMode']) {
588  return $this->‪generateLanguageView($languageIds, $defaultLanguageElementsByColumn, $languageColumn, ‪$defLangBinding);
589  }
590  return $out;
591  }
592 
601  protected function ‪generateLanguageView(
602  array $languageIds,
603  array $defaultLanguageElementsByColumn,
604  array $languageColumn,
605  array ‪$defLangBinding
606  ): string {
607  // Get language selector:
608  $languageSelector = $this->‪languageSelector($this->id);
609  // Reset out - we will make new content here:
610  $out = '';
611  // Traverse languages found on the page and build up the table displaying them side by side:
612  $cCont = [];
613  $sCont = [];
614  foreach ($languageIds as $languageId) {
615  $languageMode = '';
616  $labelClass = 'info';
617  // Header:
618  $languageId = (int)$languageId;
619  // Determine language mode
620  if ($languageId > 0 && isset($this->languageHasTranslationsCache[$languageId]['mode'])) {
621  switch ($this->languageHasTranslationsCache[$languageId]['mode']) {
622  case 'mixed':
623  $languageMode = $this->‪getLanguageService()->getLL('languageModeMixed');
624  $labelClass = 'danger';
625  break;
626  case 'connected':
627  $languageMode = $this->‪getLanguageService()->getLL('languageModeConnected');
628  break;
629  case 'free':
630  $languageMode = $this->‪getLanguageService()->getLL('languageModeFree');
631  break;
632  default:
633  // we'll let opcode optimize this intentionally empty case
634  }
635  }
636  $columnAttributes = [
637  'valign' => 'top',
638  'class' => 't3-page-column t3-page-column-lang-name',
639  'data-language-uid' => (string)$languageId,
640  'data-language-title' => $this->siteLanguages[$languageId]->getTitle(),
641  'data-flag-identifier' => $this->siteLanguages[$languageId]->getFlagIdentifier(),
642  ];
643 
644  $cCont[$languageId] = '
645  <td ' . GeneralUtility::implodeAttributes($columnAttributes, true) . '>
646  <h2>' . htmlspecialchars($this->tt_contentConfig['languageCols'][$languageId]) . '</h2>
647  ' . ($languageMode !== '' ? '<span class="badge badge-' . $labelClass . '">' . $languageMode . '</span>' : '') . '
648  </td>';
649 
650  $editLink = '';
651  $recordIcon = '';
652  $viewLink = '';
653  // "View page" icon is added:
654  if (!‪VersionState::cast($this->pageinfo['t3ver_state'])->equals(‪VersionState::DELETE_PLACEHOLDER)) {
655  $attributes = ‪PreviewUriBuilder::create($this->id)
656  ->withRootLine(BackendUtility::BEgetRootLine($this->id))
657  ->withLanguage($languageId)
658  ->serializeDispatcherAttributes();
659  $viewLink = '<a href="#" class="btn btn-default btn-sm" ' . $attributes . ' title="' . htmlspecialchars($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.showPage')) . '">' . $this->iconFactory->getIcon('actions-view', ‪Icon::SIZE_SMALL)->render() . '</a>';
660  }
661  // Language overlay page header:
662  if ($languageId) {
663  $pageLocalizationRecord = BackendUtility::getRecordLocalization('pages', $this->id, $languageId);
664  if (is_array($pageLocalizationRecord)) {
665  $pageLocalizationRecord = reset($pageLocalizationRecord);
666  }
667  BackendUtility::workspaceOL('pages', $pageLocalizationRecord);
668  $recordIcon = BackendUtility::wrapClickMenuOnIcon(
669  $this->iconFactory->getIconForRecord('pages', $pageLocalizationRecord, ‪Icon::SIZE_SMALL)->render(),
670  'pages',
671  $pageLocalizationRecord['uid']
672  );
673  $urlParameters = [
674  'edit' => [
675  'pages' => [
676  $pageLocalizationRecord['uid'] => 'edit',
677  ],
678  ],
679  // Disallow manual adjustment of the language field for pages
680  'overrideVals' => [
681  'pages' => [
682  'sys_language_uid' => $languageId,
683  ],
684  ],
685  'returnUrl' => ‪$GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri(),
686  ];
687  $url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
688  if ($this->‪getBackendUser()->check('tables_modify', 'pages')) {
689  $editLink = '<a href="' . htmlspecialchars($url) . '" class="btn btn-default btn-sm"'
690  . ' title="' . htmlspecialchars($this->‪getLanguageService()->getLL('edit')) . '">'
691  . $this->iconFactory->getIcon('actions-open', ‪Icon::SIZE_SMALL)->render() . '</a>';
692  }
693 
694  $defaultLanguageElements = [];
695  array_walk($defaultLanguageElementsByColumn, static function (array $columnContent) use (&$defaultLanguageElements) {
696  $defaultLanguageElements = array_merge($defaultLanguageElements, $columnContent);
697  });
698 
699  $localizationButtons = [];
700  $localizationButtons[] = $this->‪newLanguageButton(
701  $this->‪getNonTranslatedTTcontentUids($defaultLanguageElements, $this->id, $languageId),
702  $languageId
703  );
704 
705  $languageLabel =
706  '<div class="btn-group">'
707  . $viewLink
708  . $editLink
709  . (!empty($localizationButtons) ? implode(LF, $localizationButtons) : '')
710  . '</div>'
711  . ' ' . $recordIcon . ' ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($pageLocalizationRecord['title'], 20))
712  ;
713  } else {
714  if ($this->‪getBackendUser()->checkLanguageAccess(0)) {
715  $recordIcon = BackendUtility::wrapClickMenuOnIcon(
716  $this->iconFactory->getIconForRecord('pages', $this->pageRecord, ‪Icon::SIZE_SMALL)->render(),
717  'pages',
718  $this->id
719  );
720  $urlParameters = [
721  'edit' => [
722  'pages' => [
723  $this->id => 'edit',
724  ],
725  ],
726  'returnUrl' => ‪$GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri(),
727  ];
728  $url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
729  if ($this->‪getBackendUser()->check('tables_modify', 'pages')) {
730  $editLink = '<a href="' . htmlspecialchars($url) . '" class="btn btn-default btn-sm"'
731  . ' title="' . htmlspecialchars($this->‪getLanguageService()->getLL('edit')) . '">'
732  . $this->iconFactory->getIcon('actions-open', ‪Icon::SIZE_SMALL)->render() . '</a>';
733  }
734  }
735 
736  $languageLabel =
737  '<div class="btn-group">'
738  . $viewLink
739  . $editLink
740  . '</div>'
741  . ' ' . $recordIcon . ' ' . htmlspecialchars(‪GeneralUtility::fixed_lgd_cs($this->pageRecord['title'], 20));
742  }
743  $sCont[$languageId] = '
744  <td class="t3-page-column t3-page-lang-label nowrap">' . $languageLabel . '</td>';
745  }
746  // Add headers:
747  $out .= '<tr>' . implode('', $cCont) . '</tr>';
748  $out .= '<tr>' . implode('', $sCont) . '</tr>';
749  unset($cCont, $sCont);
750 
751  // Traverse previously built content for the columns:
752  foreach ($languageColumn as $cKey => $cCont) {
753  $out .= '<tr>';
754  foreach ($cCont as $languageId => $columnContent) {
755  $out .= '<td valign="top" data-colpos="' . $cKey . '" class="t3-grid-cell t3-page-column t3js-page-column t3js-page-lang-column t3js-page-lang-column-' . $languageId . '">' . $columnContent . '</td>';
756  }
757  $out .= '</tr>';
758  if ($this->defLangBinding && !empty(‪$defLangBinding[$cKey])) {
759  $maxItemsCount = max(array_map('count', ‪$defLangBinding[$cKey]));
760  for ($i = 0; $i < $maxItemsCount; $i++) {
761  $defUid = $defaultLanguageElementsByColumn[$cKey][$i] ?? 0;
762  $cCont = [];
763  foreach ($languageIds as $languageId) {
764  if ($languageId > 0
765  && is_array(‪$defLangBinding[$cKey][$languageId])
766  && !$this->‪checkIfTranslationsExistInLanguage($defaultLanguageElementsByColumn[$cKey], $languageId)
767  && count(‪$defLangBinding[$cKey][$languageId]) > $i
768  ) {
769  $slice = array_slice(‪$defLangBinding[$cKey][$languageId], $i, 1);
770  $element = $slice[0] ?? '';
771  } else {
772  $element = ‪$defLangBinding[$cKey][$languageId][$defUid] ?? '';
773  }
774  $cCont[] = $element;
775  }
776  $out .= '
777  <tr>
778  <td valign="top" class="t3-grid-cell" data-colpos="' . $cKey . '">' . implode('</td>
779  <td valign="top" class="t3-grid-cell" data-colpos="' . $cKey . '">', $cCont) . '</td>
780  </tr>';
781  }
782  }
783  }
784  // Finally, wrap it all in a table and add the language selector on top of it:
785  return $languageSelector . '
786  <div class="t3-grid-container">
787  <table class="t3-page-columns t3-grid-table t3js-page-columns">
788  ' . $out . '
789  </table>
790  </div>';
791  }
792 
803  protected function ‪getContentRecordsPerColumn($table, ‪$id, array $columns, $additionalWhereClause = '')
804  {
805  $contentRecordsPerColumn = array_fill_keys($columns, []);
806  $columns = array_flip($columns);
807  $queryBuilder = $this->‪getQueryBuilder(
808  $table,
809  ‪$id,
810  [
811  $additionalWhereClause,
812  ]
813  );
814 
815  // Traverse any selected elements and render their display code:
816  $result = $queryBuilder->executeQuery();
817  $results = $this->‪getResult($result);
818  $unused = [];
819  $hookArray = ‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['record_is_used'] ?? [];
820  foreach ($results as $record) {
821  $used = isset($columns[$record['colPos']]);
822  foreach ($hookArray as $_funcRef) {
823  $_params = ['columns' => $columns, 'record' => $record, 'used' => $used];
824  $used = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
825  }
826  if ($used) {
827  $columnValue = (string)$record['colPos'];
828  $contentRecordsPerColumn[$columnValue][] = $record;
829  } else {
830  $unused[] = $record;
831  }
832  }
833  if (!empty($unused)) {
834  $contentRecordsPerColumn['unused'] = $unused;
835  }
836  return $contentRecordsPerColumn;
837  }
838 
846  public function ‪tt_content_drawColHeader($colName, $editParams = '')
847  {
848  $icons = '';
849  // Edit whole of column:
850  if ($editParams && $this->‪hasContentModificationAndAccessPermissions() && $this->‪getBackendUser()->checkLanguageAccess(0)) {
851  $link = $this->uriBuilder->buildUriFromRoute('record_edit') . $editParams . '&returnUrl=' . rawurlencode(‪$GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri());
852  $icons = '<a href="' . htmlspecialchars($link) . '" title="'
853  . htmlspecialchars($this->‪getLanguageService()->getLL('editColumn')) . '">'
854  . $this->iconFactory->getIcon('actions-document-open', ‪Icon::SIZE_SMALL)->render() . '</a>';
855  $icons = '<div class="t3-page-column-header-icons">' . $icons . '</div>';
856  }
857  return '<div class="t3-page-column-header">
858  ' . $icons . '
859  <div class="t3-page-column-header-label">' . htmlspecialchars($colName) . '</div>
860  </div>';
861  }
862 
870  protected function ‪tt_content_drawFooter(array $row)
871  {
872  $content = '';
873  // Get processed values:
874  $info = [];
875  $this->‪getProcessedValue('tt_content', 'starttime,endtime,fe_group,space_before_class,space_after_class', $row, $info);
876 
877  // Content element annotation
878  if (!empty(‪$GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn']) && !empty($row[‪$GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn']])) {
879  $info[] = htmlspecialchars($row[‪$GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn']]);
880  }
881 
882  // Call drawFooter hooks
883  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawFooter'] ?? [] as $className) {
884  $hookObject = GeneralUtility::makeInstance($className);
885  if (!$hookObject instanceof PageLayoutViewDrawFooterHookInterface) {
886  throw new \UnexpectedValueException($className . ' must implement interface ' . PageLayoutViewDrawFooterHookInterface::class, 1404378171);
887  }
888  $hookObject->preProcess($this, $info, $row);
889  }
890 
891  // Display info from records fields:
892  if (!empty($info)) {
893  $content = '<div class="t3-page-ce-info">' . implode('<br>', $info) . '</div>';
894  }
895  if (!empty($content)) {
896  $content = '<div class="t3-page-ce-footer">' . $content . '</div>';
897  }
898  return $content;
899  }
900 
911  public function ‪tt_content_drawHeader($row, $space = 0, $disableMoveAndNewButtons = false, $langMode = false, $dragDropEnabled = false)
912  {
913  $backendUser = $this->‪getBackendUser();
914  $out = '';
915  // Render control panel for the element
916  if ($backendUser->recordEditAccessInternals('tt_content', $row) && $this->isContentEditable($row['sys_language_uid'])) {
917  // Edit content element:
918  $urlParameters = [
919  'edit' => [
920  'tt_content' => [
921  $row['uid'] => 'edit',
922  ],
923  ],
924  'returnUrl' => ‪$GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri() . '#element-tt_content-' . $row['uid'],
925  ];
926  $url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters) . '#element-tt_content-' . $row['uid'];
927 
928  $out .= '<a class="btn btn-default" href="' . htmlspecialchars($url)
929  . '" title="' . htmlspecialchars($this->‪getLanguageService()->getLL('edit'))
930  . '">' . $this->iconFactory->getIcon('actions-open', ‪Icon::SIZE_SMALL)->render() . '</a>';
931  // Hide element:
932  $hiddenField = ‪$GLOBALS['TCA']['tt_content']['ctrl']['enablecolumns']['disabled'];
933  if ($hiddenField && ‪$GLOBALS['TCA']['tt_content']['columns'][$hiddenField]
934  && (!‪$GLOBALS['TCA']['tt_content']['columns'][$hiddenField]['exclude']
935  || $backendUser->check('non_exclude_fields', 'tt_content:' . $hiddenField))
936  ) {
937  if ($row[$hiddenField]) {
938  $value = 0;
939  $label = 'unHide';
940  } else {
941  $value = 1;
942  $label = 'hide';
943  }
944  $params = '&data[tt_content][' . ($row['_ORIG_uid'] ?: $row['uid'])
945  . '][' . $hiddenField . ']=' . $value;
946  $out .= '<a class="btn btn-default" href="' . htmlspecialchars(BackendUtility::getLinkToDataHandlerAction($params))
947  . '#element-tt_content-' . $row['uid'] . '" title="' . htmlspecialchars($this->‪getLanguageService()->getLL($label)) . '">'
948  . $this->iconFactory->getIcon('actions-edit-' . strtolower($label), ‪Icon::SIZE_SMALL)->render() . '</a>';
949  }
950  // Delete
951  $disableDelete = (bool)\trim(
952  $backendUser->getTSConfig()['options.']['disableDelete.']['tt_content']
953  ?? $backendUser->getTSConfig()['options.']['disableDelete']
954  ?? '0'
955  );
956  if (!$disableDelete) {
957  $params = '&cmd[tt_content][' . $row['uid'] . '][delete]=1';
958 
959  $recordInfo = $row['header'] ?? '';
960  if ($this->‪getBackendUser()->shallDisplayDebugInformation()) {
961  $recordInfo .= ' [' . 'tt_content' . ':' . $row['uid'] . ']';
962  }
963 
964  $refCountMsg = BackendUtility::referenceCount(
965  'tt_content',
966  $row['uid'],
967  ' ' . $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.referencesToRecord'),
968  (string)$this->‪getReferenceCount('tt_content', $row['uid'])
969  ) . BackendUtility::translationCount(
970  'tt_content',
971  $row['uid'],
972  ' ' . $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.translationsOfRecord')
973  );
974 
975  $out .= '<a class="btn btn-default t3js-modal-trigger" href="' . htmlspecialchars(BackendUtility::getLinkToDataHandlerAction($params)) . '"'
976  . ' data-severity="warning"'
977  . ' data-title="' . htmlspecialchars($this->‪getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:label.confirm.delete_record.title')) . '"'
978  . ' data-bs-content="' . htmlspecialchars(sprintf($this->‪getLanguageService()->getLL('deleteWarning'), trim($recordInfo)) . $refCountMsg) . '" '
979  . ' data-button-close-text="' . htmlspecialchars($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:cancel')) . '"'
980  . ' title="' . htmlspecialchars($this->‪getLanguageService()->getLL('deleteItem')) . '">'
981  . $this->iconFactory->getIcon('actions-edit-delete', ‪Icon::SIZE_SMALL)->render() . '</a>';
983  $out = '<div class="btn-group btn-group-sm" role="group">' . $out . '</div>';
984  } else {
985  $out = '';
986  }
987  }
988  if (!$disableMoveAndNewButtons) {
989  $moveButtonContent = '';
990  $displayMoveButtons = false;
991  // Move element up:
992  if ($this->tt_contentData['prev'][$row['uid']]) {
993  $params = '&cmd[tt_content][' . $row['uid'] . '][move]=' . $this->tt_contentData['prev'][$row['uid']];
994  $moveButtonContent .= '<a class="btn btn-default" href="'
995  . htmlspecialchars(BackendUtility::getLinkToDataHandlerAction($params))
996  . '" title="' . htmlspecialchars($this->‪getLanguageService()->getLL('moveUp')) . '">'
997  . $this->iconFactory->getIcon('actions-move-up', ‪Icon::SIZE_SMALL)->render() . '</a>';
998  if (!$dragDropEnabled) {
999  $displayMoveButtons = true;
1000  }
1001  } else {
1002  $moveButtonContent .= '<span class="btn btn-default disabled">' . $this->iconFactory->getIcon('empty-empty', ‪Icon::SIZE_SMALL)->render() . '</span>';
1003  }
1004  // Move element down:
1005  if ($this->tt_contentData['next'][$row['uid']]) {
1006  $params = '&cmd[tt_content][' . $row['uid'] . '][move]= ' . $this->tt_contentData['next'][$row['uid']];
1007  $moveButtonContent .= '<a class="btn btn-default" href="'
1008  . htmlspecialchars(BackendUtility::getLinkToDataHandlerAction($params))
1009  . '" title="' . htmlspecialchars($this->‪getLanguageService()->getLL('moveDown')) . '">'
1010  . $this->iconFactory->getIcon('actions-move-down', ‪Icon::SIZE_SMALL)->render() . '</a>';
1011  if (!$dragDropEnabled) {
1012  $displayMoveButtons = true;
1013  }
1014  } else {
1015  $moveButtonContent .= '<span class="btn btn-default disabled">' . $this->iconFactory->getIcon('empty-empty', ‪Icon::SIZE_SMALL)->render() . '</span>';
1016  }
1017  if ($displayMoveButtons) {
1018  $out .= '<div class="btn-group btn-group-sm" role="group">' . $moveButtonContent . '</div>';
1019  }
1020  }
1021  }
1022  $allowDragAndDrop = $this->‪isDragAndDropAllowed($row);
1023  $additionalIcons = [];
1024  $additionalIcons[] = $this->‪getIcon('tt_content', $row) . ' ';
1025  if ($langMode && isset($this->siteLanguages[(int)$row['sys_language_uid']])) {
1026  $additionalIcons[] = $this->‪renderLanguageFlag($this->siteLanguages[(int)$row['sys_language_uid']]);
1027  }
1028  // Get record locking status:
1029  if ($lockInfo = BackendUtility::isRecordLocked('tt_content', $row['uid'])) {
1030  $additionalIcons[] = '<a href="#" data-bs-toggle="tooltip" title="' . htmlspecialchars($lockInfo['msg']) . '">'
1031  . $this->iconFactory->getIcon('status-user-backend', ‪Icon::SIZE_SMALL, 'overlay-edit')->render() . '</a>';
1032  }
1033  // Call stats information hook
1034  $_params = ['tt_content', $row['uid'], &$row];
1035  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['recStatInfoHooks'] ?? [] as $_funcRef) {
1036  $additionalIcons[] = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1037  }
1038 
1039  $contentType = $this->CType_labels[$row['CType']];
1040  if (!isset($contentType)) {
1041  $contentType = BackendUtility::getLabelFromItemListMerged($row['pid'], 'tt_content', 'CType', $row['CType']);
1042  }
1043 
1044  // Wrap the whole header
1045  // NOTE: end-tag for <div class="t3-page-ce-body"> is in getTable_tt_content()
1046  return '<div class="t3-page-ce-header ' . ($allowDragAndDrop ? 't3-page-ce-header-draggable t3js-page-ce-draghandle' : '') . '">
1047  <div class="t3-page-ce-header-left">' . implode('', $additionalIcons) . '</div>
1048  <div class="t3-page-ce-header-title">' . $contentType . '</div>
1049  <div class="t3-page-ce-header-right">' . ($out ? '<div class="btn-toolbar">' . $out . '</div>' : '') . '</div>
1050  </div>
1051  <div class="t3-page-ce-body">';
1052  }
1053 
1062  protected function ‪getReferenceCount(string $tableName, int $uid): int
1063  {
1064  if (!isset($this->referenceCount[$tableName][$uid])) {
1065  $referenceIndex = GeneralUtility::makeInstance(ReferenceIndex::class);
1066  $numberOfReferences = $referenceIndex->getNumberOfReferencedRecords($tableName, $uid);
1067  $this->referenceCount[$tableName][$uid] = $numberOfReferences;
1068  }
1069  return $this->referenceCount[$tableName][$uid];
1070  }
1071 
1078  protected function ‪isDragAndDropAllowed(array $row)
1079  {
1080  if ((int)$row['l18n_parent'] === 0 &&
1081  (
1082  $this->‪getBackendUser()->isAdmin()
1083  || ((int)$row['editlock'] === 0 && (int)$this->pageinfo['editlock'] === 0)
1085  && $this->‪getBackendUser()->checkAuthMode('tt_content', 'CType', $row['CType'])
1086  )
1087  ) {
1088  return true;
1089  }
1090  return false;
1091  }
1092 
1100  public function ‪tt_content_drawItem($row)
1101  {
1102  $out = '';
1103  $outHeader = $this->‪renderContentElementHeader($row);
1104  $drawItem = true;
1105  // Hook: Render an own preview of a record
1106  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawItem'] ?? [] as $className) {
1107  $hookObject = GeneralUtility::makeInstance($className);
1108  if (!$hookObject instanceof PageLayoutViewDrawItemHookInterface) {
1109  throw new \UnexpectedValueException($className . ' must implement interface ' . PageLayoutViewDrawItemHookInterface::class, 1218547409);
1110  }
1111  $hookObject->preProcess($this, $drawItem, $outHeader, $out, $row);
1112  }
1113 
1114  // If the previous hook did not render something,
1115  // then check if a Fluid-based preview template was defined for this CType
1116  // and render it via Fluid. Possible option:
1117  // mod.web_layout.tt_content.preview.media = EXT:site_mysite/Resources/Private/Templates/Preview/Media.html
1118  if ($drawItem) {
1120  if ($fluidPreview !== null) {
1121  $out .= $fluidPreview;
1122  $drawItem = false;
1123  }
1124  }
1125 
1126  // Draw preview of the item depending on its CType (if not disabled by previous hook)
1127  if ($drawItem) {
1128  $out .= $this->‪renderContentElementPreview($row);
1129  }
1130  $out = $outHeader . $out;
1131 
1132  return $out;
1133  }
1134 
1135  public function ‪renderContentElementHeader(array $row): string
1136  {
1137  $outHeader = '';
1138  // Make header:
1139  if ($row['header']) {
1140  $hiddenHeaderNote = '';
1141  // If header layout is set to 'hidden', display an accordant note:
1142  if ($row['header_layout'] == 100) {
1143  $hiddenHeaderNote = ' <em>[' . htmlspecialchars($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.hidden')) . ']</em>';
1144  }
1145  $outHeader = $row['date']
1146  ? htmlspecialchars($this->itemLabels['date'] . ' ' . BackendUtility::date($row['date'])) . '<br />'
1147  : '';
1148  $outHeader .= '<strong>' . $this->‪linkEditContent($this->‪renderText($row['header']), $row)
1149  . $hiddenHeaderNote . '</strong><br />';
1150  }
1151  return $outHeader;
1152  }
1153 
1154  public function ‪renderContentElementPreviewFromFluidTemplate(array $row): ?string
1155  {
1156  $tsConfig = BackendUtility::getPagesTSconfig($row['pid'])['mod.']['web_layout.']['tt_content.']['preview.'] ?? [];
1157  $fluidTemplateFile = '';
1158 
1159  if ($row['CType'] === 'list' && !empty($row['list_type'])
1160  && !empty($tsConfig['list.'][$row['list_type']])
1161  ) {
1162  $fluidTemplateFile = $tsConfig['list.'][$row['list_type']];
1163  } elseif (!empty($tsConfig[$row['CType']])) {
1164  $fluidTemplateFile = $tsConfig[$row['CType']];
1165  }
1166 
1167  if ($fluidTemplateFile) {
1168  $fluidTemplateFile = GeneralUtility::getFileAbsFileName($fluidTemplateFile);
1169  if ($fluidTemplateFile) {
1170  try {
1171  $view = GeneralUtility::makeInstance(StandaloneView::class);
1172  $view->setTemplatePathAndFilename($fluidTemplateFile);
1173  $view->assignMultiple($row);
1174  if (!empty($row['pi_flexform'])) {
1175  $flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
1176  $view->assign('pi_flexform_transformed', $flexFormService->convertFlexFormContentToArray($row['pi_flexform']));
1177  }
1178  return $view->render();
1179  } catch (\Exception $e) {
1180  $this->logger->warning('The backend preview for content element {uid} can not be rendered using the Fluid template file "{file}"', [
1181  'uid' => $row['uid'],
1182  'file' => $fluidTemplateFile,
1183  $e->getMessage(),
1184  ]);
1185 
1186  if ($this->‪getBackendUser()->shallDisplayDebugInformation()) {
1187  $view = GeneralUtility::makeInstance(StandaloneView::class);
1188  $view->assign('error', [
1189  'message' => str_replace(‪Environment::getProjectPath(), '', $e->getMessage()),
1190  'title' => 'Error while rendering FluidTemplate preview using ' . str_replace(‪Environment::getProjectPath(), '', $fluidTemplateFile),
1191  ]);
1192  $view->setTemplateSource('<f:be.infobox title="{error.title}" state="2">{error.message}</f:be.infobox>');
1193  return $view->render();
1194  }
1195  }
1196  }
1197  }
1198  return null;
1199  }
1200 
1206  public function ‪renderContentElementPreview(array $row): string
1207  {
1208  $previewHtml = '';
1209  switch ($row['CType']) {
1210  case 'header':
1211  if ($row['subheader']) {
1212  $previewHtml = $this->‪linkEditContent($this->‪renderText($row['subheader']), $row) . '<br>';
1213  }
1214  break;
1215  case 'bullets':
1216  case 'table':
1217  if ($row['bodytext']) {
1218  $previewHtml = $this->‪linkEditContent($this->‪renderText($row['bodytext']), $row) . '<br>';
1219  }
1220  break;
1221  case 'uploads':
1222  if ($row['media']) {
1223  $previewHtml = $this->‪linkEditContent($this->‪getThumbCodeUnlinked($row, 'tt_content', 'media'), $row) . '<br>';
1224  }
1225  break;
1226  case 'shortcut':
1227  if (!empty($row['records'])) {
1228  $shortcutContent = [];
1229  $recordList = explode(',', $row['records']);
1230  foreach ($recordList as $recordIdentifier) {
1231  $split = BackendUtility::splitTable_Uid($recordIdentifier);
1232  $tableName = empty($split[0]) ? 'tt_content' : $split[0];
1233  $shortcutRecord = BackendUtility::getRecord($tableName, $split[1]);
1234  if (is_array($shortcutRecord)) {
1235  $icon = $this->iconFactory->getIconForRecord($tableName, $shortcutRecord, ‪Icon::SIZE_SMALL)->render();
1236  $icon = BackendUtility::wrapClickMenuOnIcon(
1237  $icon,
1238  $tableName,
1239  $shortcutRecord['uid']
1240  );
1241  $shortcutContent[] = $icon
1242  . htmlspecialchars(BackendUtility::getRecordTitle($tableName, $shortcutRecord));
1243  }
1244  }
1245  $previewHtml = implode('<br>', $shortcutContent) . '<br>';
1246  }
1247  break;
1248  case 'list':
1249  $hookOut = '';
1250  $_params = ['pObj' => &$this, 'row' => $row];
1251  foreach (
1252  ‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['list_type_Info'][$row['list_type']] ??
1253  ‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['list_type_Info']['_DEFAULT'] ??
1254  [] as $_funcRef
1255  ) {
1256  $hookOut .= GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1257  }
1258  if ((string)$hookOut !== '') {
1259  $previewHtml = $hookOut;
1260  } elseif (!empty($row['list_type'])) {
1261  $label = BackendUtility::getLabelFromItemListMerged($row['pid'], 'tt_content', 'list_type', $row['list_type']);
1262  if (!empty($label)) {
1263  $previewHtml = $this->‪linkEditContent('<strong>' . htmlspecialchars($this->‪getLanguageService()->sL($label)) . '</strong>', $row) . '<br />';
1264  } else {
1265  $message = sprintf($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.noMatchingValue'), $row['list_type']);
1266  $previewHtml = '<span class="badge badge-warning">' . htmlspecialchars($message) . '</span>';
1267  }
1268  } else {
1269  $previewHtml = '<strong>' . $this->‪getLanguageService()->getLL('noPluginSelected') . '</strong>';
1270  }
1271  $previewHtml .= htmlspecialchars($this->‪getLanguageService()->sL(
1272  BackendUtility::getLabelFromItemlist('tt_content', 'pages', $row['pages'])
1273  )) . '<br />';
1274  break;
1275  default:
1276  $contentType = $this->CType_labels[$row['CType']];
1277  if (!isset($contentType)) {
1278  $contentType = BackendUtility::getLabelFromItemListMerged($row['pid'], 'tt_content', 'CType', $row['CType']);
1279  }
1280 
1281  if ($contentType) {
1282  $previewHtml = '';
1283  if ($row['bodytext']) {
1284  $previewHtml .= $this->‪linkEditContent($this->‪renderText($row['bodytext']), $row) . '<br />';
1285  }
1286  if ($row['image']) {
1287  $previewHtml .= $this->‪linkEditContent($this->‪getThumbCodeUnlinked($row, 'tt_content', 'image'), $row) . '<br />';
1288  }
1289  } else {
1290  $message = sprintf(
1291  $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.noMatchingValue'),
1292  $row['CType']
1293  );
1294  $previewHtml = '<span class="badge badge-warning">' . htmlspecialchars($message) . '</span>';
1295  }
1296  }
1297  return $previewHtml;
1298  }
1299 
1310  public function ‪getNonTranslatedTTcontentUids($defaultLanguageUids, ‪$id, $lP)
1311  {
1312  if ($lP && !empty($defaultLanguageUids)) {
1313  // Select all translations here:
1314  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1315  ->getQueryBuilderForTable('tt_content');
1316  $queryBuilder->getRestrictions()
1317  ->removeAll()
1318  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1319  ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, 0));
1320  $queryBuilder
1321  ->select('*')
1322  ->from('tt_content')
1323  ->where(
1324  $queryBuilder->expr()->eq(
1325  'sys_language_uid',
1326  $queryBuilder->createNamedParameter($lP, \PDO::PARAM_INT)
1327  ),
1328  $queryBuilder->expr()->in(
1329  'l18n_parent',
1330  $queryBuilder->createNamedParameter($defaultLanguageUids, Connection::PARAM_INT_ARRAY)
1331  )
1332  );
1333 
1334  $result = $queryBuilder->executeQuery();
1335 
1336  // Flip uids:
1337  $defaultLanguageUids = array_flip($defaultLanguageUids);
1338  // Traverse any selected elements and unset original UID if any:
1339  while ($row = $result->fetchAssociative()) {
1340  BackendUtility::workspaceOL('tt_content', $row);
1341  unset($defaultLanguageUids[$row['l18n_parent']]);
1342  }
1343  // Flip again:
1344  $defaultLanguageUids = array_keys($defaultLanguageUids);
1345  }
1346  return $defaultLanguageUids;
1347  }
1348 
1356  public function ‪newLanguageButton($defaultLanguageUids, $lP)
1357  {
1358  $lP = (int)$lP;
1359  if (!$this->doEdit || !$lP || !$this->‪hasContentModificationAndAccessPermissions()) {
1360  return '';
1361  }
1362  $theNewButton = '';
1363 
1364  $localizationTsConfig = BackendUtility::getPagesTSconfig($this->id)['mod.']['web_layout.']['localization.'] ?? [];
1365  $allowCopy = (bool)($localizationTsConfig['enableCopy'] ?? true);
1366  $allowTranslate = (bool)($localizationTsConfig['enableTranslate'] ?? true);
1367  if (!empty($this->languageHasTranslationsCache[$lP])) {
1368  if (isset($this->languageHasTranslationsCache[$lP]['hasStandAloneContent'])) {
1369  $allowTranslate = false;
1370  }
1371  if (isset($this->languageHasTranslationsCache[$lP]['hasTranslations'])) {
1372  $allowCopy = $allowCopy && !$this->languageHasTranslationsCache[$lP]['hasTranslations'];
1373  }
1374  }
1375 
1376  if (isset($this->contentElementCache[$lP]) && is_array($this->contentElementCache[$lP])) {
1377  foreach ($this->contentElementCache[$lP] as $column => $records) {
1378  foreach ($records as $record) {
1379  $key = array_search($record['l10n_source'], $defaultLanguageUids);
1380  if ($key !== false) {
1381  unset($defaultLanguageUids[$key]);
1382  }
1383  }
1384  }
1385  }
1386 
1387  if (!empty($defaultLanguageUids)) {
1388  $theNewButton =
1389  '<a'
1390  . ' href="#"'
1391  . ' class="btn btn-default btn-sm t3js-localize disabled"'
1392  . ' title="' . htmlspecialchars($this->‪getLanguageService()->getLL('newPageContent_translate')) . '"'
1393  . ' data-page="' . htmlspecialchars($this->‪getLocalizedPageTitle()) . '"'
1394  . ' data-has-elements="' . (int)!empty($this->contentElementCache[$lP]) . '"'
1395  . ' data-allow-copy="' . (int)$allowCopy . '"'
1396  . ' data-allow-translate="' . (int)$allowTranslate . '"'
1397  . ' data-table="tt_content"'
1398  . ' data-page-id="' . (int)‪GeneralUtility::_GP('id') . '"'
1399  . ' data-language-id="' . $lP . '"'
1400  . ' data-language-name="' . htmlspecialchars($this->tt_contentConfig['languageCols'][$lP]) . '"'
1401  . '>'
1402  . $this->iconFactory->getIcon('actions-localize', ‪Icon::SIZE_SMALL)->render()
1403  . ' ' . htmlspecialchars($this->‪getLanguageService()->getLL('newPageContent_translate'))
1404  . '</a>';
1405  }
1406 
1407  return $theNewButton;
1408  }
1409 
1419  public function ‪linkEditContent($str, $row)
1420  {
1421  if ($this->doEdit
1423  && $this->‪getBackendUser()->recordEditAccessInternals('tt_content', $row)
1424  ) {
1425  $urlParameters = [
1426  'edit' => [
1427  'tt_content' => [
1428  $row['uid'] => 'edit',
1429  ],
1430  ],
1431  'returnUrl' => ‪$GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri() . '#element-tt_content-' . $row['uid'],
1432  ];
1433  $url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
1434  return '<a href="' . htmlspecialchars($url) . '" title="' . htmlspecialchars($this->‪getLanguageService()->getLL('edit')) . '">' . $str . '</a>';
1435  }
1436  return $str;
1437  }
1438 
1448  public function ‪languageSelector(‪$id)
1449  {
1450  if (!$this->‪getBackendUser()->check('tables_modify', 'pages')) {
1451  return '';
1452  }
1453  ‪$id = (int)‪$id;
1454 
1455  // First, select all languages that are available for the current user
1456  $availableTranslations = [];
1457  foreach ($this->siteLanguages as $language) {
1458  if ($language->getLanguageId() <= 0) {
1459  continue;
1460  }
1461  $availableTranslations[$language->getLanguageId()] = $language->getTitle();
1462  }
1463 
1464  // Then, subtract the languages which are already on the page:
1465  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
1466  $queryBuilder->getRestrictions()->removeAll()
1467  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1468  ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, (int)$this->‪getBackendUser()->workspace));
1469  $queryBuilder->select('uid', ‪$GLOBALS['TCA']['pages']['ctrl']['languageField'])
1470  ->from('pages')
1471  ->where(
1472  $queryBuilder->expr()->eq(
1473  ‪$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
1474  $queryBuilder->createNamedParameter(‪$id, \PDO::PARAM_INT)
1475  )
1476  );
1477  $statement = $queryBuilder->executeQuery();
1478  while ($row = $statement->fetchAssociative()) {
1479  unset($availableTranslations[(int)$row[‪$GLOBALS['TCA']['pages']['ctrl']['languageField']]]);
1480  }
1481  // If any languages are left, make selector:
1482  if (!empty($availableTranslations)) {
1483  ‪$output = '<option value="">' . htmlspecialchars($this->‪getLanguageService()->getLL('new_language')) . '</option>';
1484  foreach ($availableTranslations as $languageUid => $languageTitle) {
1485  // Build localize command URL to DataHandler (tce_db)
1486  // which redirects to FormEngine (record_edit)
1487  // which, when finished editing should return back to the current page (returnUrl)
1488  $parameters = [
1489  'justLocalized' => 'pages:' . ‪$id . ':' . $languageUid,
1490  'returnUrl' => ‪$GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri(),
1491  ];
1492  $redirectUrl = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $parameters);
1493  $targetUrl = BackendUtility::getLinkToDataHandlerAction(
1494  '&cmd[pages][' . ‪$id . '][localize]=' . $languageUid,
1495  $redirectUrl
1496  );
1497 
1498  ‪$output .= '<option value="' . htmlspecialchars($targetUrl) . '">' . htmlspecialchars($languageTitle) . '</option>';
1499  }
1501  return '<div class="row row-cols-auto align-items-end g-3 mb-3">'
1502  . '<div class="col">'
1503  . '<select class="form-select" name="createNewLanguage" data-global-event="change" data-action-navigate="$value">'
1504  . ‪$output
1505  . '</select></div></div>';
1506  }
1507  return '';
1508  }
1509 
1516  public function ‪getResult($result): array
1517  {
1518  ‪$output = [];
1519  // Traverse the result:
1520  while ($row = $result->fetchAssociative()) {
1521  BackendUtility::workspaceOL('tt_content', $row, -99, true);
1522  if ($row) {
1523  // Add the row to the array:
1524  ‪$output[] = $row;
1525  }
1526  }
1528  // Return selected records
1529  return ‪$output;
1530  }
1531 
1532  /********************************
1533  *
1534  * Various helper functions
1535  *
1536  ********************************/
1537 
1543  protected function ‪generateTtContentDataArray(array $rowArray)
1544  {
1545  if (empty($this->tt_contentData)) {
1546  $this->tt_contentData = [
1547  'next' => [],
1548  'prev' => [],
1549  ];
1550  }
1551  foreach ($rowArray as $key => $value) {
1552  // Create information for next and previous content elements
1553  if (isset($rowArray[$key - 1])) {
1554  if (isset($rowArray[$key - 2])) {
1555  $this->tt_contentData['prev'][$value['uid']] = -$rowArray[$key - 2]['uid'];
1556  } else {
1557  $this->tt_contentData['prev'][$value['uid']] = $value['pid'];
1558  }
1559  $this->tt_contentData['next'][$rowArray[$key - 1]['uid']] = -$value['uid'];
1560  }
1561  }
1562  }
1563 
1570  public function ‪renderText($input)
1571  {
1572  $input = strip_tags($input);
1573  $input = ‪GeneralUtility::fixed_lgd_cs($input, 1500);
1574  return nl2br(htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8', false));
1575  }
1576 
1584  public function ‪getIcon($table, $row)
1585  {
1586  // Initialization
1587  $toolTip = BackendUtility::getRecordToolTip($row, $table);
1588  $icon = '<span ' . $toolTip . '>' . $this->iconFactory->getIconForRecord($table, $row, ‪Icon::SIZE_SMALL)->render() . '</span>';
1589  // The icon with link
1590  if ($this->‪getBackendUser()->recordEditAccessInternals($table, $row)) {
1591  $icon = BackendUtility::wrapClickMenuOnIcon($icon, $table, $row['uid']);
1592  }
1593  return $icon;
1594  }
1595 
1605  public function ‪getProcessedValue($table, $fieldList, array $row, array &$info)
1606  {
1607  // Splitting values from $fieldList
1608  $fieldArr = explode(',', $fieldList);
1609  // Traverse fields from $fieldList
1610  foreach ($fieldArr as $field) {
1611  if ($row[$field]) {
1612  $info[] = '<strong>' . htmlspecialchars($this->itemLabels[$field]) . '</strong> '
1613  . htmlspecialchars(BackendUtility::getProcessedValue($table, $field, $row[$field]) ?? '');
1614  }
1615  }
1616  }
1617 
1625  public function ‪isDisabled($table, $row)
1626  {
1627  $enableCols = ‪$GLOBALS['TCA'][$table]['ctrl']['enablecolumns'];
1628  return $enableCols['disabled'] && $row[$enableCols['disabled']]
1629  || $enableCols['starttime'] && $row[$enableCols['starttime']] > ‪$GLOBALS['EXEC_TIME']
1630  || $enableCols['endtime'] && $row[$enableCols['endtime']] && $row[$enableCols['endtime']] < ‪$GLOBALS['EXEC_TIME'];
1631  }
1632 
1633  /*****************************************
1634  *
1635  * External renderings
1636  *
1637  *****************************************/
1638 
1647  public function ‪getThumbCodeUnlinked($row, $table, $field)
1648  {
1649  return BackendUtility::thumbCode($row, $table, $field, '', '', null, 0, '', '', false);
1650  }
1651 
1660  protected function ‪checkIfTranslationsExistInLanguage(array $contentElements, int $language)
1661  {
1662  // If in default language, you may always create new entries
1663  // Also, you may override this strict behavior via user TS Config
1664  // If you do so, you're on your own and cannot rely on any support by the TYPO3 core
1665  // We jump out here since we don't need to do the expensive loop operations
1666  $allowInconsistentLanguageHandling = (bool)(BackendUtility::getPagesTSconfig($this->id)['mod.']['web_layout.']['allowInconsistentLanguageHandling'] ?? false);
1667  if ($language === 0 || $allowInconsistentLanguageHandling) {
1668  return false;
1669  }
1673  if (!isset($this->languageHasTranslationsCache[$language])) {
1674  foreach ($contentElements as $columns) {
1675  foreach ($columns as $contentElement) {
1676  if ((int)$contentElement['l18n_parent'] === 0) {
1677  $this->languageHasTranslationsCache[$language]['hasStandAloneContent'] = true;
1678  $this->languageHasTranslationsCache[$language]['mode'] = 'free';
1679  }
1680  if ((int)$contentElement['l18n_parent'] > 0) {
1681  $this->languageHasTranslationsCache[$language]['hasTranslations'] = true;
1682  $this->languageHasTranslationsCache[$language]['mode'] = 'connected';
1683  }
1684  }
1685  }
1686  if (!isset($this->languageHasTranslationsCache[$language])) {
1687  $this->languageHasTranslationsCache[$language]['hasTranslations'] = false;
1688  }
1689  // Check whether we have a mix of both
1690  if (isset($this->languageHasTranslationsCache[$language]['hasStandAloneContent'])
1691  && $this->languageHasTranslationsCache[$language]['hasTranslations']
1692  ) {
1693  $this->languageHasTranslationsCache[$language]['mode'] = 'mixed';
1694  $siteLanguage = $this->siteLanguages[$language];
1695  $message = GeneralUtility::makeInstance(
1696  FlashMessage::class,
1697  $this->‪getLanguageService()->getLL('staleTranslationWarning'),
1698  sprintf($this->‪getLanguageService()->getLL('staleTranslationWarningTitle'), $siteLanguage->getTitle()),
1699  ContextualFeedbackSeverity::WARNING
1700  );
1701  $service = GeneralUtility::makeInstance(FlashMessageService::class);
1702  $queue = $service->getMessageQueueByIdentifier();
1703  $queue->addMessage($message);
1704  }
1705  }
1706 
1707  return $this->languageHasTranslationsCache[$language]['hasTranslations'];
1708  }
1709 
1713  protected function ‪getBackendLayoutView()
1714  {
1715  return GeneralUtility::makeInstance(BackendLayoutView::class);
1716  }
1717 
1718  protected function ‪getBackendUser(): ‪BackendUserAuthentication
1719  {
1720  return ‪$GLOBALS['BE_USER'];
1721  }
1722 
1731  public function ‪thumbCode($row, $table, $field)
1732  {
1733  return BackendUtility::thumbCode($row, $table, $field);
1734  }
1735 
1745  public function ‪getQueryBuilder(
1746  string $table,
1747  int $pageId,
1748  array $additionalConstraints = [],
1749  array ‪$fields = ['*']
1750  ): QueryBuilder {
1751  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1752  ->getQueryBuilderForTable($table);
1753  $queryBuilder->getRestrictions()
1754  ->removeAll()
1755  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1756  ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, (int)$this->‪getBackendUser()->workspace));
1757  $queryBuilder
1758  ->select(...‪$fields)
1759  ->from($table);
1760 
1761  if (!empty($additionalConstraints)) {
1762  $queryBuilder->andWhere(...$additionalConstraints);
1763  }
1764 
1765  $queryBuilder = $this->‪prepareQueryBuilder($table, $pageId, ‪$fields, $additionalConstraints, $queryBuilder);
1766 
1767  return $queryBuilder;
1768  }
1769 
1781  protected function ‪prepareQueryBuilder(
1782  string $table,
1783  int $pageId,
1784  array $fieldList,
1785  array $additionalConstraints,
1786  QueryBuilder $queryBuilder
1787  ): QueryBuilder {
1788  $parameters = [
1789  'table' => $table,
1790  'fields' => $fieldList,
1791  'groupBy' => null,
1792  'orderBy' => null,
1793  ];
1794 
1795  $sortBy = (string)(‪$GLOBALS['TCA'][$table]['ctrl']['sortby'] ?: ‪$GLOBALS['TCA'][$table]['ctrl']['default_sortby']);
1796  foreach (‪QueryHelper::parseOrderBy($sortBy) as $orderBy) {
1797  $queryBuilder->addOrderBy($orderBy[0], $orderBy[1]);
1798  }
1799 
1800  // Build the query constraints
1801  $queryBuilder->andWhere(
1802  $queryBuilder->expr()->eq(
1803  $table . '.pid',
1804  $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
1805  )
1806  );
1807 
1808  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][PageLayoutView::class]['modifyQuery'] ?? [] as $className) {
1809  $hookObject = GeneralUtility::makeInstance($className);
1810  if (method_exists($hookObject, 'modifyQuery')) {
1811  $hookObject->modifyQuery(
1812  $parameters,
1813  $table,
1814  $pageId,
1815  $additionalConstraints,
1816  $fieldList,
1817  $queryBuilder
1818  );
1819  }
1820  }
1821 
1822  return $queryBuilder;
1823  }
1824 
1831  protected function ‪renderLanguageFlag(SiteLanguage $language)
1832  {
1833  $title = htmlspecialchars($language->getTitle());
1834  if ($language->getFlagIdentifier()) {
1835  $icon = $this->iconFactory->getIcon(
1836  $language->getFlagIdentifier(),
1838  )->render();
1839  return '<span title="' . $title . '" class="t3js-flag">' . $icon . '</span>&nbsp;<span class="t3js-language-title">' . $title . '</span>';
1840  }
1841  return $title;
1842  }
1843 
1850  protected function ‪resolveSiteLanguages(int $pageId)
1851  {
1852  try {
1853  $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($pageId);
1854  } catch (SiteNotFoundException $e) {
1855  $site = new NullSite();
1856  }
1857  $this->siteLanguages = $site->getAvailableLanguages($this->‪getBackendUser(), true, $pageId);
1858  }
1859 
1863  protected function ‪getLocalizedPageTitle(): string
1864  {
1865  if (($this->tt_contentConfig['sys_language_uid'] ?? 0) > 0) {
1866  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1867  ->getQueryBuilderForTable('pages');
1868  $queryBuilder->getRestrictions()
1869  ->removeAll()
1870  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1871  ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, (int)$this->‪getBackendUser()->workspace));
1872  $localizedPage = $queryBuilder
1873  ->select('*')
1874  ->from('pages')
1875  ->where(
1876  $queryBuilder->expr()->eq(
1877  ‪$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
1878  $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
1879  ),
1880  $queryBuilder->expr()->eq(
1881  ‪$GLOBALS['TCA']['pages']['ctrl']['languageField'],
1882  $queryBuilder->createNamedParameter($this->tt_contentConfig['sys_language_uid'], \PDO::PARAM_INT)
1883  )
1884  )
1885  ->setMaxResults(1)
1886  ->executeQuery()
1887  ->fetchAssociative();
1888  BackendUtility::workspaceOL('pages', $localizedPage);
1889  return $localizedPage['title'];
1890  }
1891  return $this->pageinfo['title'];
1892  }
1893 
1899  protected function ‪isPageEditable()
1900  {
1901  if ($this->‪getBackendUser()->isAdmin()) {
1902  return true;
1903  }
1904  return !$this->pageinfo['editlock'] && $this->‪getBackendUser()->doesUserHaveAccess($this->pageinfo, ‪Permission::PAGE_EDIT);
1905  }
1906 
1913  protected function ‪isContentEditable(?int $languageId = null)
1914  {
1915  if ($this->‪getBackendUser()->isAdmin()) {
1916  return true;
1917  }
1918  return !$this->pageinfo['editlock']
1920  && ($languageId === null || $this->‪getBackendUser()->checkLanguageAccess($languageId));
1921  }
1922 
1928  protected function ‪hasContentModificationAndAccessPermissions(): bool
1929  {
1930  return $this->‪getBackendUser()->check('tables_modify', 'tt_content')
1931  && $this->‪getBackendUser()->doesUserHaveAccess($this->pageinfo, ‪Permission::CONTENT_EDIT);
1932  }
1933 
1934  protected function ‪getLanguageService(): LanguageService
1935  {
1936  return ‪$GLOBALS['LANG'];
1937  }
1938 }
‪TYPO3\CMS\Core\Imaging\Icon\SIZE_SMALL
‪const SIZE_SMALL
Definition: Icon.php:35
‪TYPO3\CMS\Core\Database\Query\QueryHelper\parseOrderBy
‪static array array[] parseOrderBy(string $input)
Definition: QueryHelper.php:44
‪TYPO3\CMS\Backend\View\PageLayoutView\getIcon
‪string getIcon($table, $row)
Definition: PageLayoutView.php:1568
‪TYPO3\CMS\Backend\View\BackendLayoutView\getSelectedBackendLayout
‪array null getSelectedBackendLayout($pageId)
Definition: BackendLayoutView.php:340
‪TYPO3\CMS\Backend\View\PageLayoutView\$iconFactory
‪IconFactory $iconFactory
Definition: PageLayoutView.php:145
‪TYPO3\CMS\Backend\View\PageLayoutView\createFromPageLayoutContext
‪static PageLayoutView createFromPageLayoutContext(PageLayoutContext $context)
Definition: PageLayoutView.php:179
‪TYPO3\CMS\Backend\View\PageLayoutView\$localizationController
‪LocalizationController $localizationController
Definition: PageLayoutView.php:155
‪TYPO3\CMS\Backend\View\PageLayoutView\getLanguageService
‪getLanguageService()
Definition: PageLayoutView.php:1918
‪TYPO3\CMS\Backend\View\PageLayoutView\$id
‪int $id
Definition: PageLayoutView.php:116
‪TYPO3\CMS\Backend\View\PageLayoutView\$tt_contentData
‪array $tt_contentData
Definition: PageLayoutView.php:95
‪TYPO3\CMS\Backend\View\PageLayoutViewDrawItemHookInterface
Definition: PageLayoutViewDrawItemHookInterface.php:23
‪TYPO3\CMS\Backend\View\PageLayoutView\$languageHasTranslationsCache
‪array $languageHasTranslationsCache
Definition: PageLayoutView.php:151
‪TYPO3\CMS\Backend\View\PageLayoutView\hasContentModificationAndAccessPermissions
‪bool hasContentModificationAndAccessPermissions()
Definition: PageLayoutView.php:1912
‪TYPO3\CMS\Backend\View\PageLayoutView\isDragAndDropAllowed
‪bool isDragAndDropAllowed(array $row)
Definition: PageLayoutView.php:1062
‪TYPO3\CMS\Backend\View\PageLayoutView\$defLangBinding
‪bool $defLangBinding
Definition: PageLayoutView.php:73
‪TYPO3\CMS\Core\Imaging\Icon
Definition: Icon.php:26
‪TYPO3\CMS\Core\Utility\GeneralUtility\fixed_lgd_cs
‪static string fixed_lgd_cs($string, $chars, $appendString='...')
Definition: GeneralUtility.php:189
‪TYPO3\CMS\Backend\View
Definition: ArrayBrowser.php:18
‪TYPO3\CMS\Core\Database\ReferenceIndex
Definition: ReferenceIndex.php:43
‪TYPO3\CMS\Backend\View\PageLayoutView\getQueryBuilder
‪TYPO3 CMS Core Database Query QueryBuilder getQueryBuilder(string $table, int $pageId, array $additionalConstraints=[], array $fields=[' *'])
Definition: PageLayoutView.php:1729
‪TYPO3\CMS\Core\Site\Entity\SiteLanguage\getTitle
‪string getTitle()
Definition: SiteLanguage.php:222
‪TYPO3\CMS\Backend\Exception
Definition: Exception.php:24
‪TYPO3\CMS\Backend\View\PageLayoutView\__construct
‪__construct()
Definition: PageLayoutView.php:167
‪TYPO3\CMS\Core\Site\Entity\NullSite
Definition: NullSite.php:32
‪TYPO3\CMS\Core\Exception\SiteNotFoundException
Definition: SiteNotFoundException.php:26
‪TYPO3\CMS\Core\Utility\GeneralUtility\_GP
‪static mixed _GP($var)
Definition: GeneralUtility.php:106
‪TYPO3\CMS\Core\Site\SiteFinder
Definition: SiteFinder.php:31
‪TYPO3\CMS\Backend\View\PageLayoutView\renderLanguageFlag
‪string renderLanguageFlag(SiteLanguage $language)
Definition: PageLayoutView.php:1815
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:34
‪TYPO3\CMS\Core\Page\JavaScriptModuleInstruction
Definition: JavaScriptModuleInstruction.php:23
‪TYPO3\CMS\Core\Versioning\VersionState\DELETE_PLACEHOLDER
‪const DELETE_PLACEHOLDER
Definition: VersionState.php:49
‪TYPO3\CMS\Core\Page\JavaScriptModuleInstruction\create
‪static self create(string $name, string $exportName=null)
Definition: JavaScriptModuleInstruction.php:53
‪$fields
‪$fields
Definition: pages.php:5
‪TYPO3\CMS\Backend\View\PageLayoutView\$itemLabels
‪array $itemLabels
Definition: PageLayoutView.php:110
‪TYPO3\CMS\Backend\View\PageLayoutView\prepareQueryBuilder
‪QueryBuilder prepareQueryBuilder(string $table, int $pageId, array $fieldList, array $additionalConstraints, QueryBuilder $queryBuilder)
Definition: PageLayoutView.php:1765
‪TYPO3\CMS\Core\Type\Bitmask\Permission
Definition: Permission.php:26
‪TYPO3\CMS\Backend\View\PageLayoutView\renderContentElementHeader
‪renderContentElementHeader(array $row)
Definition: PageLayoutView.php:1119
‪TYPO3\CMS\Backend\View\PageLayoutView\getReferenceCount
‪int getReferenceCount(string $tableName, int $uid)
Definition: PageLayoutView.php:1046
‪TYPO3\CMS\Core\Service\FlexFormService
Definition: FlexFormService.php:25
‪TYPO3\CMS\Backend\View\PageLayoutView\resolveSiteLanguages
‪resolveSiteLanguages(int $pageId)
Definition: PageLayoutView.php:1834
‪TYPO3\CMS\Core\Type\ContextualFeedbackSeverity
‪ContextualFeedbackSeverity
Definition: ContextualFeedbackSeverity.php:25
‪TYPO3\CMS\Core\Site\Entity\SiteLanguage
Definition: SiteLanguage.php:26
‪TYPO3\CMS\Backend\View\PageLayoutContext\getItemLabels
‪getItemLabels()
Definition: PageLayoutContext.php:245
‪TYPO3\CMS\Backend\View\PageLayoutView\$uriBuilder
‪UriBuilder $uriBuilder
Definition: PageLayoutView.php:165
‪TYPO3\CMS\Core\Type\Enumeration\cast
‪static static cast($value)
Definition: Enumeration.php:186
‪TYPO3\CMS\Backend\View\PageLayoutView\languageSelector
‪string languageSelector($id)
Definition: PageLayoutView.php:1432
‪TYPO3\CMS\Core\Core\Environment\getProjectPath
‪static string getProjectPath()
Definition: Environment.php:175
‪TYPO3\CMS\Backend\View\PageLayoutView\isContentEditable
‪bool isContentEditable(?int $languageId=null)
Definition: PageLayoutView.php:1897
‪TYPO3\CMS\Core\Database\Query\QueryHelper
Definition: QueryHelper.php:32
‪TYPO3\CMS\Core\Site\Entity\SiteLanguage\getFlagIdentifier
‪string getFlagIdentifier()
Definition: SiteLanguage.php:246
‪TYPO3\CMS\Backend\View\PageLayoutContext\getContentTypeLabels
‪getContentTypeLabels()
Definition: PageLayoutContext.php:235
‪TYPO3\CMS\Backend\View\PageLayoutView\tt_content_drawFooter
‪string tt_content_drawFooter(array $row)
Definition: PageLayoutView.php:854
‪TYPO3\CMS\Backend\View\PageLayoutView\getBackendLayoutView
‪BackendLayoutView getBackendLayoutView()
Definition: PageLayoutView.php:1697
‪TYPO3\CMS\Backend\View\PageLayoutView\$CType_labels
‪array $CType_labels
Definition: PageLayoutView.php:104
‪TYPO3\CMS\Backend\View\PageLayoutView\tt_content_drawHeader
‪string tt_content_drawHeader($row, $space=0, $disableMoveAndNewButtons=false, $langMode=false, $dragDropEnabled=false)
Definition: PageLayoutView.php:895
‪TYPO3\CMS\Backend\Routing\UriBuilder
Definition: UriBuilder.php:41
‪TYPO3\CMS\Backend\View\PageLayoutView\isPageEditable
‪bool isPageEditable()
Definition: PageLayoutView.php:1883
‪TYPO3\CMS\Backend\View\PageLayoutContext\getPageRecord
‪getPageRecord()
Definition: PageLayoutContext.php:158
‪TYPO3\CMS\Backend\View\PageLayoutView\initialize
‪initialize()
Definition: PageLayoutView.php:203
‪TYPO3\CMS\Backend\Routing\PreviewUriBuilder\create
‪static static create(int $pageId)
Definition: PreviewUriBuilder.php:64
‪TYPO3\CMS\Backend\Controller\Page\LocalizationController
Definition: LocalizationController.php:42
‪TYPO3\CMS\Backend\View\PageLayoutView\generateLanguageView
‪string generateLanguageView(array $languageIds, array $defaultLanguageElementsByColumn, array $languageColumn, array $defLangBinding)
Definition: PageLayoutView.php:585
‪TYPO3\CMS\Backend\View\PageLayoutView\getTable_tt_content
‪string getTable_tt_content($id)
Definition: PageLayoutView.php:260
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:63
‪TYPO3\CMS\Backend\View\PageLayoutView\generateTtContentDataArray
‪generateTtContentDataArray(array $rowArray)
Definition: PageLayoutView.php:1527
‪TYPO3\CMS\Backend\View\PageLayoutView\getSelectedLanguages
‪int[] getSelectedLanguages()
Definition: PageLayoutView.php:241
‪TYPO3\CMS\Backend\View\PageLayoutView\renderContentElementPreviewFromFluidTemplate
‪renderContentElementPreviewFromFluidTemplate(array $row)
Definition: PageLayoutView.php:1138
‪TYPO3\CMS\Backend\View\PageLayoutView\renderText
‪string renderText($input)
Definition: PageLayoutView.php:1554
‪TYPO3\CMS\Backend\View\PageLayoutView\getNonTranslatedTTcontentUids
‪array getNonTranslatedTTcontentUids($defaultLanguageUids, $id, $lP)
Definition: PageLayoutView.php:1294
‪TYPO3\CMS\Core\Versioning\VersionState
Definition: VersionState.php:24
‪$output
‪$output
Definition: annotationChecker.php:119
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:34
‪TYPO3\CMS\Backend\View\PageLayoutView\newLanguageButton
‪string newLanguageButton($defaultLanguageUids, $lP)
Definition: PageLayoutView.php:1340
‪TYPO3\CMS\Backend\View\PageLayoutView\getLocalizedPageTitle
‪string getLocalizedPageTitle()
Definition: PageLayoutView.php:1847
‪TYPO3\CMS\Core\Messaging\FlashMessage
Definition: FlashMessage.php:27
‪TYPO3\CMS\Fluid\View\StandaloneView
Definition: StandaloneView.php:33
‪TYPO3\CMS\Backend\View\PageLayoutView\getResult
‪array getResult($result)
Definition: PageLayoutView.php:1500
‪TYPO3\CMS\Core\Type\Bitmask\Permission\CONTENT_EDIT
‪const CONTENT_EDIT
Definition: Permission.php:55
‪TYPO3\CMS\Backend\View\PageLayoutView\$pageRecord
‪string[] $pageRecord
Definition: PageLayoutView.php:122
‪TYPO3\CMS\Backend\View\PageLayoutView\getProcessedValue
‪getProcessedValue($table, $fieldList, array $row, array &$info)
Definition: PageLayoutView.php:1589
‪TYPO3\CMS\Backend\View\BackendLayoutView
Definition: BackendLayoutView.php:36
‪TYPO3\CMS\Backend\View\PageLayoutView\renderContentElementPreview
‪string renderContentElementPreview(array $row)
Definition: PageLayoutView.php:1190
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Backend\View\PageLayoutContext
Definition: PageLayoutContext.php:42
‪TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction
Definition: DeletedRestriction.php:28
‪TYPO3\CMS\Backend\View\PageLayoutContext\getPageId
‪getPageId()
Definition: PageLayoutContext.php:163
‪TYPO3\CMS\Backend\View\PageLayoutView
Definition: PageLayoutView.php:59
‪TYPO3\CMS\Backend\Routing\PreviewUriBuilder
Definition: PreviewUriBuilder.php:44
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:41
‪TYPO3\CMS\Backend\View\PageLayoutView\linkEditContent
‪string linkEditContent($str, $row)
Definition: PageLayoutView.php:1403
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_EDIT
‪const PAGE_EDIT
Definition: Permission.php:40
‪TYPO3\CMS\Backend\View\PageLayoutView\$siteLanguages
‪SiteLanguage[] $siteLanguages
Definition: PageLayoutView.php:128
‪TYPO3\CMS\Backend\View\PageLayoutView\$tt_contentConfig
‪array $tt_contentConfig
Definition: PageLayoutView.php:79
‪TYPO3\CMS\Core\Utility\GeneralUtility\intExplode
‪static int[] intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:848
‪TYPO3\CMS\Backend\View\PageLayoutView\getThumbCodeUnlinked
‪string getThumbCodeUnlinked($row, $table, $field)
Definition: PageLayoutView.php:1631
‪TYPO3\CMS\Backend\View\PageLayoutView\getBackendUser
‪getBackendUser()
Definition: PageLayoutView.php:1702
‪TYPO3\CMS\Core\Utility\GeneralUtility\inList
‪static bool inList($list, $item)
Definition: GeneralUtility.php:522
‪TYPO3\CMS\Backend\View\PageLayoutView\$referenceCount
‪array $referenceCount
Definition: PageLayoutView.php:161
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:39
‪TYPO3\CMS\Backend\View\PageLayoutView\checkIfTranslationsExistInLanguage
‪bool checkIfTranslationsExistInLanguage(array $contentElements, int $language)
Definition: PageLayoutView.php:1644
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:45
‪TYPO3\CMS\Backend\View\PageLayoutContext\getDrawingConfiguration
‪getDrawingConfiguration()
Definition: PageLayoutContext.php:148
‪TYPO3\CMS\Backend\View\PageLayoutView\thumbCode
‪string thumbCode($row, $table, $field)
Definition: PageLayoutView.php:1715
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:51
‪TYPO3\CMS\Core\Utility\StringUtility
Definition: StringUtility.php:24
‪TYPO3\CMS\Backend\View\PageLayoutView\tt_content_drawItem
‪string tt_content_drawItem($row)
Definition: PageLayoutView.php:1084
‪TYPO3\CMS\Backend\View\PageLayoutView\$contentElementCache
‪array $contentElementCache
Definition: PageLayoutView.php:141
‪TYPO3\CMS\Backend\View\PageLayoutView\$doEdit
‪bool $doEdit
Definition: PageLayoutView.php:66
‪TYPO3\CMS\Core\Messaging\FlashMessageService
Definition: FlashMessageService.php:27
‪TYPO3\CMS\Backend\View\PageLayoutView\getContentRecordsPerColumn
‪array getContentRecordsPerColumn($table, $id, array $columns, $additionalWhereClause='')
Definition: PageLayoutView.php:787
‪TYPO3\CMS\Core\Utility\StringUtility\getUniqueId
‪static getUniqueId(string $prefix='')
Definition: StringUtility.php:29
‪TYPO3\CMS\Backend\View\PageLayoutView\$pageinfo
‪array $pageinfo
Definition: PageLayoutView.php:134
‪TYPO3\CMS\Backend\View\PageLayoutView\tt_content_drawColHeader
‪string tt_content_drawColHeader($colName, $editParams='')
Definition: PageLayoutView.php:830
‪TYPO3\CMS\Backend\View\PageLayoutView\isDisabled
‪bool isDisabled($table, $row)
Definition: PageLayoutView.php:1609
‪TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction
Definition: WorkspaceRestriction.php:40