TYPO3 CMS  TYPO3_8-7
PageLayoutView.php
Go to the documentation of this file.
1 <?php
2 
3 namespace TYPO3\CMS\Backend\View;
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 
40 
44 class PageLayoutView extends \TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRecordList
45 {
51  public $pI_showUser = 0;
52 
58  public $nextThree = 3;
59 
66 
72  public $option_newWizard = 1;
73 
79  public $ext_function = 0;
80 
86  public $doEdit = 1;
87 
93  public $agePrefixes = ' min| hrs| days| yrs| min| hour| day| year';
94 
100  public $externalTables = [];
101 
107  public $descrTable;
108 
115  public $defLangBinding = false;
116 
122  public $tt_contentConfig = [
123  // Boolean: Display info-marks or not
124  'showInfo' => 1,
125  // Boolean: Display up/down arrows and edit icons for tt_content records
126  'showCommands' => 1,
127  'languageCols' => 0,
128  'languageMode' => 0,
129  'languageColsPointer' => 0,
130  'showHidden' => 1,
131  // Displays hidden records as well
132  'sys_language_uid' => 0,
133  // Which language
134  'cols' => '1,0,2,3',
135  'activeCols' => '1,0,2,3'
136  // Which columns can be accessed by current BE user
137  ];
138 
144  public $activeTables = [];
145 
149  public $tt_contentData = [
150  'nextThree' => [],
151  'prev' => [],
152  'next' => []
153  ];
154 
160  public $CType_labels = [];
161 
167  public $itemLabels = [];
168 
172  protected $clipboard;
173 
180 
186  protected $pageinfo;
187 
193  protected $languagesInColumnCache = [];
194 
201  protected $contentElementCache = [];
202 
206  protected $iconFactory;
207 
214 
219 
223  public function __construct()
224  {
225  parent::__construct();
226  $this->localizationController = GeneralUtility::makeInstance(LocalizationController::class);
227  $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
228  $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
229  $pageRenderer->addInlineLanguageLabelFile('EXT:backend/Resources/Private/Language/locallang_layout.xlf');
230  $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Tooltip');
231  $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Localization');
232  }
233 
234  /*****************************************
235  *
236  * Renderings
237  *
238  *****************************************/
247  public function getTable($table, $id, $fields = '')
248  {
249  if (isset($this->externalTables[$table])) {
250  return $this->getExternalTables($id, $table);
251  }
252  // Branch out based on table name:
253  switch ($table) {
254  case 'pages':
255  return $this->getTable_pages($id);
256  break;
257  case 'tt_content':
258  return $this->getTable_tt_content($id);
259  break;
260  default:
261  return '';
262  }
263  }
264 
272  public function getExternalTables($id, $table)
273  {
274  $this->pageinfo = BackendUtility::readPageAccess($id, '');
275  $type = $this->getPageLayoutController()->MOD_SETTINGS[$table];
276  if (!isset($type)) {
277  $type = 0;
278  }
279  // eg. "name;title;email;company,image"
280  $fList = $this->externalTables[$table][$type]['fList'];
281  // The columns are separeted by comma ','.
282  // Values separated by semicolon ';' are shown in the same column.
283  $icon = $this->externalTables[$table][$type]['icon'];
284  $addWhere = $this->externalTables[$table][$type]['addWhere'];
285  // Create listing
286  $out = $this->makeOrdinaryList($table, $id, $fList, $icon, $addWhere);
287  return $out;
288  }
289 
297  public function getTable_pages($id)
298  {
299  // Initializing:
300  $out = '';
301  $lang = $this->getLanguageService();
302  // Select current page:
303  if (!$id) {
304  // The root has a pseudo record in pageinfo...
305  $row = $this->getPageLayoutController()->pageinfo;
306  } else {
307  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
308  ->getQueryBuilderForTable('pages');
309  $queryBuilder->getRestrictions()
310  ->removeAll()
311  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
312  $row = $queryBuilder
313  ->select('*')
314  ->from('pages')
315  ->where(
316  $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)),
317  $this->getBackendUser()->getPagePermsClause(1)
318  )
319  ->execute()
320  ->fetch();
321  BackendUtility::workspaceOL('pages', $row);
322  }
323  // If there was found a page:
324  if (is_array($row)) {
325  // Getting select-depth:
326  $depth = (int)$this->getPageLayoutController()->MOD_SETTINGS['pages_levels'];
327  // Overriding a few things:
328  $this->no_noWrap = 0;
329  // Items
330  $this->eCounter = $this->firstElementNumber;
331  // Creating elements:
332  list($flag, $code) = $this->fwd_rwd_nav();
333  $out .= $code;
334  $editUids = [];
335  if ($flag) {
336  // Getting children:
337  $theRows = $this->getPageRecordsRecursive($row['uid'], $depth);
338  if ($this->getBackendUser()->doesUserHaveAccess($row, 2) && $row['uid'] > 0) {
339  $editUids[] = $row['uid'];
340  }
341  $out .= $this->pages_drawItem($row, $this->fieldArray);
342  // Traverse all pages selected:
343  foreach ($theRows as $sRow) {
344  if ($this->getBackendUser()->doesUserHaveAccess($sRow, 2)) {
345  $editUids[] = $sRow['uid'];
346  }
347  $out .= $this->pages_drawItem($sRow, $this->fieldArray);
348  }
349  $this->eCounter++;
350  }
351  // Header line is drawn
352  $theData = [];
353  $editIdList = implode(',', $editUids);
354  // Traverse fields (as set above) in order to create header values:
355  foreach ($this->fieldArray as $field) {
356  if ($editIdList
357  && isset($GLOBALS['TCA']['pages']['columns'][$field])
358  && $field !== 'uid'
359  && !$this->pages_noEditColumns
360  ) {
361  $iTitle = sprintf(
362  $lang->getLL('editThisColumn'),
363  rtrim(trim($lang->sL(BackendUtility::getItemLabel('pages', $field))), ':')
364  );
365  $urlParameters = [
366  'edit' => [
367  'pages' => [
368  $editIdList => 'edit'
369  ]
370  ],
371  'columnsOnly' => $field,
372  'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
373  ];
374  $url = BackendUtility::getModuleUrl('record_edit', $urlParameters);
375  $eI = '<a class="btn btn-default" href="' . htmlspecialchars($url)
376  . '" title="' . htmlspecialchars($iTitle) . '">'
377  . $this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)->render() . '</a>';
378  } else {
379  $eI = '';
380  }
381  switch ($field) {
382  case 'title':
383  $theData[$field] = '&nbsp;' . $eI . '<strong>'
384  . $lang->sL($GLOBALS['TCA']['pages']['columns'][$field]['label'])
385  . '</strong>';
386  break;
387  case 'uid':
388  $theData[$field] = '&nbsp;<strong>ID:</strong>';
389  break;
390  default:
391  if (substr($field, 0, 6) === 'table_') {
392  $f2 = substr($field, 6);
393  if ($GLOBALS['TCA'][$f2]) {
394  $theData[$field] = '&nbsp;' .
395  '<span title="' .
396  htmlspecialchars($lang->sL($GLOBALS['TCA'][$f2]['ctrl']['title'])) .
397  '">' .
398  $this->iconFactory->getIconForRecord($f2, [], Icon::SIZE_SMALL)->render() .
399  '</span>';
400  }
401  } else {
402  $theData[$field] = '&nbsp;&nbsp;' . $eI . '<strong>'
403  . htmlspecialchars($lang->sL($GLOBALS['TCA']['pages']['columns'][$field]['label']))
404  . '</strong>';
405  }
406  }
407  }
408  $out = '<div class="table-fit">'
409  . '<table class="table table-striped table-hover typo3-page-pages">'
410  . '<thead>'
411  . $this->addElement(1, '', $theData)
412  . '</thead>'
413  . '<tbody>'
414  . $out
415  . '</tbody>'
416  . '</table>'
417  . '</div>';
418  }
419  return $out;
420  }
421 
428  public function getTable_tt_content($id)
429  {
430  $backendUser = $this->getBackendUser();
431  $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
432  ->getConnectionForTable('tt_content')
433  ->getExpressionBuilder();
434  $this->pageinfo = BackendUtility::readPageAccess($this->id, '');
435  $this->initializeLanguages();
436  $this->initializeClipboard();
437  $pageTitleParamForAltDoc = '&recTitle=' . rawurlencode(BackendUtility::getRecordTitle('pages', BackendUtility::getRecordWSOL('pages', $id), true));
439  $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
440  $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/LayoutModule/DragDrop');
441  $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Modal');
442  $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/LayoutModule/Paste');
443  $userCanEditPage = $this->ext_CALC_PERMS & Permission::PAGE_EDIT && !empty($this->id) && ($backendUser->isAdmin() || (int)$this->pageinfo['editlock'] === 0);
444  if ($this->tt_contentConfig['languageColsPointer'] > 0) {
445  $userCanEditPage = $this->getBackendUser()->check('tables_modify', 'pages_language_overlay');
446  }
447  if ($userCanEditPage) {
448  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
449  ->getQueryBuilderForTable('pages_language_overlay');
450  $queryBuilder->getRestrictions()
451  ->removeAll()
452  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
453  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
454 
455  $queryBuilder->select('uid')
456  ->from('pages_language_overlay')
457  ->where(
458  $queryBuilder->expr()->eq(
459  'pid',
460  $queryBuilder->createNamedParameter((int)$this->id, \PDO::PARAM_INT)
461  ),
462  $queryBuilder->expr()->eq(
463  'sys_language_uid',
464  $queryBuilder->createNamedParameter(
465  $this->tt_contentConfig['sys_language_uid'],
466  \PDO::PARAM_INT
467  )
468  )
469  )
470  ->setMaxResults(1);
471 
472  $languageOverlayId = (int)$queryBuilder->execute()->fetchColumn(0);
473 
474  $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/PageActions', 'function(PageActions) {
475  PageActions.setPageId(' . (int)$this->id . ');
476  PageActions.setLanguageOverlayId(' . $languageOverlayId . ');
477  PageActions.initializePageTitleRenaming();
478  }');
479  }
480  // Get labels for CTypes and tt_content element fields in general:
481  $this->CType_labels = [];
482  foreach ($GLOBALS['TCA']['tt_content']['columns']['CType']['config']['items'] as $val) {
483  $this->CType_labels[$val[1]] = $this->getLanguageService()->sL($val[0]);
484  }
485 
486  $this->itemLabels = [];
487  foreach ($GLOBALS['TCA']['tt_content']['columns'] as $name => $val) {
488  $this->itemLabels[$name] = $this->getLanguageService()->sL($val['label']);
489  }
490  $languageColumn = [];
491  $out = '';
492 
493  // Setting language list:
494  $langList = $this->tt_contentConfig['sys_language_uid'];
495  if ($this->tt_contentConfig['languageMode']) {
496  if ($this->tt_contentConfig['languageColsPointer']) {
497  $langList = '0,' . $this->tt_contentConfig['languageColsPointer'];
498  } else {
499  $langList = implode(',', array_keys($this->tt_contentConfig['languageCols']));
500  }
501  $languageColumn = [];
502  }
503  $langListArr = GeneralUtility::intExplode(',', $langList);
504  $defaultLanguageElementsByColumn = [];
505  $defLangBinding = [];
506  // For each languages... :
507  // If not languageMode, then we'll only be through this once.
508  foreach ($langListArr as $lP) {
509  $lP = (int)$lP;
510 
511  if (!isset($this->contentElementCache[$lP])) {
512  $this->contentElementCache[$lP] = [];
513  }
514 
515  if (count($langListArr) === 1 || $lP === 0) {
516  $showLanguage = $expressionBuilder->in('sys_language_uid', [$lP, -1]);
517  } else {
518  $showLanguage = $expressionBuilder->eq('sys_language_uid', $lP);
519  }
520  $cList = explode(',', $this->tt_contentConfig['cols']);
521  $content = [];
522  $head = [];
523 
524  // Select content records per column
525  $contentRecordsPerColumn = $this->getContentRecordsPerColumn('table', $id, array_values($cList), $showLanguage);
526  // For each column, render the content into a variable:
527  foreach ($cList as $columnId) {
528  if (!isset($this->contentElementCache[$lP][$columnId])) {
529  $this->contentElementCache[$lP][$columnId] = [];
530  }
531 
532  if (!$lP) {
533  $defaultLanguageElementsByColumn[$columnId] = [];
534  }
535  // Start wrapping div
536  $content[$columnId] .= '<div data-colpos="' . $columnId . '" data-language-uid="' . $lP . '" class="t3js-sortable t3js-sortable-lang t3js-sortable-lang-' . $lP . ' t3-page-ce-wrapper';
537  if (empty($contentRecordsPerColumn[$columnId])) {
538  $content[$columnId] .= ' t3-page-ce-empty';
539  }
540  $content[$columnId] .= '">';
541  // Add new content at the top most position
542  $link = '';
543  if ($this->getPageLayoutController()->contentIsNotLockedForEditors()
544  && (!$this->checkIfTranslationsExistInLanguage($contentRecordsPerColumn, $lP))
545  ) {
546  if ($this->option_newWizard) {
547  $urlParameters = [
548  'id' => $id,
549  'sys_language_uid' => $lP,
550  'colPos' => $columnId,
551  'uid_pid' => $id,
552  'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
553  ];
554  $tsConfig = BackendUtility::getModTSconfig($id, 'mod');
555  $moduleName = isset($tsConfig['properties']['newContentElementWizard.']['override'])
556  ? $tsConfig['properties']['newContentElementWizard.']['override']
557  : 'new_content_element';
558  $url = BackendUtility::getModuleUrl($moduleName, $urlParameters);
559  } else {
560  $urlParameters = [
561  'edit' => [
562  'tt_content' => [
563  $id => 'new'
564  ]
565  ],
566  'defVals' => [
567  'tt_content' => [
568  'colPos' => $columnId,
569  'sys_language_uid' => $lP
570  ]
571  ],
572  'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
573  ];
574  $url = BackendUtility::getModuleUrl('record_edit', $urlParameters);
575  }
576 
577  $link = '<a href="' . htmlspecialchars($url) . '" title="'
578  . htmlspecialchars($this->getLanguageService()->getLL('newContentElement')) . '" class="btn btn-default btn-sm">'
579  . $this->iconFactory->getIcon('actions-document-new', Icon::SIZE_SMALL)->render()
580  . ' '
581  . htmlspecialchars($this->getLanguageService()->getLL('content')) . '</a>';
582  }
583  if ($this->getBackendUser()->checkLanguageAccess($lP)) {
584  $content[$columnId] .= '
585  <div class="t3-page-ce t3js-page-ce" data-page="' . (int)$id . '" id="' . StringUtility::getUniqueId() . '">
586  <div class="t3js-page-new-ce t3-page-ce-wrapper-new-ce" id="colpos-' . $columnId . '-' . 'page-' . $id . '-' . StringUtility::getUniqueId() . '">'
587  . $link
588  . '</div>
589  <div class="t3-page-ce-dropzone-available t3js-page-ce-dropzone-available"></div>
590  </div>
591  ';
592  }
593  $editUidList = '';
594  if (!isset($contentRecordsPerColumn[$columnId]) || !is_array($contentRecordsPerColumn[$columnId])) {
595  $message = GeneralUtility::makeInstance(
596  FlashMessage::class,
597  $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:error.invalidBackendLayout'),
598  '',
600  );
601  $service = GeneralUtility::makeInstance(FlashMessageService::class);
602  $queue = $service->getMessageQueueByIdentifier();
603  $queue->addMessage($message);
604  } else {
605  $rowArr = $contentRecordsPerColumn[$columnId];
606  $this->generateTtContentDataArray($rowArr);
607 
608  foreach ((array)$rowArr as $rKey => $row) {
609  $this->contentElementCache[$lP][$columnId][$row['uid']] = $row;
610  if ($this->tt_contentConfig['languageMode']) {
611  $languageColumn[$columnId][$lP] = $head[$columnId] . $content[$columnId];
612  if (!$this->defLangBinding) {
613  $languageColumn[$columnId][$lP] .= $this->newLanguageButton(
614  $this->getNonTranslatedTTcontentUids($defaultLanguageElementsByColumn[$columnId], $id, $lP),
615  $lP,
616  $columnId
617  );
618  }
619  }
620  if (is_array($row) && !VersionState::cast($row['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) {
621  $singleElementHTML = '';
622  if (!$lP && ($this->defLangBinding || $row['sys_language_uid'] != -1)) {
623  $defaultLanguageElementsByColumn[$columnId][] = (isset($row['_ORIG_uid']) ? $row['_ORIG_uid'] : $row['uid']);
624  }
625  $editUidList .= $row['uid'] . ',';
626  $disableMoveAndNewButtons = $this->defLangBinding && $lP > 0 && $this->checkIfTranslationsExistInLanguage($contentRecordsPerColumn, $lP);
627  if (!$this->tt_contentConfig['languageMode']) {
628  $singleElementHTML .= '<div class="t3-page-ce-dragitem" id="' . StringUtility::getUniqueId() . '">';
629  }
630  $singleElementHTML .= $this->tt_content_drawHeader(
631  $row,
632  $this->tt_contentConfig['showInfo'] ? 15 : 5,
633  $disableMoveAndNewButtons,
634  true,
635  $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT)
636  );
637  $innerContent = '<div ' . ($row['_ORIG_uid'] ? ' class="ver-element"' : '') . '>'
638  . $this->tt_content_drawItem($row) . '</div>';
639  $singleElementHTML .= '<div class="t3-page-ce-body-inner">' . $innerContent . '</div>'
640  . $this->tt_content_drawFooter($row);
641  $isDisabled = $this->isDisabled('tt_content', $row);
642  $statusHidden = $isDisabled ? ' t3-page-ce-hidden t3js-hidden-record' : '';
643  $displayNone = !$this->tt_contentConfig['showHidden'] && $isDisabled ? ' style="display: none;"' : '';
644  $highlightHeader = false;
645  if ($this->checkIfTranslationsExistInLanguage([], (int)$row['sys_language_uid']) && (int)$row['l18n_parent'] === 0) {
646  $highlightHeader = true;
647  }
648  $singleElementHTML = '<div class="t3-page-ce ' . ($highlightHeader ? 't3-page-ce-danger' : '') . ' t3js-page-ce t3js-page-ce-sortable ' . $statusHidden . '" id="element-tt_content-'
649  . $row['uid'] . '" data-table="tt_content" data-uid="' . $row['uid'] . '"' . $displayNone . '>' . $singleElementHTML . '</div>';
650 
651  if ($this->tt_contentConfig['languageMode']) {
652  $singleElementHTML .= '<div class="t3-page-ce" data-colpos="' . $columnId . '">';
653  }
654  $singleElementHTML .= '<div class="t3js-page-new-ce t3-page-ce-wrapper-new-ce" id="colpos-' . $columnId . '-' . 'page-' . $id .
655  '-' . StringUtility::getUniqueId() . '">';
656  // Add icon "new content element below"
657  if (!$disableMoveAndNewButtons
658  && $this->getPageLayoutController()->contentIsNotLockedForEditors()
659  && $this->getBackendUser()->checkLanguageAccess($lP)
660  && (!$this->checkIfTranslationsExistInLanguage($contentRecordsPerColumn, $lP))
661  ) {
662  // New content element:
663  if ($this->option_newWizard) {
664  $urlParameters = [
665  'id' => $row['pid'],
666  'sys_language_uid' => $row['sys_language_uid'],
667  'colPos' => $row['colPos'],
668  'uid_pid' => -$row['uid'],
669  'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
670  ];
671  $tsConfig = BackendUtility::getModTSconfig($row['pid'], 'mod');
672  $moduleName = isset($tsConfig['properties']['newContentElementWizard.']['override'])
673  ? $tsConfig['properties']['newContentElementWizard.']['override']
674  : 'new_content_element';
675  $url = BackendUtility::getModuleUrl($moduleName, $urlParameters);
676  } else {
677  $urlParameters = [
678  'edit' => [
679  'tt_content' => [
680  -$row['uid'] => 'new'
681  ]
682  ],
683  'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
684  ];
685  $url = BackendUtility::getModuleUrl('record_edit', $urlParameters);
686  }
687  $singleElementHTML .= '
688  <a href="' . htmlspecialchars($url) . '" title="'
689  . htmlspecialchars($this->getLanguageService()->getLL('newContentElement')) . '" class="btn btn-default btn-sm">'
690  . $this->iconFactory->getIcon('actions-document-new', Icon::SIZE_SMALL)->render()
691  . ' '
692  . htmlspecialchars($this->getLanguageService()->getLL('content')) . '</a>
693  ';
694  }
695  $singleElementHTML .= '</div></div><div class="t3-page-ce-dropzone-available t3js-page-ce-dropzone-available"></div></div>';
696  if ($this->defLangBinding && $this->tt_contentConfig['languageMode']) {
697  $defLangBinding[$columnId][$lP][$row[$lP ? 'l18n_parent' : 'uid'] ?: $row['uid']] = $singleElementHTML;
698  } else {
699  $content[$columnId] .= $singleElementHTML;
700  }
701  } else {
702  unset($rowArr[$rKey]);
703  }
704  }
705  $content[$columnId] .= '</div>';
706  $colTitle = BackendUtility::getProcessedValue('tt_content', 'colPos', $columnId);
707  $tcaItems = GeneralUtility::callUserFunction(\TYPO3\CMS\Backend\View\BackendLayoutView::class . '->getColPosListItemsParsed', $id, $this);
708  foreach ($tcaItems as $item) {
709  if ($item[1] == $columnId) {
710  $colTitle = $this->getLanguageService()->sL($item[0]);
711  }
712  }
713  $editParam = $this->doEdit && !empty($rowArr)
714  ? '&edit[tt_content][' . $editUidList . ']=edit' . $pageTitleParamForAltDoc
715  : '';
716  $head[$columnId] .= $this->tt_content_drawColHeader($colTitle, $editParam);
717  }
718  }
719  // For each column, fit the rendered content into a table cell:
720  $out = '';
721  if ($this->tt_contentConfig['languageMode']) {
722  // in language mode process the content elements, but only fill $languageColumn. output will be generated later
723  $sortedLanguageColumn = [];
724  foreach ($cList as $columnId) {
725  if (GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnId)) {
726  $languageColumn[$columnId][$lP] = $head[$columnId] . $content[$columnId];
727  if (!$this->defLangBinding) {
728  $languageColumn[$columnId][$lP] .= $this->newLanguageButton(
729  $this->getNonTranslatedTTcontentUids($defaultLanguageElementsByColumn[$columnId], $id, $lP),
730  $lP,
731  $columnId
732  );
733  }
734  // We sort $languageColumn again according to $cList as it may contain data already from above.
735  $sortedLanguageColumn[$columnId] = $languageColumn[$columnId];
736  }
737  }
738  $languageColumn = $sortedLanguageColumn;
739  } else {
740  $backendLayout = $this->getBackendLayoutView()->getSelectedBackendLayout($this->id);
741  // GRID VIEW:
742  $grid = '<div class="t3-grid-container"><table border="0" cellspacing="0" cellpadding="0" width="100%" class="t3-page-columns t3-grid-table t3js-page-columns">';
743  // Add colgroups
744  $colCount = (int)$backendLayout['__config']['backend_layout.']['colCount'];
745  $rowCount = (int)$backendLayout['__config']['backend_layout.']['rowCount'];
746  $grid .= '<colgroup>';
747  for ($i = 0; $i < $colCount; $i++) {
748  $grid .= '<col />';
749  }
750  $grid .= '</colgroup>';
751  // Cycle through rows
752  for ($row = 1; $row <= $rowCount; $row++) {
753  $rowConfig = $backendLayout['__config']['backend_layout.']['rows.'][$row . '.'];
754  if (!isset($rowConfig)) {
755  continue;
756  }
757  $grid .= '<tr>';
758  for ($col = 1; $col <= $colCount; $col++) {
759  $columnConfig = $rowConfig['columns.'][$col . '.'];
760  if (!isset($columnConfig)) {
761  continue;
762  }
763  // Which tt_content colPos should be displayed inside this cell
764  $columnKey = (int)$columnConfig['colPos'];
765  // Render the grid cell
766  $colSpan = (int)$columnConfig['colspan'];
767  $rowSpan = (int)$columnConfig['rowspan'];
768  $grid .= '<td valign="top"' .
769  ($colSpan > 0 ? ' colspan="' . $colSpan . '"' : '') .
770  ($rowSpan > 0 ? ' rowspan="' . $rowSpan . '"' : '') .
771  ' 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 .
772  ((!isset($columnConfig['colPos']) || $columnConfig['colPos'] === '') ? ' t3-grid-cell-unassigned' : '') .
773  ((isset($columnConfig['colPos']) && $columnConfig['colPos'] !== '' && !$head[$columnKey]) || !GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos']) ? ' t3-grid-cell-restricted' : '') .
774  ($colSpan > 0 ? ' t3-gridCell-width' . $colSpan : '') .
775  ($rowSpan > 0 ? ' t3-gridCell-height' . $rowSpan : '') . '">';
776 
777  // Draw the pre-generated header with edit and new buttons if a colPos is assigned.
778  // If not, a new header without any buttons will be generated.
779  if (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== '' && $head[$columnKey]
780  && GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos'])
781  ) {
782  $grid .= $head[$columnKey] . $content[$columnKey];
783  } elseif (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== ''
784  && GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos'])
785  ) {
786  $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->getLL('noAccess'));
787  } elseif (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== ''
788  && !GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos'])
789  ) {
790  $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->sL($columnConfig['name']) .
791  ' (' . $this->getLanguageService()->getLL('noAccess') . ')');
792  } elseif (isset($columnConfig['name']) && $columnConfig['name'] !== '') {
793  $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->sL($columnConfig['name'])
794  . ' (' . $this->getLanguageService()->getLL('notAssigned') . ')');
795  } else {
796  $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->getLL('notAssigned'));
797  }
798 
799  $grid .= '</td>';
800  }
801  $grid .= '</tr>';
802  }
803  $out .= $grid . '</table></div>';
804  }
805  // CSH:
806  $out .= BackendUtility::cshItem($this->descrTable, 'columns_multi', null, '<span class="btn btn-default btn-sm">|</span>');
807  }
808  $elFromTable = $this->clipboard->elFromTable('tt_content');
809  if (!empty($elFromTable) && $this->getPageLayoutController()->pageIsNotLockedForEditors()) {
810  $pasteItem = substr(key($elFromTable), 11);
811  $pasteRecord = BackendUtility::getRecord('tt_content', (int)$pasteItem);
812  $pasteTitle = $pasteRecord['header'] ? $pasteRecord['header'] : $pasteItem;
813  $copyMode = $this->clipboard->clipData['normal']['mode'] ? '-' . $this->clipboard->clipData['normal']['mode'] : '';
814  $addExtOnReadyCode = '
815  top.pasteIntoLinkTemplate = '
816  . $this->tt_content_drawPasteIcon($pasteItem, $pasteTitle, $copyMode, 't3js-paste-into', 'pasteIntoColumn')
817  . ';
818  top.pasteAfterLinkTemplate = '
819  . $this->tt_content_drawPasteIcon($pasteItem, $pasteTitle, $copyMode, 't3js-paste-after', 'pasteAfterRecord')
820  . ';';
821  } else {
822  $addExtOnReadyCode = '
823  top.pasteIntoLinkTemplate = \'\';
824  top.pasteAfterLinkTemplate = \'\';';
825  }
826  $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
827  $pageRenderer->addJsInlineCode('pasteLinkTemplates', $addExtOnReadyCode);
828  // If language mode, then make another presentation:
829  // Notice that THIS presentation will override the value of $out!
830  // But it needs the code above to execute since $languageColumn is filled with content we need!
831  if ($this->tt_contentConfig['languageMode']) {
832  // Get language selector:
833  $languageSelector = $this->languageSelector($id);
834  // Reset out - we will make new content here:
835  $out = '';
836  // Traverse languages found on the page and build up the table displaying them side by side:
837  $cCont = [];
838  $sCont = [];
839  foreach ($langListArr as $lP) {
840  $languageMode = '';
841  $labelClass = 'info';
842  // Header:
843  $lP = (int)$lP;
844  // Determine language mode
845  if ($lP > 0 && isset($this->languageHasTranslationsCache[$lP]['mode'])) {
846  switch ($this->languageHasTranslationsCache[$lP]['mode']) {
847  case 'mixed':
848  $languageMode = $this->getLanguageService()->getLL('languageModeMixed');
849  $labelClass = 'danger';
850  break;
851  case 'connected':
852  $languageMode = $this->getLanguageService()->getLL('languageModeConnected');
853  break;
854  case 'free':
855  $languageMode = $this->getLanguageService()->getLL('languageModeFree');
856  break;
857  default:
858  // we'll let opcode optimize this intentionally empty case
859  }
860  }
861  $cCont[$lP] = '
862  <td valign="top" class="t3-page-column t3-page-column-lang-name" data-language-uid="' . $lP . '">
863  <h2>' . htmlspecialchars($this->tt_contentConfig['languageCols'][$lP]) . '</h2>
864  ' . ($languageMode !== '' ? '<span class="label label-' . $labelClass . '">' . $languageMode . '</span>' : '') . '
865  </td>';
866 
867  // "View page" icon is added:
868  $viewLink = '';
869  if (!VersionState::cast($this->getPageLayoutController()->pageinfo['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) {
870  $onClick = BackendUtility::viewOnClick($this->id, '', BackendUtility::BEgetRootLine($this->id), '', '', ('&L=' . $lP));
871  $viewLink = '<a href="#" class="btn btn-default btn-sm" onclick="' . htmlspecialchars($onClick) . '" title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.showPage')) . '">' . $this->iconFactory->getIcon('actions-view', Icon::SIZE_SMALL)->render() . '</a>';
872  }
873  // Language overlay page header:
874  if ($lP) {
875  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
876  ->getQueryBuilderForTable('pages_language_overlay');
877  $queryBuilder->getRestrictions()
878  ->removeAll()
879  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
880  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
881 
882  $lpRecord = $queryBuilder->select('*')
883  ->from('pages_language_overlay')
884  ->where(
885  $queryBuilder->expr()->eq(
886  'pid',
887  $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
888  ),
889  $queryBuilder->expr()->eq(
890  'sys_language_uid',
891  $queryBuilder->createNamedParameter($lP, \PDO::PARAM_INT)
892  )
893  )
894  ->setMaxResults(1)
895  ->execute()
896  ->fetch();
897 
898  BackendUtility::workspaceOL('pages_language_overlay', $lpRecord);
900  $this->iconFactory->getIconForRecord('pages_language_overlay', $lpRecord, Icon::SIZE_SMALL)->render(),
901  'pages_language_overlay',
902  $lpRecord['uid']
903  );
904  $urlParameters = [
905  'edit' => [
906  'pages_language_overlay' => [
907  $lpRecord['uid'] => 'edit'
908  ]
909  ],
910  'overrideVals' => [
911  'pages_language_overlay' => [
912  'sys_language_uid' => $lP
913  ]
914  ],
915  'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
916  ];
917  $url = BackendUtility::getModuleUrl('record_edit', $urlParameters);
918  $editLink = (
919  $this->getBackendUser()->check('tables_modify', 'pages_language_overlay')
920  ? '<a href="' . htmlspecialchars($url) . '" class="btn btn-default btn-sm"'
921  . ' title="' . htmlspecialchars($this->getLanguageService()->getLL('edit')) . '">'
922  . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>'
923  : ''
924  );
925 
926  $lPLabel =
927  '<div class="btn-group">'
928  . $viewLink
929  . $editLink
930  . '</div>'
931  . ' ' . $recordIcon . ' ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($lpRecord['title'], 20));
932  } else {
933  $editLink = '';
934  $recordIcon = '';
935  if ($this->getBackendUser()->checkLanguageAccess(0)) {
937  $this->iconFactory->getIconForRecord('pages', $this->pageRecord, Icon::SIZE_SMALL)->render(),
938  'pages',
939  $this->id
940  );
941  $urlParameters = [
942  'edit' => [
943  'pages' => [
944  $this->id => 'edit'
945  ]
946  ],
947  'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
948  ];
949  $url = BackendUtility::getModuleUrl('record_edit', $urlParameters);
950  $editLink = (
951  $this->getBackendUser()->check('tables_modify', 'pages_language_overlay')
952  ? '<a href="' . htmlspecialchars($url) . '" class="btn btn-default btn-sm"'
953  . ' title="' . htmlspecialchars($this->getLanguageService()->getLL('edit')) . '">'
954  . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>'
955  : ''
956  );
957  }
958 
959  $lPLabel =
960  '<div class="btn-group">'
961  . $viewLink
962  . $editLink
963  . '</div>'
964  . ' ' . $recordIcon . ' ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($this->pageRecord['title'], 20));
965  }
966  $sCont[$lP] = '
967  <td class="t3-page-column t3-page-lang-label nowrap">' . $lPLabel . '</td>';
968  }
969  // Add headers:
970  $out .= '<tr>' . implode('', $cCont) . '</tr>';
971  $out .= '<tr>' . implode('', $sCont) . '</tr>';
972  unset($cCont, $sCont);
973 
974  // Traverse previously built content for the columns:
975  foreach ($languageColumn as $cKey => $cCont) {
976  $out .= '<tr>';
977  foreach ($cCont as $languageId => $columnContent) {
978  $out .= '<td valign="top" class="t3-grid-cell t3-page-column t3js-page-column t3js-page-lang-column t3js-page-lang-column-' . $languageId . '">' . $columnContent . '</td>';
979  }
980  $out .= '</tr>';
981  if ($this->defLangBinding && !empty($defLangBinding[$cKey])) {
982  $maxItemsCount = max(array_map('count', $defLangBinding[$cKey]));
983  for ($i = 0; $i < $maxItemsCount; $i++) {
984  $defUid = $defaultLanguageElementsByColumn[$cKey][$i] ?? 0;
985  $cCont = [];
986  foreach ($langListArr as $lP) {
987  if ($lP > 0
988  && is_array($defLangBinding[$cKey][$lP])
989  && !$this->checkIfTranslationsExistInLanguage($defaultLanguageElementsByColumn[$cKey], $lP)
990  && count($defLangBinding[$cKey][$lP]) > $i
991  ) {
992  $slice = array_slice($defLangBinding[$cKey][$lP], $i, 1);
993  $element = $slice[0] ?? '';
994  } else {
995  $element = $defLangBinding[$cKey][$lP][$defUid] ?? '';
996  }
997  $cCont[] = $element . $this->newLanguageButton(
998  $this->getNonTranslatedTTcontentUids([$defUid], $id, $lP),
999  $lP,
1000  $cKey
1001  );
1002  }
1003  $out .= '
1004  <tr>
1005  <td valign="top" class="t3-grid-cell">' . implode(('</td>' . '
1006  <td valign="top" class="t3-grid-cell">'), $cCont) . '</td>
1007  </tr>';
1008  }
1009  }
1010  }
1011  // Finally, wrap it all in a table and add the language selector on top of it:
1012  $out = $languageSelector . '
1013  <div class="t3-grid-container">
1014  <table cellpadding="0" cellspacing="0" class="t3-page-columns t3-grid-table t3js-page-columns">
1015  ' . $out . '
1016  </table>
1017  </div>';
1018  // CSH:
1019  $out .= BackendUtility::cshItem($this->descrTable, 'language_list', null, '<span class="btn btn-default btn-sm">|</span>');
1020  }
1021 
1022  return $out;
1023  }
1024 
1025  /**********************************
1026  *
1027  * Generic listing of items
1028  *
1029  **********************************/
1040  public function makeOrdinaryList($table, $id, $fList, $icon = false, $addWhere = '')
1041  {
1042  // Initialize
1043  $addWhere = empty($addWhere) ? [] : [QueryHelper::stripLogicalOperatorPrefix($addWhere)];
1044  $queryBuilder = $this->getQueryBuilder($table, $id, $addWhere);
1045  $this->setTotalItems($table, $id, $addWhere);
1046  $dbCount = 0;
1047  $result = false;
1048  // Make query for records if there were any records found in the count operation
1049  if ($this->totalItems) {
1050  $result = $queryBuilder->execute();
1051  // Will return FALSE, if $result is invalid
1052  $dbCount = $result->rowCount();
1053  }
1054  // If records were found, render the list
1055  if (!$dbCount) {
1056  return '';
1057  }
1058  // Set fields
1059  $out = '';
1060  $this->fieldArray = GeneralUtility::trimExplode(',', '__cmds__,' . $fList . ',__editIconLink__', true);
1061  $theData = [];
1062  $theData = $this->headerFields($this->fieldArray, $table, $theData);
1063  // Title row
1064  $localizedTableTitle = htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$table]['ctrl']['title']));
1065  $out .= '<tr><th class="col-icon"></th>'
1066  . '<th colspan="' . (count($theData) - 2) . '"><span class="c-table">'
1067  . $localizedTableTitle . '</span> (' . $dbCount . ')</td>' . '<td class="col-icon"></td>'
1068  . '</tr>';
1069  // Column's titles
1070  if ($this->doEdit) {
1071  $urlParameters = [
1072  'edit' => [
1073  $table => [
1074  $this->id => 'new'
1075  ]
1076  ],
1077  'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
1078  ];
1079  $url = BackendUtility::getModuleUrl('record_edit', $urlParameters);
1080  $theData['__cmds__'] = '<a href="' . htmlspecialchars($url) . '" '
1081  . 'title="' . htmlspecialchars($this->getLanguageService()->getLL('new')) . '">'
1082  . $this->iconFactory->getIcon('actions-document-new', Icon::SIZE_SMALL)->render() . '</a>';
1083  }
1084  $out .= $this->addElement(1, '', $theData, ' class="c-headLine"', 15, '', 'th');
1085  // Render Items
1086  $this->eCounter = $this->firstElementNumber;
1087  while ($row = $result->fetch()) {
1088  BackendUtility::workspaceOL($table, $row);
1089  if (is_array($row)) {
1090  list($flag, $code) = $this->fwd_rwd_nav();
1091  $out .= $code;
1092  if ($flag) {
1093  $Nrow = [];
1094  // Setting icons links
1095  if ($icon) {
1096  $Nrow['__cmds__'] = $this->getIcon($table, $row);
1097  }
1098  // Get values:
1099  $Nrow = $this->dataFields($this->fieldArray, $table, $row, $Nrow);
1100  // Attach edit icon
1101  if ($this->doEdit && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT)) {
1102  $urlParameters = [
1103  'edit' => [
1104  $table => [
1105  $row['uid'] => 'edit'
1106  ]
1107  ],
1108  'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
1109  ];
1110  $url = BackendUtility::getModuleUrl('record_edit', $urlParameters);
1111  $Nrow['__editIconLink__'] = '<a class="btn btn-default" href="' . htmlspecialchars($url)
1112  . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('edit')) . '">'
1113  . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>';
1114  } else {
1115  $Nrow['__editIconLink__'] = $this->noEditIcon();
1116  }
1117  $out .= $this->addElement(1, '', $Nrow);
1118  }
1119  $this->eCounter++;
1120  }
1121  }
1122  // Wrap it all in a table:
1123  $out = '
1124  <!--
1125  Standard list of table "' . $table . '"
1126  -->
1127  <div class="table-fit"><table class="table table-hover table-striped">
1128  ' . $out . '
1129  </table></div>';
1130  return $out;
1131  }
1132 
1147  public function dataFields($fieldArr, $table, $row, $out = [])
1148  {
1149  // Check table validity
1150  if (!isset($GLOBALS['TCA'][$table])) {
1151  return $out;
1152  }
1153 
1154  $thumbsCol = $GLOBALS['TCA'][$table]['ctrl']['thumbnail'];
1155  // Traverse fields
1156  foreach ($fieldArr as $fieldName) {
1157  if ($GLOBALS['TCA'][$table]['columns'][$fieldName]) {
1158  // Each field has its own cell (if configured in TCA)
1159  // If the column is a thumbnail column:
1160  if ($fieldName == $thumbsCol) {
1161  $out[$fieldName] = $this->thumbCode($row, $table, $fieldName);
1162  } else {
1163  // ... otherwise just render the output:
1164  $out[$fieldName] = nl2br(htmlspecialchars(trim(GeneralUtility::fixed_lgd_cs(
1165  BackendUtility::getProcessedValue($table, $fieldName, $row[$fieldName], 0, 0, 0, $row['uid']),
1166  250
1167  ))));
1168  }
1169  } else {
1170  // Each field is separated by <br /> and shown in the same cell (If not a TCA field, then explode
1171  // the field name with ";" and check each value there as a TCA configured field)
1172  $theFields = explode(';', $fieldName);
1173  // Traverse fields, separated by ";" (displayed in a single cell).
1174  foreach ($theFields as $fName2) {
1175  if ($GLOBALS['TCA'][$table]['columns'][$fName2]) {
1176  $out[$fieldName] .= '<strong>' . htmlspecialchars($this->getLanguageService()->sL(
1177  $GLOBALS['TCA'][$table]['columns'][$fName2]['label']
1178  )) . '</strong>' . '&nbsp;&nbsp;' . htmlspecialchars(GeneralUtility::fixed_lgd_cs(
1179  BackendUtility::getProcessedValue($table, $fName2, $row[$fName2], 0, 0, 0, $row['uid']),
1180  25
1181  )) . '<br />';
1182  }
1183  }
1184  }
1185  // If no value, add a nbsp.
1186  if (!$out[$fieldName]) {
1187  $out[$fieldName] = '&nbsp;';
1188  }
1189  // Wrap in dimmed-span tags if record is "disabled"
1190  if ($this->isDisabled($table, $row)) {
1191  $out[$fieldName] = '<span class="text-muted">' . $out[$fieldName] . '</span>';
1192  }
1193  }
1194  return $out;
1195  }
1196 
1206  public function headerFields($fieldArr, $table, $out = [])
1207  {
1208  foreach ($fieldArr as $fieldName) {
1209  $ll = htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$table]['columns'][$fieldName]['label']));
1210  $out[$fieldName] = $ll ? $ll : '&nbsp;';
1211  }
1212  return $out;
1213  }
1214 
1225  protected function getContentRecordsPerColumn($table, $id, array $columns, $additionalWhereClause = '')
1226  {
1227  $columns = array_map('intval', $columns);
1228  $contentRecordsPerColumn = array_fill_keys($columns, []);
1229 
1230  $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1231  ->getQueryBuilderForTable('tt_content')
1232  ->expr();
1233 
1234  $queryBuilder = $this->getQueryBuilder(
1235  'tt_content',
1236  $id,
1237  [
1238  $expressionBuilder->in('colPos', $columns),
1239  $additionalWhereClause
1240  ]
1241  );
1242 
1243  // Traverse any selected elements and render their display code:
1244  $results = $this->getResult($queryBuilder->execute());
1245 
1246  foreach ($results as $record) {
1247  $columnValue = $record['colPos'];
1248  $contentRecordsPerColumn[$columnValue][] = $record;
1249  }
1250 
1251  return $contentRecordsPerColumn;
1252  }
1253 
1254  /**********************************
1255  *
1256  * Additional functions; Pages
1257  *
1258  **********************************/
1259 
1269  protected function getPageRecordsRecursive(int $pid, int $depth, string $iconPrefix = '', array $rows = []): array
1270  {
1271  $depth--;
1272  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
1273  $queryBuilder->getRestrictions()
1274  ->removeAll()
1275  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1276  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1277 
1278  $queryBuilder
1279  ->select('*')
1280  ->from('pages')
1281  ->where(
1282  $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)),
1283  $this->getBackendUser()->getPagePermsClause(1)
1284  );
1285 
1286  if (!empty($GLOBALS['TCA']['pages']['ctrl']['sortby'])) {
1287  $queryBuilder->orderBy($GLOBALS['TCA']['pages']['ctrl']['sortby']);
1288  }
1289 
1290  if ($depth >= 0) {
1291  $result = $queryBuilder->execute();
1292  $rowCount = $result->rowCount();
1293  $count = 0;
1294  while ($row = $result->fetch()) {
1295  BackendUtility::workspaceOL('pages', $row);
1296  if (is_array($row)) {
1297  $count++;
1298  $row['treeIcons'] = $iconPrefix
1299  . '<span class="treeline-icon treeline-icon-join'
1300  . ($rowCount === $count ? 'bottom' : '')
1301  . '"></span>';
1302  $rows[] = $row;
1303  // Get the branch
1304  $spaceOutIcons = '<span class="treeline-icon treeline-icon-'
1305  . ($rowCount === $count ? 'clear' : 'line')
1306  . '"></span>';
1307  $rows = $this->getPageRecordsRecursive(
1308  $row['uid'],
1309  $row['php_tree_stop'] ? 0 : $depth,
1310  $iconPrefix . $spaceOutIcons,
1311  $rows
1312  );
1313  }
1314  }
1315  }
1316 
1317  return $rows;
1318  }
1319 
1331  public function pages_getTree($theRows, $pid, $qWhere, $treeIcons, $depth)
1332  {
1334  $depth--;
1335  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
1336  $queryBuilder->getRestrictions()
1337  ->removeAll();
1338  $queryBuilder
1339  ->select('*')
1340  ->from('pages')
1341  ->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)));
1342 
1343  if (!empty($GLOBALS['TCA']['pages']['ctrl']['sortby'])) {
1344  $queryBuilder->orderBy($GLOBALS['TCA']['pages']['ctrl']['sortby']);
1345  }
1346 
1347  if (!empty($qWhere)) {
1348  $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($qWhere));
1349  }
1350 
1351  if ($depth >= 0) {
1352  $result = $queryBuilder->execute();
1353  $rc = $result->rowCount();
1354  $c = 0;
1355  while ($row = $result->fetch()) {
1356  BackendUtility::workspaceOL('pages', $row);
1357  if (is_array($row)) {
1358  $c++;
1359  $row['treeIcons'] = $treeIcons . '<span class="treeline-icon treeline-icon-join' . ($rc === $c ? 'bottom' : '') . '"></span>';
1360  $theRows[] = $row;
1361  // Get the branch
1362  $spaceOutIcons = '<span class="treeline-icon treeline-icon-' . ($rc === $c ? 'clear' : 'line') . '"></span>';
1363  $theRows = $this->pages_getTree(
1364  $theRows,
1365  $row['uid'],
1366  $qWhere,
1367  $treeIcons . $spaceOutIcons,
1368  $row['php_tree_stop'] ? 0 : $depth
1369  );
1370  }
1371  }
1372  } else {
1373  $count = (int)$queryBuilder->count('uid')->execute()->fetchColumn(0);
1374  if ($count) {
1375  $this->plusPages[$pid] = $count;
1376  }
1377  }
1378  return $theRows;
1379  }
1380 
1388  public function pages_drawItem($row, $fieldArr)
1389  {
1390  // Initialization
1391  $theIcon = $this->getIcon('pages', $row);
1392  // Preparing and getting the data-array
1393  $theData = [];
1394  foreach ($fieldArr as $field) {
1395  switch ($field) {
1396  case 'title':
1397  $pTitle = htmlspecialchars(BackendUtility::getProcessedValue('pages', $field, $row[$field], 20));
1398  $theData[$field] = $row['treeIcons'] . $theIcon . $pTitle . '&nbsp;&nbsp;';
1399  break;
1400  case 'php_tree_stop':
1401  // Intended fall through
1402  case 'TSconfig':
1403  $theData[$field] = $row[$field] ? '&nbsp;<strong>x</strong>' : '&nbsp;';
1404  break;
1405  case 'uid':
1406  if ($this->getBackendUser()->doesUserHaveAccess($row, 2) && $row['uid'] > 0) {
1407  $urlParameters = [
1408  'edit' => [
1409  'pages' => [
1410  $row['uid'] => 'edit'
1411  ]
1412  ],
1413  'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
1414  ];
1415  $url = BackendUtility::getModuleUrl('record_edit', $urlParameters);
1416  $eI = '<a class="btn btn-default" href="' . htmlspecialchars($url)
1417  . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('editThisPage')) . '">'
1418  . $this->iconFactory->getIcon('actions-page-open', Icon::SIZE_SMALL)->render() . '</a>';
1419  } else {
1420  $eI = '';
1421  }
1422  $theData[$field] = $eI . '<span align="right">' . $row['uid'] . '</span>';
1423  break;
1424  case 'shortcut':
1425  case 'shortcut_mode':
1426  if ((int)$row['doktype'] === \TYPO3\CMS\Frontend\Page\PageRepository::DOKTYPE_SHORTCUT) {
1427  $theData[$field] = $this->getPagesTableFieldValue($field, $row);
1428  }
1429  break;
1430  default:
1431  if (substr($field, 0, 6) === 'table_') {
1432  $f2 = substr($field, 6);
1433  if ($GLOBALS['TCA'][$f2]) {
1434  $c = $this->numberOfRecords($f2, $row['uid']);
1435  $theData[$field] = '&nbsp;&nbsp;' . ($c ? $c : '');
1436  }
1437  } else {
1438  $theData[$field] = $this->getPagesTableFieldValue($field, $row);
1439  }
1440  }
1441  }
1442  $this->addElement_tdParams['title'] = $row['_CSSCLASS'] ? ' class="' . $row['_CSSCLASS'] . '"' : '';
1443  return $this->addElement(1, '', $theData);
1444  }
1445 
1454  protected function getPagesTableFieldValue($field, array $row)
1455  {
1456  return '&nbsp;&nbsp;' . htmlspecialchars(BackendUtility::getProcessedValue('pages', $field, $row[$field]));
1457  }
1458 
1459  /**********************************
1460  *
1461  * Additional functions; Content Elements
1462  *
1463  **********************************/
1471  public function tt_content_drawColHeader($colName, $editParams = '')
1472  {
1473  $iconsArr = [];
1474  // Create command links:
1475  if ($this->tt_contentConfig['showCommands']) {
1476  // Edit whole of column:
1477  if ($editParams && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT) && $this->getBackendUser()->checkLanguageAccess(0)) {
1478  $iconsArr['edit'] = '<a href="#" onclick="'
1479  . htmlspecialchars(BackendUtility::editOnClick($editParams)) . '" title="'
1480  . htmlspecialchars($this->getLanguageService()->getLL('editColumn')) . '">'
1481  . $this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)->render() . '</a>';
1482  }
1483  }
1484  $icons = '';
1485  if (!empty($iconsArr)) {
1486  $icons = '<div class="t3-page-column-header-icons">' . implode('', $iconsArr) . '</div>';
1487  }
1488  // Create header row:
1489  $out = '<div class="t3-page-column-header">
1490  ' . $icons . '
1491  <div class="t3-page-column-header-label">' . htmlspecialchars($colName) . '</div>
1492  </div>';
1493  return $out;
1494  }
1495 
1507  protected function tt_content_drawPasteIcon($pasteItem, $pasteTitle, $copyMode, $cssClass, $title)
1508  {
1509  $pasteIcon = json_encode(
1510  ' <a data-content="' . htmlspecialchars($pasteItem) . '"'
1511  . ' data-title="' . htmlspecialchars($pasteTitle) . '"'
1512  . ' class="t3js-paste t3js-paste' . htmlspecialchars($copyMode) . ' ' . htmlspecialchars($cssClass) . ' btn btn-default btn-sm"'
1513  . ' title="' . htmlspecialchars($this->getLanguageService()->getLL($title)) . '">'
1514  . $this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL)->render()
1515  . '</a>'
1516  );
1517  return $pasteIcon;
1518  }
1519 
1527  protected function tt_content_drawFooter(array $row)
1528  {
1529  $content = '';
1530  // Get processed values:
1531  $info = [];
1532  $this->getProcessedValue('tt_content', 'starttime,endtime,fe_group,space_before_class,space_after_class', $row, $info);
1533 
1534  // Content element annotation
1535  if (!empty($GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn']) && !empty($row[$GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn']])) {
1536  $info[] = htmlspecialchars($row[$GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn']]);
1537  }
1538 
1539  // Call drawFooter hooks
1540  $drawFooterHooks = &$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawFooter'];
1541  if (is_array($drawFooterHooks)) {
1542  foreach ($drawFooterHooks as $hookClass) {
1543  $hookObject = GeneralUtility::getUserObj($hookClass);
1544  if (!$hookObject instanceof PageLayoutViewDrawFooterHookInterface) {
1545  throw new \UnexpectedValueException($hookClass . ' must implement interface ' . PageLayoutViewDrawFooterHookInterface::class, 1404378171);
1546  }
1547  $hookObject->preProcess($this, $info, $row);
1548  }
1549  }
1550 
1551  // Display info from records fields:
1552  if (!empty($info)) {
1553  $content = '<div class="t3-page-ce-info">
1554  ' . implode('<br>', $info) . '
1555  </div>';
1556  }
1557  // Wrap it
1558  if (!empty($content)) {
1559  $content = '<div class="t3-page-ce-footer">' . $content . '</div>';
1560  }
1561  return $content;
1562  }
1563 
1574  public function tt_content_drawHeader($row, $space = 0, $disableMoveAndNewButtons = false, $langMode = false, $dragDropEnabled = false)
1575  {
1576  $out = '';
1577  // If show info is set...;
1578  if ($this->tt_contentConfig['showInfo'] && $this->getBackendUser()->recordEditAccessInternals('tt_content', $row)) {
1579  // Render control panel for the element:
1580  if ($this->tt_contentConfig['showCommands'] && $this->doEdit) {
1581  // Edit content element:
1582  $urlParameters = [
1583  'edit' => [
1584  'tt_content' => [
1585  $this->tt_contentData['nextThree'][$row['uid']] => 'edit'
1586  ]
1587  ],
1588  'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI') . '#element-tt_content-' . $row['uid'],
1589  ];
1590  $url = BackendUtility::getModuleUrl('record_edit', $urlParameters) . '#element-tt_content-' . $row['uid'];
1591 
1592  $out .= '<a class="btn btn-default" href="' . htmlspecialchars($url)
1593  . '" title="' . htmlspecialchars($this->nextThree > 1
1594  ? sprintf($this->getLanguageService()->getLL('nextThree'), $this->nextThree)
1595  : $this->getLanguageService()->getLL('edit'))
1596  . '">' . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>';
1597  // Hide element:
1598  $hiddenField = $GLOBALS['TCA']['tt_content']['ctrl']['enablecolumns']['disabled'];
1599  if ($hiddenField && $GLOBALS['TCA']['tt_content']['columns'][$hiddenField]
1600  && (!$GLOBALS['TCA']['tt_content']['columns'][$hiddenField]['exclude']
1601  || $this->getBackendUser()->check('non_exclude_fields', 'tt_content:' . $hiddenField))
1602  ) {
1603  if ($row[$hiddenField]) {
1604  $value = 0;
1605  $label = 'unHide';
1606  } else {
1607  $value = 1;
1608  $label = 'hide';
1609  }
1610  $params = '&data[tt_content][' . ($row['_ORIG_uid'] ? $row['_ORIG_uid'] : $row['uid'])
1611  . '][' . $hiddenField . ']=' . $value;
1612  $out .= '<a class="btn btn-default" href="' . htmlspecialchars(BackendUtility::getLinkToDataHandlerAction($params))
1613  . '#element-tt_content-' . $row['uid'] . '" title="' . htmlspecialchars($this->getLanguageService()->getLL($label)) . '">'
1614  . $this->iconFactory->getIcon('actions-edit-' . strtolower($label), Icon::SIZE_SMALL)->render() . '</a>';
1615  }
1616  // Delete
1617  $disableDeleteTS = $this->getBackendUser()->getTSConfig('options.disableDelete');
1618  $disableDelete = (bool)trim(isset($disableDeleteTS['properties']['tt_content']) ? $disableDeleteTS['properties']['tt_content'] : $disableDeleteTS['value']);
1619  if (!$disableDelete) {
1620  $params = '&cmd[tt_content][' . $row['uid'] . '][delete]=1';
1621  $confirm = $this->getLanguageService()->getLL('deleteWarning')
1622  . BackendUtility::translationCount('tt_content', $row['uid'], (' '
1623  . $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.translationsOfRecord')));
1624  $out .= '<a class="btn btn-default t3js-modal-trigger" href="' . htmlspecialchars(BackendUtility::getLinkToDataHandlerAction($params)) . '"'
1625  . ' data-severity="warning"'
1626  . ' data-title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_alt_doc.xlf:label.confirm.delete_record.title')) . '"'
1627  . ' data-content="' . htmlspecialchars($confirm) . '" '
1628  . ' data-button-close-text="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:cancel')) . '"'
1629  . ' title="' . htmlspecialchars($this->getLanguageService()->getLL('deleteItem')) . '">'
1630  . $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render() . '</a>';
1631  if ($out && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT)) {
1632  $out = '<div class="btn-group btn-group-sm" role="group">' . $out . '</div>';
1633  } else {
1634  $out = '';
1635  }
1636  }
1637  if (!$disableMoveAndNewButtons) {
1638  $moveButtonContent = '';
1639  $displayMoveButtons = false;
1640  // Move element up:
1641  if ($this->tt_contentData['prev'][$row['uid']]) {
1642  $params = '&cmd[tt_content][' . $row['uid'] . '][move]=' . $this->tt_contentData['prev'][$row['uid']];
1643  $moveButtonContent .= '<a class="btn btn-default" href="'
1644  . htmlspecialchars(BackendUtility::getLinkToDataHandlerAction($params))
1645  . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('moveUp')) . '">'
1646  . $this->iconFactory->getIcon('actions-move-up', Icon::SIZE_SMALL)->render() . '</a>';
1647  if (!$dragDropEnabled) {
1648  $displayMoveButtons = true;
1649  }
1650  } else {
1651  $moveButtonContent .= '<span class="btn btn-default disabled">' . $this->iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
1652  }
1653  // Move element down:
1654  if ($this->tt_contentData['next'][$row['uid']]) {
1655  $params = '&cmd[tt_content][' . $row['uid'] . '][move]= ' . $this->tt_contentData['next'][$row['uid']];
1656  $moveButtonContent .= '<a class="btn btn-default" href="'
1657  . htmlspecialchars(BackendUtility::getLinkToDataHandlerAction($params))
1658  . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('moveDown')) . '">'
1659  . $this->iconFactory->getIcon('actions-move-down', Icon::SIZE_SMALL)->render() . '</a>';
1660  if (!$dragDropEnabled) {
1661  $displayMoveButtons = true;
1662  }
1663  } else {
1664  $moveButtonContent .= '<span class="btn btn-default disabled">' . $this->iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
1665  }
1666  if ($displayMoveButtons) {
1667  $out .= '<div class="btn-group btn-group-sm" role="group">' . $moveButtonContent . '</div>';
1668  }
1669  }
1670  }
1671  }
1672  $allowDragAndDrop = $this->isDragAndDropAllowed($row);
1673  $additionalIcons = [];
1674  $additionalIcons[] = $this->getIcon('tt_content', $row) . ' ';
1675  $additionalIcons[] = $langMode ? $this->languageFlag($row['sys_language_uid'], false) : '';
1676  // Get record locking status:
1677  if ($lockInfo = BackendUtility::isRecordLocked('tt_content', $row['uid'])) {
1678  $additionalIcons[] = '<a href="#" data-toggle="tooltip" data-title="' . htmlspecialchars($lockInfo['msg']) . '">'
1679  . $this->iconFactory->getIcon('status-warning-in-use', Icon::SIZE_SMALL)->render() . '</a>';
1680  }
1681  // Call stats information hook
1682  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['recStatInfoHooks'])) {
1683  $_params = ['tt_content', $row['uid'], &$row];
1684  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['recStatInfoHooks'] as $_funcRef) {
1685  $additionalIcons[] = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1686  }
1687  }
1688  // Wrap the whole header
1689  // NOTE: end-tag for <div class="t3-page-ce-body"> is in getTable_tt_content()
1690  return '<div class="t3-page-ce-header ' . ($allowDragAndDrop ? 't3-page-ce-header-draggable t3js-page-ce-draghandle' : '') . '">
1691  <div class="t3-page-ce-header-icons-left">' . implode('', $additionalIcons) . '</div>
1692  <div class="t3-page-ce-header-icons-right">' . ($out ? '<div class="btn-toolbar">' . $out . '</div>' : '') . '</div>
1693  </div>
1694  <div class="t3-page-ce-body">';
1695  }
1696 
1703  protected function isDragAndDropAllowed(array $row)
1704  {
1705  if ((int)$row['l18n_parent'] === 0 &&
1706  (
1707  $this->getBackendUser()->isAdmin()
1708  || ((int)$row['editlock'] === 0 && (int)$this->pageinfo['editlock'] === 0)
1709  && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT)
1710  && $this->getBackendUser()->checkAuthMode('tt_content', 'CType', $row['CType'], $GLOBALS['TYPO3_CONF_VARS']['BE']['explicitADmode'])
1711  )
1712  ) {
1713  return true;
1714  }
1715  return false;
1716  }
1717 
1725  public function tt_content_drawItem($row)
1726  {
1727  $out = '';
1728  $outHeader = '';
1729  // Make header:
1730 
1731  if ($row['header']) {
1732  $infoArr = [];
1733  $this->getProcessedValue('tt_content', 'header_position,header_layout,header_link', $row, $infoArr);
1734  $hiddenHeaderNote = '';
1735  // If header layout is set to 'hidden', display an accordant note:
1736  if ($row['header_layout'] == 100) {
1737  $hiddenHeaderNote = ' <em>[' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.hidden')) . ']</em>';
1738  }
1739  $outHeader = $row['date']
1740  ? htmlspecialchars($this->itemLabels['date'] . ' ' . BackendUtility::date($row['date'])) . '<br />'
1741  : '';
1742  $outHeader .= '<strong>' . $this->linkEditContent($this->renderText($row['header']), $row)
1743  . $hiddenHeaderNote . '</strong><br />';
1744  }
1745  // Make content:
1746  $infoArr = [];
1747  $drawItem = true;
1748  // Hook: Render an own preview of a record
1749  $drawItemHooks = &$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawItem'];
1750  if (is_array($drawItemHooks)) {
1751  foreach ($drawItemHooks as $hookClass) {
1752  $hookObject = GeneralUtility::getUserObj($hookClass);
1753  if (!$hookObject instanceof PageLayoutViewDrawItemHookInterface) {
1754  throw new \UnexpectedValueException($hookClass . ' must implement interface ' . PageLayoutViewDrawItemHookInterface::class, 1218547409);
1755  }
1756  $hookObject->preProcess($this, $drawItem, $outHeader, $out, $row);
1757  }
1758  }
1759 
1760  // If the previous hook did not render something,
1761  // then check if a Fluid-based preview template was defined for this CType
1762  // and render it via Fluid. Possible option:
1763  // mod.web_layout.tt_content.preview.media = EXT:site_mysite/Resources/Private/Templates/Preview/Media.html
1764  if ($drawItem) {
1765  $tsConfig = BackendUtility::getModTSconfig($row['pid'], 'mod.web_layout.tt_content.preview');
1766  $fluidTemplateFile = '';
1767 
1768  if (
1769  $row['CType'] === 'list' && !empty($row['list_type'])
1770  && !empty($tsConfig['properties']['list.'][$row['list_type']])
1771  ) {
1772  $fluidTemplateFile = $tsConfig['properties']['list.'][$row['list_type']];
1773  } elseif (!empty($tsConfig['properties'][$row['CType']])) {
1774  $fluidTemplateFile = $tsConfig['properties'][$row['CType']];
1775  }
1776 
1777  if ($fluidTemplateFile) {
1778  $fluidTemplateFile = GeneralUtility::getFileAbsFileName($fluidTemplateFile);
1779  if ($fluidTemplateFile) {
1780  try {
1782  $view = GeneralUtility::makeInstance(StandaloneView::class);
1783  $view->setTemplatePathAndFilename($fluidTemplateFile);
1784  $view->assignMultiple($row);
1785  if (!empty($row['pi_flexform'])) {
1787  $flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
1788  $view->assign('pi_flexform_transformed', $flexFormService->convertFlexFormContentToArray($row['pi_flexform']));
1789  }
1790  $out = $view->render();
1791  $drawItem = false;
1792  } catch (\Exception $e) {
1793  GeneralUtility::sysLog(
1794  sprintf(
1795  'The backend preview for content element $d can not be rendered using the Fluid template file "%s": %s',
1796  $row['uid'],
1797  $fluidTemplateFile,
1798  $e->getMessage()
1799  ),
1800  'backend',
1802  );
1803  }
1804  }
1805  }
1806  }
1807 
1808  // Draw preview of the item depending on its CType (if not disabled by previous hook):
1809  if ($drawItem) {
1810  switch ($row['CType']) {
1811  case 'header':
1812  if ($row['subheader']) {
1813  $out .= $this->linkEditContent($this->renderText($row['subheader']), $row) . '<br />';
1814  }
1815  break;
1816  case 'bullets':
1817  case 'table':
1818  if ($row['bodytext']) {
1819  $out .= $this->linkEditContent($this->renderText($row['bodytext']), $row) . '<br />';
1820  }
1821  break;
1822  case 'uploads':
1823  if ($row['media']) {
1824  $out .= $this->linkEditContent($this->getThumbCodeUnlinked($row, 'tt_content', 'media'), $row) . '<br />';
1825  }
1826  break;
1827  case 'menu':
1828  $contentType = $this->CType_labels[$row['CType']];
1829  $out .= $this->linkEditContent('<strong>' . htmlspecialchars($contentType) . '</strong>', $row) . '<br />';
1830  // Add Menu Type
1831  $menuTypeLabel = $this->getLanguageService()->sL(
1832  BackendUtility::getLabelFromItemListMerged($row['pid'], 'tt_content', 'menu_type', $row['menu_type'])
1833  );
1834  $menuTypeLabel = $menuTypeLabel ?: 'invalid menu type';
1835  $out .= $this->linkEditContent($menuTypeLabel, $row);
1836  if ($row['menu_type'] !== '2' && ($row['pages'] || $row['selected_categories'])) {
1837  // Show pages if menu type is not "Sitemap"
1838  $out .= ':' . $this->linkEditContent($this->generateListForCTypeMenu($row), $row) . '<br />';
1839  }
1840  break;
1841  case 'shortcut':
1842  if (!empty($row['records'])) {
1843  $shortcutContent = [];
1844  $recordList = explode(',', $row['records']);
1845  foreach ($recordList as $recordIdentifier) {
1846  $split = BackendUtility::splitTable_Uid($recordIdentifier);
1847  $tableName = empty($split[0]) ? 'tt_content' : $split[0];
1848  $shortcutRecord = BackendUtility::getRecord($tableName, $split[1]);
1849  if (is_array($shortcutRecord)) {
1850  $icon = $this->iconFactory->getIconForRecord($tableName, $shortcutRecord, Icon::SIZE_SMALL)->render();
1852  $icon,
1853  $tableName,
1854  $shortcutRecord['uid']
1855  );
1856  $shortcutContent[] = $icon
1857  . htmlspecialchars(BackendUtility::getRecordTitle($tableName, $shortcutRecord));
1858  }
1859  }
1860  $out .= implode('<br />', $shortcutContent) . '<br />';
1861  }
1862  break;
1863  case 'list':
1864  $hookArr = [];
1865  $hookOut = '';
1866  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['list_type_Info'][$row['list_type']])) {
1867  $hookArr = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['list_type_Info'][$row['list_type']];
1868  } elseif (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['list_type_Info']['_DEFAULT'])) {
1869  $hookArr = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['list_type_Info']['_DEFAULT'];
1870  }
1871  if (!empty($hookArr)) {
1872  $_params = ['pObj' => &$this, 'row' => $row, 'infoArr' => $infoArr];
1873  foreach ($hookArr as $_funcRef) {
1874  $hookOut .= GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1875  }
1876  }
1877  if ((string)$hookOut !== '') {
1878  $out .= $hookOut;
1879  } elseif (!empty($row['list_type'])) {
1880  $label = BackendUtility::getLabelFromItemListMerged($row['pid'], 'tt_content', 'list_type', $row['list_type']);
1881  if (!empty($label)) {
1882  $out .= $this->linkEditContent('<strong>' . htmlspecialchars($this->getLanguageService()->sL($label)) . '</strong>', $row) . '<br />';
1883  } else {
1884  $message = sprintf($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.noMatchingValue'), $row['list_type']);
1885  $out .= '<span class="label label-warning">' . htmlspecialchars($message) . '</span>';
1886  }
1887  } else {
1888  $out .= '<strong>' . $this->getLanguageService()->getLL('noPluginSelected') . '</strong>';
1889  }
1890  $out .= htmlspecialchars($this->getLanguageService()->sL(
1891  BackendUtility::getLabelFromItemlist('tt_content', 'pages', $row['pages'])
1892  )) . '<br />';
1893  break;
1894  default:
1895  $contentType = $this->CType_labels[$row['CType']];
1896  if (!isset($contentType)) {
1897  $contentType = BackendUtility::getLabelFromItemListMerged($row['pid'], 'tt_content', 'CType', $row['CType']);
1898  }
1899 
1900  if ($contentType) {
1901  $out .= $this->linkEditContent('<strong>' . htmlspecialchars($contentType) . '</strong>', $row) . '<br />';
1902  if ($row['bodytext']) {
1903  $out .= $this->linkEditContent($this->renderText($row['bodytext']), $row) . '<br />';
1904  }
1905  if ($row['image']) {
1906  $out .= $this->linkEditContent($this->getThumbCodeUnlinked($row, 'tt_content', 'image'), $row) . '<br />';
1907  }
1908  } else {
1909  $message = sprintf(
1910  $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.noMatchingValue'),
1911  $row['CType']
1912  );
1913  $out .= '<span class="label label-warning">' . htmlspecialchars($message) . '</span>';
1914  }
1915  }
1916  }
1917  // Wrap span-tags:
1918  $out = '
1919  <span class="exampleContent">' . $out . '</span>';
1920  // Add header:
1921  $out = $outHeader . $out;
1922  // Return values:
1923  if ($this->isDisabled('tt_content', $row)) {
1924  return '<span class="text-muted">' . $out . '</span>';
1925  }
1926  return $out;
1927  }
1928 
1935  protected function generateListForCTypeMenu(array $row)
1936  {
1937  $table = 'pages';
1938  $field = 'pages';
1939  // get categories instead of pages
1940  if (strpos($row['menu_type'], 'categorized_') !== false) {
1941  $table = 'sys_category';
1942  $field = 'selected_categories';
1943  }
1944  if (trim($row[$field]) === '') {
1945  return '';
1946  }
1947  $content = '';
1948  $uidList = explode(',', $row[$field]);
1949  foreach ($uidList as $uid) {
1950  $uid = (int)$uid;
1951  $record = BackendUtility::getRecord($table, $uid, 'title');
1952  $content .= '<br>' . $record['title'] . ' (' . $uid . ')';
1953  }
1954  return $content;
1955  }
1956 
1967  public function getNonTranslatedTTcontentUids($defaultLanguageUids, $id, $lP)
1968  {
1969  if ($lP && !empty($defaultLanguageUids)) {
1970  // Select all translations here:
1971  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1972  ->getQueryBuilderForTable('tt_content');
1973  $queryBuilder->getRestrictions()
1974  ->removeAll()
1975  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1976  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class, null, false));
1977  $queryBuilder
1978  ->select('*')
1979  ->from('tt_content')
1980  ->where(
1981  $queryBuilder->expr()->eq(
1982  'sys_language_uid',
1983  $queryBuilder->createNamedParameter($lP, \PDO::PARAM_INT)
1984  ),
1985  $queryBuilder->expr()->in(
1986  'l18n_parent',
1987  $queryBuilder->createNamedParameter($defaultLanguageUids, Connection::PARAM_INT_ARRAY)
1988  )
1989  );
1990 
1991  $result = $queryBuilder->execute();
1992 
1993  // Flip uids:
1994  $defaultLanguageUids = array_flip($defaultLanguageUids);
1995  // Traverse any selected elements and unset original UID if any:
1996  while ($row = $result->fetch()) {
1997  BackendUtility::workspaceOL('tt_content', $row);
1998  unset($defaultLanguageUids[$row['l18n_parent']]);
1999  }
2000  // Flip again:
2001  $defaultLanguageUids = array_keys($defaultLanguageUids);
2002  }
2003  return $defaultLanguageUids;
2004  }
2005 
2014  public function newLanguageButton($defaultLanguageUids, $lP, $colPos = 0)
2015  {
2016  $lP = (int)$lP;
2017  if (!$this->doEdit || !$lP) {
2018  return '';
2019  }
2020  $theNewButton = '';
2021 
2022  $allowCopy = true;
2023  $allowTranslate = true;
2024  if (!empty($this->languageHasTranslationsCache[$lP])) {
2025  if (isset($this->languageHasTranslationsCache[$lP]['hasStandAloneContent'])) {
2026  $allowTranslate = false;
2027  }
2028  if (isset($this->languageHasTranslationsCache[$lP]['hasTranslations'])) {
2029  $allowCopy = !$this->languageHasTranslationsCache[$lP]['hasTranslations'];
2030  }
2031  }
2032 
2033  if (isset($this->contentElementCache[$lP][$colPos]) && is_array($this->contentElementCache[$lP][$colPos])) {
2034  foreach ($this->contentElementCache[$lP][$colPos] as $record) {
2035  $key = array_search($record['l10n_source'], $defaultLanguageUids);
2036  if ($key !== false) {
2037  unset($defaultLanguageUids[$key]);
2038  }
2039  }
2040  }
2041 
2042  if (!empty($defaultLanguageUids)) {
2043  $theNewButton =
2044  '<input'
2045  . ' class="btn btn-default t3js-localize"'
2046  . ' type="button"'
2047  . ' disabled'
2048  . ' value="' . htmlspecialchars($this->getLanguageService()->getLL('newPageContent_translate')) . '"'
2049  . ' data-has-elements="' . (int)!empty($this->contentElementCache[$lP][$colPos]) . '"'
2050  . ' data-allow-copy="' . (int)$allowCopy . '"'
2051  . ' data-allow-translate="' . (int)$allowTranslate . '"'
2052  . ' data-table="tt_content"'
2053  . ' data-page-id="' . (int)GeneralUtility::_GP('id') . '"'
2054  . ' data-language-id="' . $lP . '"'
2055  . ' data-language-name="' . htmlspecialchars($this->tt_contentConfig['languageCols'][$lP]) . '"'
2056  . ' data-colpos-id="' . $colPos . '"'
2057  . ' data-colpos-name="' . BackendUtility::getProcessedValue('tt_content', 'colPos', $colPos) . '"'
2058  . '/>';
2059  }
2060 
2061  return '<div class="t3-page-lang-copyce">' . $theNewButton . '</div>';
2062  }
2063 
2073  public function newContentElementOnClick($id, $colPos, $sys_language)
2074  {
2075  if ($this->option_newWizard) {
2076  $tsConfig = BackendUtility::getModTSconfig($id, 'mod');
2077  $moduleName = isset($tsConfig['properties']['newContentElementWizard.']['override'])
2078  ? $tsConfig['properties']['newContentElementWizard.']['override']
2079  : 'new_content_element';
2080  $onClick = 'window.location.href=' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl($moduleName) . '&id=' . $id . '&colPos=' . $colPos
2081  . '&sys_language_uid=' . $sys_language . '&uid_pid=' . $id
2082  . '&returnUrl=' . rawurlencode(GeneralUtility::getIndpEnv('REQUEST_URI'))) . ';';
2083  } else {
2084  $onClick = BackendUtility::editOnClick('&edit[tt_content][' . $id . ']=new&defVals[tt_content][colPos]='
2085  . $colPos . '&defVals[tt_content][sys_language_uid]=' . $sys_language);
2086  }
2087  return $onClick;
2088  }
2089 
2099  public function linkEditContent($str, $row)
2100  {
2101  if ($this->doEdit && $this->getBackendUser()->recordEditAccessInternals('tt_content', $row)) {
2102  $urlParameters = [
2103  'edit' => [
2104  'tt_content' => [
2105  $row['uid'] => 'edit'
2106  ]
2107  ],
2108  'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI') . '#element-tt_content-' . $row['uid']
2109  ];
2110  $url = BackendUtility::getModuleUrl('record_edit', $urlParameters);
2111  // Return link
2112  return '<a href="' . htmlspecialchars($url) . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('edit')) . '">' . $str . '</a>';
2113  }
2114  return $str;
2115  }
2116 
2126  public function languageSelector($id)
2127  {
2128  if ($this->getBackendUser()->check('tables_modify', 'pages_language_overlay')) {
2129  // First, select all
2130  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language');
2131  $queryBuilder->getRestrictions()->removeAll();
2132  $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(HiddenRestriction::class));
2133  $statement = $queryBuilder->select('uid', 'title')
2134  ->from('sys_language')
2135  ->orderBy('sorting')
2136  ->execute();
2137  $availableTranslations = [];
2138  while ($row = $statement->fetch()) {
2139  if ($this->getBackendUser()->checkLanguageAccess($row['uid'])) {
2140  $availableTranslations[(int)$row['uid']] = $row['title'];
2141  }
2142  }
2143  // Then, subtract the languages which are already on the page:
2144  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language');
2145  $queryBuilder->getRestrictions()->removeAll();
2146  $queryBuilder->select('sys_language.uid AS uid', 'sys_language.title AS title')
2147  ->from('sys_language')
2148  ->join(
2149  'sys_language',
2150  'pages_language_overlay',
2151  'pages_language_overlay',
2152  $queryBuilder->expr()->eq('sys_language.uid', $queryBuilder->quoteIdentifier('pages_language_overlay.sys_language_uid'))
2153  )
2154  ->where(
2155  $queryBuilder->expr()->eq(
2156  'pages_language_overlay.deleted',
2157  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
2158  ),
2159  $queryBuilder->expr()->eq(
2160  'pages_language_overlay.pid',
2161  $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
2162  ),
2163  $queryBuilder->expr()->orX(
2164  $queryBuilder->expr()->gte(
2165  'pages_language_overlay.t3ver_state',
2166  $queryBuilder->createNamedParameter(
2168  \PDO::PARAM_INT
2169  )
2170  ),
2171  $queryBuilder->expr()->eq(
2172  'pages_language_overlay.t3ver_wsid',
2173  $queryBuilder->createNamedParameter($this->getBackendUser()->workspace, \PDO::PARAM_INT)
2174  )
2175  )
2176  )
2177  ->groupBy(
2178  'pages_language_overlay.sys_language_uid',
2179  'sys_language.uid',
2180  'sys_language.pid',
2181  'sys_language.tstamp',
2182  'sys_language.hidden',
2183  'sys_language.title',
2184  'sys_language.language_isocode',
2185  'sys_language.static_lang_isocode',
2186  'sys_language.flag',
2187  'sys_language.sorting'
2188  )
2189  ->orderBy('sys_language.sorting');
2190  if (!$this->getBackendUser()->isAdmin()) {
2191  $queryBuilder->andWhere(
2192  $queryBuilder->expr()->eq(
2193  'sys_language.hidden',
2194  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
2195  )
2196  );
2197  }
2198  $statement = $queryBuilder->execute();
2199  while ($row = $statement->fetch()) {
2200  unset($availableTranslations[(int)$row['uid']]);
2201  }
2202  // Remove disallowed languages
2203  if (!empty($availableTranslations)
2204  && !$this->getBackendUser()->user['admin']
2205  && $this->getBackendUser()->groupData['allowed_languages'] !== ''
2206  ) {
2207  $allowed_languages = array_flip(explode(',', $this->getBackendUser()->groupData['allowed_languages']));
2208  if (!empty($allowed_languages)) {
2209  foreach ($availableTranslations as $key => $value) {
2210  if (!isset($allowed_languages[$key]) && $key != 0) {
2211  unset($availableTranslations[$key]);
2212  }
2213  }
2214  }
2215  }
2216  // Remove disabled languages
2218  $disableLanguages = isset($modSharedTSconfig['properties']['disableLanguages'])
2219  ? GeneralUtility::trimExplode(',', $modSharedTSconfig['properties']['disableLanguages'], true)
2220  : [];
2221  if (!empty($availableTranslations) && !empty($disableLanguages)) {
2222  foreach ($disableLanguages as $language) {
2223  if ($language != 0 && isset($availableTranslations[$language])) {
2224  unset($availableTranslations[$language]);
2225  }
2226  }
2227  }
2228  // If any languages are left, make selector:
2229  if (!empty($availableTranslations)) {
2230  $output = '<option value=""></option>';
2231  foreach ($availableTranslations as $languageUid => $languageTitle) {
2232  // Build localize command URL to DataHandler (tce_db)
2233  // which redirects to FormEngine (record_edit)
2234  // which, when finished editing should return back to the current page (returnUrl)
2235  $parameters = [
2236  'justLocalized' => 'pages:' . $id . ':' . $languageUid,
2237  'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
2238  ];
2239  $redirectUrl = BackendUtility::getModuleUrl('record_edit', $parameters);
2241  '&cmd[pages][' . $id . '][localize]=' . $languageUid,
2242  $redirectUrl
2243  );
2244 
2245  $output .= '<option value="' . htmlspecialchars($targetUrl) . '">' . htmlspecialchars($languageTitle) . '</option>';
2246  }
2247 
2248  return '<div class="form-inline form-inline-spaced">'
2249  . '<div class="form-group">'
2250  . '<label for="createNewLanguage">'
2251  . htmlspecialchars($this->getLanguageService()->getLL('new_language'))
2252  . '</label>'
2253  . '<select class="form-control input-sm" name="createNewLanguage" onchange="window.location.href=this.options[this.selectedIndex].value">'
2254  . $output
2255  . '</select></div></div>';
2256  }
2257  }
2258  return '';
2259  }
2260 
2268  public function getResult($result, string $table = 'tt_content'): array
2269  {
2270  if ($result instanceof \mysqli_result) {
2271  GeneralUtility::deprecationLog('Using \TYPO3\CMS\Backend\View\PageLayoutView::getResult with a mysqli_result object is deprecated since TYPO3 CMS 8 and will be removed in TYPO3 CMS 9');
2272  }
2273  $output = [];
2274  // Traverse the result:
2275  while ($row = ($result instanceof Statement ? $result->fetch() : $result->fetch_assoc())) {
2276  BackendUtility::workspaceOL($table, $row, -99, true);
2277  if ($row) {
2278  // Add the row to the array:
2279  $output[] = $row;
2280  }
2281  }
2282  $this->generateTtContentDataArray($output);
2283  // Return selected records
2284  return $output;
2285  }
2286 
2287  /********************************
2288  *
2289  * Various helper functions
2290  *
2291  ********************************/
2292 
2301  protected function initializeClipboard()
2302  {
2303  // Start clipboard
2304  $this->clipboard = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Clipboard\Clipboard::class);
2305 
2306  // Initialize - reads the clipboard content from the user session
2307  $this->clipboard->initializeClipboard();
2308 
2309  // This locks the clipboard to the Normal for this request.
2310  $this->clipboard->lockToNormal();
2311 
2312  // Clean up pad
2313  $this->clipboard->cleanCurrent();
2314 
2315  // Save the clipboard content
2316  $this->clipboard->endClipboard();
2317  }
2318 
2324  protected function generateTtContentDataArray(array $rowArray)
2325  {
2326  if (empty($this->tt_contentData)) {
2327  $this->tt_contentData = [
2328  'nextThree' => [],
2329  'next' => [],
2330  'prev' => [],
2331  ];
2332  }
2333  foreach ($rowArray as $key => $value) {
2334  // Create the list of the next three ids (for editing links...)
2335  for ($i = 0; $i < $this->nextThree; $i++) {
2336  if (isset($rowArray[$key - $i])
2337  && !GeneralUtility::inList($this->tt_contentData['nextThree'][$rowArray[$key - $i]['uid']], $value['uid'])
2338  ) {
2339  $this->tt_contentData['nextThree'][$rowArray[$key - $i]['uid']] .= $value['uid'] . ',';
2340  }
2341  }
2342 
2343  // Create information for next and previous content elements
2344  if (isset($rowArray[$key - 1])) {
2345  if (isset($rowArray[$key - 2])) {
2346  $this->tt_contentData['prev'][$value['uid']] = -$rowArray[$key - 2]['uid'];
2347  } else {
2348  $this->tt_contentData['prev'][$value['uid']] = $value['pid'];
2349  }
2350  $this->tt_contentData['next'][$rowArray[$key - 1]['uid']] = -$value['uid'];
2351  }
2352  }
2353  }
2354 
2362  public function numberOfRecords($table, $pid)
2363  {
2364  $count = 0;
2365  if ($GLOBALS['TCA'][$table]) {
2366  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
2367  ->getQueryBuilderForTable($table);
2368  $queryBuilder->getRestrictions()
2369  ->removeAll()
2370  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
2371  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
2372  $count = (int)$queryBuilder->count('uid')
2373  ->from($table)
2374  ->where(
2375  $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT))
2376  )
2377  ->execute()
2378  ->fetchColumn();
2379  }
2380 
2381  return $count;
2382  }
2383 
2390  public function renderText($input)
2391  {
2392  $input = strip_tags($input);
2393  $input = GeneralUtility::fixed_lgd_cs($input, 1500);
2394  return nl2br(htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8', false));
2395  }
2396 
2405  public function getIcon($table, $row, $enabledClickMenuItems = '')
2406  {
2407  // Initialization
2408  $toolTip = BackendUtility::getRecordToolTip($row, 'tt_content');
2409  $icon = '<span ' . $toolTip . '>' . $this->iconFactory->getIconForRecord($table, $row, Icon::SIZE_SMALL)->render() . '</span>';
2410  $this->counter++;
2411  // The icon with link
2412  if ($this->getBackendUser()->recordEditAccessInternals($table, $row)) {
2413  $icon = BackendUtility::wrapClickMenuOnIcon($icon, $table, $row['uid']);
2414  }
2415  return $icon;
2416  }
2417 
2427  public function getProcessedValue($table, $fieldList, array $row, array &$info)
2428  {
2429  // Splitting values from $fieldList
2430  $fieldArr = explode(',', $fieldList);
2431  // Traverse fields from $fieldList
2432  foreach ($fieldArr as $field) {
2433  if ($row[$field]) {
2434  $info[] = '<strong>' . htmlspecialchars($this->itemLabels[$field]) . '</strong> '
2435  . htmlspecialchars(BackendUtility::getProcessedValue($table, $field, $row[$field]));
2436  }
2437  }
2438  }
2439 
2447  public function isDisabled($table, $row)
2448  {
2449  $enableCols = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns'];
2450  return $enableCols['disabled'] && $row[$enableCols['disabled']]
2451  || $enableCols['starttime'] && $row[$enableCols['starttime']] > $GLOBALS['EXEC_TIME']
2452  || $enableCols['endtime'] && $row[$enableCols['endtime']] && $row[$enableCols['endtime']] < $GLOBALS['EXEC_TIME'];
2453  }
2454 
2463  public function noEditIcon($label = 'noEditItems')
2464  {
2465  $title = htmlspecialchars($this->getLanguageService()->getLL($label));
2466  return '<span title="' . $title . '">' . $this->iconFactory->getIcon('status-status-edit-read-only', Icon::SIZE_SMALL)->render() . '</span>';
2467  }
2468 
2469  /*****************************************
2470  *
2471  * External renderings
2472  *
2473  *****************************************/
2474 
2483  public function getTableMenu($id)
2484  {
2485  // Initialize:
2486  $this->activeTables = [];
2487  $theTables = ['tt_content'];
2488  // External tables:
2489  if (is_array($this->externalTables)) {
2490  $theTables = array_unique(array_merge($theTables, array_keys($this->externalTables)));
2491  }
2492  $out = '';
2493  // Traverse tables to check:
2494  foreach ($theTables as $tName) {
2495  // Check access and whether the proper extensions are loaded:
2496  if ($this->getBackendUser()->check('tables_select', $tName)
2497  && (
2498  isset($this->externalTables[$tName])
2499  || $tName === 'fe_users' || $tName === 'tt_content'
2500  || \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded($tName)
2501  )
2502  ) {
2503  // Make query to count records from page:
2504  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
2505  ->getQueryBuilderForTable($tName);
2506  $queryBuilder->getRestrictions()
2507  ->removeAll()
2508  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
2509  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
2510  $count = $queryBuilder->count('uid')
2511  ->from($tName)
2512  ->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)))
2513  ->execute()
2514  ->fetchColumn();
2515  // If records were found (or if "tt_content" is the table...):
2516  if ($count || $tName === 'tt_content') {
2517  // Add row to menu:
2518  $out .= '
2519  <td><a href="#' . $tName . '" title="' . htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$tName]['ctrl']['title'])) . '"></a>'
2520  . $this->iconFactory->getIconForRecord($tName, [], Icon::SIZE_SMALL)->render()
2521  . '</td>';
2522  // ... and to the internal array, activeTables we also add table icon and title (for use elsewhere)
2523  $title = htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$tName]['ctrl']['title']))
2524  . ': ' . $count . ' ' . htmlspecialchars($this->getLanguageService()->getLL('records'));
2525  $this->activeTables[$tName] = '<span title="' . $title . '">'
2526  . $this->iconFactory->getIconForRecord($tName, [], Icon::SIZE_SMALL)->render()
2527  . '</span>'
2528  . '&nbsp;' . htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$tName]['ctrl']['title']));
2529  }
2530  }
2531  }
2532  // Wrap cells in table tags:
2533  $out = '
2534  <!--
2535  Menu of tables on the page (table menu)
2536  -->
2537  <table border="0" cellpadding="0" cellspacing="0" id="typo3-page-tblMenu">
2538  <tr>' . $out . '
2539  </tr>
2540  </table>';
2541  // Return the content:
2542  return $out;
2543  }
2544 
2553  public function getThumbCodeUnlinked($row, $table, $field)
2554  {
2555  return BackendUtility::thumbCode($row, $table, $field, '', '', null, 0, '', '', false);
2556  }
2557 
2566  protected function checkIfTranslationsExistInLanguage(array $contentElements, $language)
2567  {
2568  // If in default language, you may always create new entries
2569  // Also, you may override this strict behavior via user TS Config
2570  // If you do so, you're on your own and cannot rely on any support by the TYPO3 core
2571  // We jump out here since we don't need to do the expensive loop operations
2572  $allowInconsistentLanguageHandling = BackendUtility::getModTSconfig($this->id, 'mod.web_layout.allowInconsistentLanguageHandling');
2573  if ($language === 0 || $allowInconsistentLanguageHandling['value'] === '1') {
2574  return false;
2575  }
2579  if (!isset($this->languageHasTranslationsCache[$language])) {
2580  foreach ($contentElements as $columns) {
2581  foreach ($columns as $contentElement) {
2582  if ((int)$contentElement['l18n_parent'] === 0) {
2583  $this->languageHasTranslationsCache[$language]['hasStandAloneContent'] = true;
2584  $this->languageHasTranslationsCache[$language]['mode'] = 'free';
2585  }
2586  if ((int)$contentElement['l18n_parent'] > 0) {
2587  $this->languageHasTranslationsCache[$language]['hasTranslations'] = true;
2588  $this->languageHasTranslationsCache[$language]['mode'] = 'connected';
2589  }
2590  }
2591  }
2592  if (!isset($this->languageHasTranslationsCache[$language])) {
2593  $this->languageHasTranslationsCache[$language]['hasTranslations'] = false;
2594  }
2595  // Check whether we have a mix of both
2596  if (isset($this->languageHasTranslationsCache[$language]['hasStandAloneContent'])
2597  && $this->languageHasTranslationsCache[$language]['hasTranslations']
2598  ) {
2599  $this->languageHasTranslationsCache[$language]['mode'] = 'mixed';
2600  $message = GeneralUtility::makeInstance(
2601  FlashMessage::class,
2602  sprintf($this->getLanguageService()->getLL('staleTranslationWarning'), $this->languageIconTitles[$language]['title']),
2603  sprintf($this->getLanguageService()->getLL('staleTranslationWarningTitle'), $this->languageIconTitles[$language]['title']),
2605  );
2606  $service = GeneralUtility::makeInstance(FlashMessageService::class);
2607  $queue = $service->getMessageQueueByIdentifier();
2608  $queue->addMessage($message);
2609  }
2610  }
2611 
2612  return $this->languageHasTranslationsCache[$language]['hasTranslations'];
2613  }
2614 
2618  protected function getBackendLayoutView()
2619  {
2620  return GeneralUtility::makeInstance(BackendLayoutView::class);
2621  }
2622 
2626  protected function getBackendUser()
2627  {
2628  return $GLOBALS['BE_USER'];
2629  }
2630 
2634  protected function getPageLayoutController()
2635  {
2636  return $GLOBALS['SOBE'];
2637  }
2638 }
tt_content_drawPasteIcon($pasteItem, $pasteTitle, $copyMode, $cssClass, $title)
pages_getTree($theRows, $pid, $qWhere, $treeIcons, $depth)
static translationCount($table, $ref, $msg='')
static getRecordWSOL( $table, $uid, $fields=' *', $where='', $useDeleteClause=true, $unsetMovePointers=false)
getResult($result, string $table='tt_content')
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
getPageRecordsRecursive(int $pid, int $depth, string $iconPrefix='', array $rows=[])
static readPageAccess($id, $perms_clause)
static wrapClickMenuOnIcon( $content, $table, $uid=0, $context='', $_addParams='', $_enDisItems='', $returnTagParameters=false)
static editOnClick($params, $_='', $requestUri='')
newLanguageButton($defaultLanguageUids, $lP, $colPos=0)
getProcessedValue($table, $fieldList, array $row, array &$info)
static callUserFunction($funcName, &$params, &$ref, $_='', $errorMode=0)
checkIfTranslationsExistInLanguage(array $contentElements, $language)
getContentRecordsPerColumn($table, $id, array $columns, $additionalWhereClause='')
static viewOnClick( $pageUid, $backPath='', $rootLine=null, $anchorSection='', $alternativeUrl='', $additionalGetVars='', $switchFocus=true)
static getRecordToolTip(array $row, $table='pages')
static BEgetRootLine($uid, $clause='', $workspaceOL=false)
static getFileAbsFileName($filename, $_=null, $_2=null)
newContentElementOnClick($id, $colPos, $sys_language)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
makeOrdinaryList($table, $id, $fList, $icon=false, $addWhere='')
static workspaceOL($table, &$row, $wsid=-99, $unsetMovePointers=false)
static makeInstance($className,... $constructorArguments)
$fields
Definition: pages.php:4
getNonTranslatedTTcontentUids($defaultLanguageUids, $id, $lP)
getIcon($table, $row, $enabledClickMenuItems='')
getThumbCodeUnlinked($row, $table, $field)
tt_content_drawColHeader($colName, $editParams='')
tt_content_drawHeader($row, $space=0, $disableMoveAndNewButtons=false, $langMode=false, $dragDropEnabled=false)
addElement($h, $icon, $data, $rowParams='', $_='', $_2='', $colType='td')
dataFields($fieldArr, $table, $row, $out=[])
headerFields($fieldArr, $table, $out=[])
static cshItem($table, $field, $_='', $wrap='')
static getRecordTitle($table, $row, $prep=false, $forceResult=true)
languageFlag($sys_language_uid, $addAsAdditionalText=true)
static stripLogicalOperatorPrefix(string $constraint)
static fixed_lgd_cs($string, $chars, $appendString='...')
static getLabelFromItemListMerged($pageId, $table, $column, $key)
static getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
setTotalItems(string $table, int $pageId, array $constraints)
static getLinkToDataHandlerAction($parameters, $redirectUrl='')
static getLabelFromItemlist($table, $col, $key)
getQueryBuilder(string $table, int $pageId, array $additionalConstraints=[], array $fields=[' *'])