TYPO3 CMS  TYPO3_7-6
PageLayoutView.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Backend\View;
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
33 
37 class PageLayoutView extends \TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRecordList
38 {
44  public $pI_showUser = 0;
45 
51  public $nextThree = 3;
52 
59 
65  public $option_newWizard = 1;
66 
72  public $ext_function = 0;
73 
79  public $doEdit = 1;
80 
86  public $agePrefixes = ' min| hrs| days| yrs| min| hour| day| year';
87 
93  public $externalTables = [];
94 
100  public $descrTable;
101 
108  public $defLangBinding = false;
109 
115  public $tt_contentConfig = [
116  // Boolean: Display info-marks or not
117  'showInfo' => 1,
118  // Boolean: Display up/down arrows and edit icons for tt_content records
119  'showCommands' => 1,
120  'languageCols' => 0,
121  'languageMode' => 0,
122  'languageColsPointer' => 0,
123  'showHidden' => 1,
124  // Displays hidden records as well
125  'sys_language_uid' => 0,
126  // Which language
127  'cols' => '1,0,2,3',
128  'activeCols' => '1,0,2,3'
129  // Which columns can be accessed by current BE user
130  ];
131 
137  public $activeTables = [];
138 
142  public $tt_contentData = [
143  'nextThree' => [],
144  'prev' => [],
145  'next' => []
146  ];
147 
153  public $CType_labels = [];
154 
160  public $itemLabels = [];
161 
165  protected $clipboard;
166 
170  protected $plusPages = [];
171 
178 
184  protected $pageinfo;
185 
191  protected $languagesInColumnCache = [];
192 
199  protected $contentElementCache = [];
200 
204  protected $iconFactory;
205 
212 
217 
221  public function __construct()
222  {
223  parent::__construct();
224  $this->localizationController = GeneralUtility::makeInstance(LocalizationController::class);
225  $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
226  $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
227  $pageRenderer->addInlineLanguageLabelFile('EXT:backend/Resources/Private/Language/locallang_layout.xlf');
228  $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Tooltip');
229  $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Localization');
230  }
231 
232  /*****************************************
233  *
234  * Renderings
235  *
236  *****************************************/
245  public function getTable($table, $id, $fields = '')
246  {
247  if (isset($this->externalTables[$table])) {
248  return $this->getExternalTables($id, $table);
249  } else {
250  // Branch out based on table name:
251  switch ($table) {
252  case 'pages':
253  return $this->getTable_pages($id);
254  break;
255  case 'tt_content':
256  return $this->getTable_tt_content($id);
257  break;
258  default:
259  return '';
260  }
261  }
262  }
263 
271  public function getExternalTables($id, $table)
272  {
273  $this->pageinfo = BackendUtility::readPageAccess($id, '');
274  $type = $this->getPageLayoutController()->MOD_SETTINGS[$table];
275  if (!isset($type)) {
276  $type = 0;
277  }
278  // eg. "name;title;email;company,image"
279  $fList = $this->externalTables[$table][$type]['fList'];
280  // The columns are separeted by comma ','.
281  // Values separated by semicolon ';' are shown in the same column.
282  $icon = $this->externalTables[$table][$type]['icon'];
283  $addWhere = $this->externalTables[$table][$type]['addWhere'];
284  // Create listing
285  $out = $this->makeOrdinaryList($table, $id, $fList, $icon, $addWhere);
286  return $out;
287  }
288 
296  public function getTable_pages($id)
297  {
298  // Initializing:
299  $out = '';
300  // Select clause for pages:
301  $delClause = BackendUtility::deleteClause('pages') . ' AND ' . $this->getBackendUser()->getPagePermsClause(1);
302  // Select current page:
303  if (!$id) {
304  // The root has a pseudo record in pageinfo...
305  $row = $this->getPageLayoutController()->pageinfo;
306  } else {
307  $result = $this->getDatabase()->exec_SELECTquery('*', 'pages', 'uid=' . (int)$id . $delClause);
308  $row = $this->getDatabase()->sql_fetch_assoc($result);
309  BackendUtility::workspaceOL('pages', $row);
310  }
311  // If there was found a page:
312  if (is_array($row)) {
313  // Select which fields to show:
314  $pKey = $this->getPageLayoutController()->MOD_SETTINGS['pages'];
315  switch ($pKey) {
316  case 1:
317  $this->fieldArray = ['title', 'uid'] + array_keys($this->cleanTableNames());
318  break;
319  case 2:
320  $this->fieldArray = [
321  'title',
322  'uid',
323  'lastUpdated',
324  'newUntil',
325  'no_cache',
326  'cache_timeout',
327  'php_tree_stop',
328  'TSconfig',
329  'is_siteroot',
330  'fe_login_mode'
331  ];
332  break;
333  default:
334  $this->fieldArray = [
335  'title',
336  'uid',
337  'alias',
338  'starttime',
339  'endtime',
340  'fe_group',
341  'target',
342  'url',
343  'shortcut',
344  'shortcut_mode'
345  ];
346  }
347  // Getting select-depth:
348  $depth = (int)$this->getPageLayoutController()->MOD_SETTINGS['pages_levels'];
349  // Overriding a few things:
350  $this->no_noWrap = 0;
351  // Items
352  $this->eCounter = $this->firstElementNumber;
353  // Creating elements:
354  list($flag, $code) = $this->fwd_rwd_nav();
355  $out .= $code;
356  $editUids = [];
357  if ($flag) {
358  // Getting children:
359  $theRows = [];
360  $theRows = $this->pages_getTree($theRows, $row['uid'], $delClause . BackendUtility::versioningPlaceholderClause('pages'), '', $depth);
361  if ($this->getBackendUser()->doesUserHaveAccess($row, 2) && $row['uid'] > 0) {
362  $editUids[] = $row['uid'];
363  }
364  $out .= $this->pages_drawItem($row, $this->fieldArray);
365  // Traverse all pages selected:
366  foreach ($theRows as $sRow) {
367  if ($this->getBackendUser()->doesUserHaveAccess($sRow, 2)) {
368  $editUids[] = $sRow['uid'];
369  }
370  $out .= $this->pages_drawItem($sRow, $this->fieldArray);
371  }
372  $this->eCounter++;
373  }
374  // Header line is drawn
375  $theData = [];
376  $editIdList = implode(',', $editUids);
377  // Traverse fields (as set above) in order to create header values:
378  foreach ($this->fieldArray as $field) {
379  if ($editIdList && isset($GLOBALS['TCA']['pages']['columns'][$field]) && $field != 'uid' && !$this->pages_noEditColumns) {
380  $iTitle = sprintf(
381  $this->getLanguageService()->getLL('editThisColumn'),
382  rtrim(trim($this->getLanguageService()->sL(BackendUtility::getItemLabel('pages', $field))), ':')
383  );
384  $urlParameters = [
385  'edit' => [
386  'pages' => [
387  $editIdList => 'edit'
388  ]
389  ],
390  'columnsOnly' => $field,
391  'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
392  ];
393  $url = BackendUtility::getModuleUrl('record_edit', $urlParameters);
394  $eI = '<a href="' . htmlspecialchars($url)
395  . '" title="' . htmlspecialchars($iTitle) . '">'
396  . $this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)->render() . '</a>';
397  } else {
398  $eI = '';
399  }
400  switch ($field) {
401  case 'title':
402  $theData[$field] = '&nbsp;<strong>'
403  . $this->getLanguageService()->sL($GLOBALS['TCA']['pages']['columns'][$field]['label'])
404  . '</strong>' . $eI;
405  break;
406  case 'uid':
407  $theData[$field] = '&nbsp;<strong>ID:</strong>';
408  break;
409  default:
410  if (substr($field, 0, 6) == 'table_') {
411  $f2 = substr($field, 6);
412  if ($GLOBALS['TCA'][$f2]) {
413  $theData[$field] = '&nbsp;' . '<span title="' . $this->getLanguageService()->sL($GLOBALS['TCA'][$f2]['ctrl']['title'], true) . '">' . $this->iconFactory->getIconForRecord($f2, [], Icon::SIZE_SMALL)->render() . '</span>';
414  }
415  } else {
416  $theData[$field] = '&nbsp;&nbsp;<strong>'
417  . $this->getLanguageService()->sL($GLOBALS['TCA']['pages']['columns'][$field]['label'], true)
418  . '</strong>' . $eI;
419  }
420  }
421  }
422  // CSH:
423  $out = BackendUtility::cshItem($this->descrTable, ('func_' . $pKey), null, '<span class="btn btn-default btn-sm">|</span>') . '
424  <div class="table-fit">
425  <table class="table table-striped table-hover typo3-page-pages">' .
426  '<thead>' .
427  $this->addElement(1, '', $theData) .
428  '</thead>' .
429  '<tbody>' .
430  $out .
431  '</tbody>' .
432  '</table>
433  </div>';
434  }
435  return $out;
436  }
437 
444  public function getTable_tt_content($id)
445  {
446  $backendUser = $this->getBackendUser();
447  $this->pageinfo = BackendUtility::readPageAccess($this->id, '');
448  $this->initializeLanguages();
449  $this->initializeClipboard();
450  $pageTitleParamForAltDoc = '&recTitle=' . rawurlencode(BackendUtility::getRecordTitle('pages', BackendUtility::getRecordWSOL('pages', $id), true));
452  $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
453  $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/LayoutModule/DragDrop');
454  $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Modal');
455  $userCanEditPage = $this->ext_CALC_PERMS & Permission::PAGE_EDIT && !empty($this->id) && ($backendUser->isAdmin() || (int)$this->pageinfo['editlock'] === 0);
456  if ($this->tt_contentConfig['languageColsPointer'] > 0) {
457  $userCanEditPage = $this->getBackendUser()->check('tables_modify', 'pages_language_overlay');
458  }
459  if ($userCanEditPage) {
460  $languageOverlayId = 0;
461  $pageOverlayRecord = BackendUtility::getRecordsByField(
462  'pages_language_overlay',
463  'pid',
464  (int)$this->id,
465  'AND sys_language_uid=' . (int)$this->tt_contentConfig['sys_language_uid']
466  );
467  if (!empty($pageOverlayRecord[0]['uid'])) {
468  $languageOverlayId = $pageOverlayRecord[0]['uid'];
469  }
470  $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/PageActions', 'function(PageActions) {
471  PageActions.setPageId(' . (int)$this->id . ');
472  PageActions.setLanguageOverlayId(' . $languageOverlayId . ');
473  PageActions.initializePageTitleRenaming();
474  }');
475  }
476  // Get labels for CTypes and tt_content element fields in general:
477  $this->CType_labels = [];
478  foreach ($GLOBALS['TCA']['tt_content']['columns']['CType']['config']['items'] as $val) {
479  $this->CType_labels[$val[1]] = $this->getLanguageService()->sL($val[0]);
480  }
481  $this->itemLabels = [];
482  foreach ($GLOBALS['TCA']['tt_content']['columns'] as $name => $val) {
483  $this->itemLabels[$name] = $this->getLanguageService()->sL($val['label']);
484  }
485  $languageColumn = [];
486  $out = '';
487 
488  // Setting language list:
489  $langList = $this->tt_contentConfig['sys_language_uid'];
490  if ($this->tt_contentConfig['languageMode']) {
491  if ($this->tt_contentConfig['languageColsPointer']) {
492  $langList = '0,' . $this->tt_contentConfig['languageColsPointer'];
493  } else {
494  $langList = implode(',', array_keys($this->tt_contentConfig['languageCols']));
495  }
496  $languageColumn = [];
497  }
498  $langListArr = GeneralUtility::intExplode(',', $langList);
499  $defLanguageCount = [];
500  $defLangBinding = [];
501  // For each languages... :
502  // If not languageMode, then we'll only be through this once.
503  foreach ($langListArr as $lP) {
504  $lP = (int)$lP;
505 
506  if (!isset($this->contentElementCache[$lP])) {
507  $this->contentElementCache[$lP] = [];
508  }
509 
510  if (count($langListArr) === 1 || $lP === 0) {
511  $showLanguage = ' AND sys_language_uid IN (' . $lP . ',-1)';
512  } else {
513  $showLanguage = ' AND sys_language_uid=' . $lP;
514  }
515  $cList = explode(',', $this->tt_contentConfig['cols']);
516  $content = [];
517  $head = [];
518 
519  // Select content records per column
520  $contentRecordsPerColumn = $this->getContentRecordsPerColumn('table', $id, array_values($cList), $showLanguage);
521  // For each column, render the content into a variable:
522  foreach ($cList as $key) {
523  if (!isset($this->contentElementCache[$lP][$key])) {
524  $this->contentElementCache[$lP][$key] = [];
525  }
526 
527  if (!$lP) {
528  $defLanguageCount[$key] = [];
529  }
530  // Start wrapping div
531  $content[$key] .= '<div data-colpos="' . $key . '" data-language-uid="' . $lP . '" class="t3js-sortable t3js-sortable-lang t3js-sortable-lang-' . $lP . ' t3-page-ce-wrapper';
532  if (empty($contentRecordsPerColumn[$key])) {
533  $content[$key] .= ' t3-page-ce-empty';
534  }
535  $content[$key] .= '">';
536  // Add new content at the top most position
537  $link = '';
538  if ($this->getPageLayoutController()->contentIsNotLockedForEditors()
539  && (!$this->checkIfTranslationsExistInLanguage($contentRecordsPerColumn, $lP))
540  ) {
541  if ($this->option_newWizard) {
542  $urlParameters = [
543  'id' => $id,
544  'sys_language_uid' => $lP,
545  'colPos' => $key,
546  'uid_pid' => $id,
547  'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
548  ];
549  $tsConfig = BackendUtility::getModTSconfig($id, 'mod');
550  $moduleName = isset($tsConfig['properties']['newContentElementWizard.']['override'])
551  ? $tsConfig['properties']['newContentElementWizard.']['override']
552  : 'new_content_element';
553  $url = BackendUtility::getModuleUrl($moduleName, $urlParameters);
554  } else {
555  $urlParameters = [
556  'edit' => [
557  'tt_content' => [
558  $id => 'new'
559  ]
560  ],
561  'defVals' => [
562  'tt_content' => [
563  'colPos' => $key,
564  'sys_language_uid' => $lP
565  ]
566  ],
567  'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
568  ];
569  $url = BackendUtility::getModuleUrl('record_edit', $urlParameters);
570  }
571 
572  $link = '<a href="' . htmlspecialchars($url) . '" title="'
573  . $this->getLanguageService()->getLL('newContentElement', true) . '" class="btn btn-default btn-sm">'
574  . $this->iconFactory->getIcon('actions-document-new', Icon::SIZE_SMALL)->render()
575  . ' '
576  . $this->getLanguageService()->getLL('content', true) . '</a>';
577  }
578  if ($this->getBackendUser()->checkLanguageAccess($lP)) {
579  $content[$key] .= '
580  <div class="t3-page-ce t3js-page-ce" data-page="' . (int)$id . '" id="' . StringUtility::getUniqueId() . '">
581  <div class="t3js-page-new-ce t3-page-ce-wrapper-new-ce" id="colpos-' . $key . '-' . 'page-' . $id . '-' . StringUtility::getUniqueId() . '">'
582  . $link
583  . '</div>
584  <div class="t3-page-ce-dropzone-available t3js-page-ce-dropzone-available"></div>
585  </div>
586  ';
587  }
588  $editUidList = '';
589  if (!isset($contentRecordsPerColumn[$key]) || !is_array($contentRecordsPerColumn[$key])) {
590  $message = GeneralUtility::makeInstance(
591  FlashMessage::class,
592  $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:error.invalidBackendLayout'),
593  '',
595  );
596  $service = GeneralUtility::makeInstance(FlashMessageService::class);
597  $queue = $service->getMessageQueueByIdentifier();
598  $queue->addMessage($message);
599  } else {
600  $rowArr = $contentRecordsPerColumn[$key];
601  $this->generateTtContentDataArray($rowArr);
602 
603  foreach ((array)$rowArr as $rKey => $row) {
604  $this->contentElementCache[$lP][$key][$row['uid']] = $row;
605  if ($this->tt_contentConfig['languageMode']) {
606  $languageColumn[$key][$lP] = $head[$key] . $content[$key];
607  if (!$this->defLangBinding) {
608  $languageColumn[$key][$lP] .= $this->newLanguageButton(
609  $this->getNonTranslatedTTcontentUids($defLanguageCount[$key], $id, $lP),
610  $lP,
611  $key
612  );
613  }
614  }
615  if (is_array($row) && !VersionState::cast($row['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) {
616  $singleElementHTML = '';
617  if (!$lP && ($this->defLangBinding || $row['sys_language_uid'] != -1)) {
618  $defLanguageCount[$key][] = (isset($row['_ORIG_uid']) ? $row['_ORIG_uid'] : $row['uid']);
619  }
620  $editUidList .= $row['uid'] . ',';
621  $disableMoveAndNewButtons = $this->defLangBinding && $lP > 0;
622  if (!$this->tt_contentConfig['languageMode']) {
623  $singleElementHTML .= '<div class="t3-page-ce-dragitem" id="' . StringUtility::getUniqueId() . '">';
624  }
625  $singleElementHTML .= $this->tt_content_drawHeader(
626  $row,
627  $this->tt_contentConfig['showInfo'] ? 15 : 5,
628  $disableMoveAndNewButtons,
629  true,
630  $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT)
631  );
632  $innerContent = '<div ' . ($row['_ORIG_uid'] ? ' class="ver-element"' : '') . '>'
633  . $this->tt_content_drawItem($row) . '</div>';
634  $singleElementHTML .= '<div class="t3-page-ce-body-inner">' . $innerContent . '</div>'
635  . $this->tt_content_drawFooter($row);
636  $isDisabled = $this->isDisabled('tt_content', $row);
637  $statusHidden = $isDisabled ? ' t3-page-ce-hidden t3js-hidden-record' : '';
638  $displayNone = !$this->tt_contentConfig['showHidden'] && $isDisabled ? ' style="display: none;"' : '';
639  $highlightHeader = false;
640  if ($this->checkIfTranslationsExistInLanguage([], (int)$row['sys_language_uid']) && (int)$row['l18n_parent'] === 0) {
641  $highlightHeader = true;
642  }
643  $singleElementHTML = '<div class="t3-page-ce ' . ($highlightHeader ? 't3-page-ce-danger' : '') . ' t3js-page-ce t3js-page-ce-sortable ' . $statusHidden . '" id="element-tt_content-'
644  . $row['uid'] . '" data-table="tt_content" data-uid="' . $row['uid'] . '"' . $displayNone . '>' . $singleElementHTML . '</div>';
645 
646  if ($this->tt_contentConfig['languageMode']) {
647  $singleElementHTML .= '<div class="t3-page-ce t3js-page-ce">';
648  }
649  $singleElementHTML .= '<div class="t3js-page-new-ce t3-page-ce-wrapper-new-ce" id="colpos-' . $key . '-' . 'page-' . $id .
650  '-' . StringUtility::getUniqueId() . '">';
651  // Add icon "new content element below"
652  if (!$disableMoveAndNewButtons
653  && $this->getPageLayoutController()->contentIsNotLockedForEditors()
654  && $this->getBackendUser()->checkLanguageAccess($lP)
655  && (!$this->checkIfTranslationsExistInLanguage($contentRecordsPerColumn, $lP))
656  ) {
657  // New content element:
658  if ($this->option_newWizard) {
659  $urlParameters = [
660  'id' => $row['pid'],
661  'sys_language_uid' => $row['sys_language_uid'],
662  'colPos' => $row['colPos'],
663  'uid_pid' => -$row['uid'],
664  'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
665  ];
666  $tsConfig = BackendUtility::getModTSconfig($row['pid'], 'mod');
667  $moduleName = isset($tsConfig['properties']['newContentElementWizard.']['override'])
668  ? $tsConfig['properties']['newContentElementWizard.']['override']
669  : 'new_content_element';
670  $url = BackendUtility::getModuleUrl($moduleName, $urlParameters);
671  } else {
672  $urlParameters = [
673  'edit' => [
674  'tt_content' => [
675  -$row['uid'] => 'new'
676  ]
677  ],
678  'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
679  ];
680  $url = BackendUtility::getModuleUrl('record_edit', $urlParameters);
681  }
682  $singleElementHTML .= '
683  <a href="' . htmlspecialchars($url) . '" title="'
684  . $this->getLanguageService()->getLL('newContentElement', true) . '" class="btn btn-default btn-sm">'
685  . $this->iconFactory->getIcon('actions-document-new', Icon::SIZE_SMALL)->render()
686  . ' '
687  . $this->getLanguageService()->getLL('content', true) . '</a>
688  ';
689  }
690  $singleElementHTML .= '</div></div><div class="t3-page-ce-dropzone-available t3js-page-ce-dropzone-available"></div></div>';
691  if ($this->defLangBinding && $this->tt_contentConfig['languageMode']) {
692  $defLangBinding[$key][$lP][$row[$lP ? 'l18n_parent' : 'uid']] = $singleElementHTML;
693  } else {
694  $content[$key] .= $singleElementHTML;
695  }
696  } else {
697  unset($rowArr[$rKey]);
698  }
699  }
700  $content[$key] .= '</div>';
701  $colTitle = BackendUtility::getProcessedValue('tt_content', 'colPos', $key);
702  $tcaItems = GeneralUtility::callUserFunction(\TYPO3\CMS\Backend\View\BackendLayoutView::class . '->getColPosListItemsParsed', $id, $this);
703  foreach ($tcaItems as $item) {
704  if ($item[1] == $key) {
705  $colTitle = $this->getLanguageService()->sL($item[0]);
706  }
707  }
708 
709  $pasteP = ['colPos' => $key, 'sys_language_uid' => $lP];
710  $editParam = $this->doEdit && !empty($rowArr)
711  ? '&edit[tt_content][' . $editUidList . ']=edit' . $pageTitleParamForAltDoc
712  : '';
713  $head[$key] .= $this->tt_content_drawColHeader($colTitle, $editParam, '', $pasteP);
714  }
715  }
716  // For each column, fit the rendered content into a table cell:
717  $out = '';
718  if ($this->tt_contentConfig['languageMode']) {
719  // in language mode process the content elements, but only fill $languageColumn. output will be generated later
720  $sortedLanguageColumn = [];
721  foreach ($cList as $key) {
722  if (GeneralUtility::inList($this->tt_contentConfig['activeCols'], $key)) {
723  $languageColumn[$key][$lP] = $head[$key] . $content[$key];
724  if (!$this->defLangBinding) {
725  $languageColumn[$key][$lP] .= $this->newLanguageButton(
726  $this->getNonTranslatedTTcontentUids($defLanguageCount[$key], $id, $lP),
727  $lP,
728  $key
729  );
730  }
731  // We sort $languageColumn again according to $cList as it may contain data already from above.
732  $sortedLanguageColumn[$key] = $languageColumn[$key];
733  }
734  }
735  $languageColumn = $sortedLanguageColumn;
736  } else {
737  $backendLayout = $this->getBackendLayoutView()->getSelectedBackendLayout($this->id);
738  // GRID VIEW:
739  $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">';
740  // Add colgroups
741  $colCount = (int)$backendLayout['__config']['backend_layout.']['colCount'];
742  $rowCount = (int)$backendLayout['__config']['backend_layout.']['rowCount'];
743  $grid .= '<colgroup>';
744  for ($i = 0; $i < $colCount; $i++) {
745  $grid .= '<col />';
746  }
747  $grid .= '</colgroup>';
748  // Cycle through rows
749  for ($row = 1; $row <= $rowCount; $row++) {
750  $rowConfig = $backendLayout['__config']['backend_layout.']['rows.'][$row . '.'];
751  if (!isset($rowConfig)) {
752  continue;
753  }
754  $grid .= '<tr>';
755  for ($col = 1; $col <= $colCount; $col++) {
756  $columnConfig = $rowConfig['columns.'][$col . '.'];
757  if (!isset($columnConfig)) {
758  continue;
759  }
760  // Which tt_content colPos should be displayed inside this cell
761  $columnKey = (int)$columnConfig['colPos'];
762  // Render the grid cell
763  $colSpan = (int)$columnConfig['colspan'];
764  $rowSpan = (int)$columnConfig['rowspan'];
765  $grid .= '<td valign="top"' .
766  ($colSpan > 0 ? ' colspan="' . $colSpan . '"' : '') .
767  ($rowSpan > 0 ? ' rowspan="' . $rowSpan . '"' : '') .
768  ' 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 .
769  ((!isset($columnConfig['colPos']) || $columnConfig['colPos'] === '') ? ' t3-grid-cell-unassigned' : '') .
770  ((isset($columnConfig['colPos']) && $columnConfig['colPos'] !== '' && !$head[$columnKey]) || !GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos']) ? ' t3-grid-cell-restricted' : '') .
771  ($colSpan > 0 ? ' t3-gridCell-width' . $colSpan : '') .
772  ($rowSpan > 0 ? ' t3-gridCell-height' . $rowSpan : '') . '">';
773 
774  // Draw the pre-generated header with edit and new buttons if a colPos is assigned.
775  // If not, a new header without any buttons will be generated.
776  if (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== '' && $head[$columnKey]
777  && GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos'])
778  ) {
779  $grid .= $head[$columnKey] . $content[$columnKey];
780  } elseif (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== ''
781  && GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos'])
782  ) {
783  $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->getLL('noAccess'), '', '');
784  } elseif (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== ''
785  && !GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos'])
786  ) {
787  $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->sL($columnConfig['name']) .
788  ' (' . $this->getLanguageService()->getLL('noAccess') . ')', '', '');
789  } elseif (isset($columnConfig['name']) && $columnConfig['name'] !== '') {
790  $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->sL($columnConfig['name'])
791  . ' (' . $this->getLanguageService()->getLL('notAssigned') . ')', '', '');
792  } else {
793  $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->getLL('notAssigned'), '', '');
794  }
795 
796  $grid .= '</td>';
797  }
798  $grid .= '</tr>';
799  }
800  $out .= $grid . '</table></div>';
801  }
802  // CSH:
803  $out .= BackendUtility::cshItem($this->descrTable, 'columns_multi', null, '<span class="btn btn-default btn-sm">|</span>');
804  }
805  // If language mode, then make another presentation:
806  // Notice that THIS presentation will override the value of $out!
807  // But it needs the code above to execute since $languageColumn is filled with content we need!
808  if ($this->tt_contentConfig['languageMode']) {
809  // Get language selector:
810  $languageSelector = $this->languageSelector($id);
811  // Reset out - we will make new content here:
812  $out = '';
813  // Traverse languages found on the page and build up the table displaying them side by side:
814  $cCont = [];
815  $sCont = [];
816  foreach ($langListArr as $lP) {
817  // Header:
818  $lP = (int)$lP;
819  $cCont[$lP] = '
820  <td valign="top" class="t3-page-column" data-language-uid="' . $lP . '">
821  <h2>' . htmlspecialchars($this->tt_contentConfig['languageCols'][$lP]) . '</h2>
822  </td>';
823 
824  // "View page" icon is added:
825  $viewLink = '';
826  if (!VersionState::cast($this->getPageLayoutController()->pageinfo['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) {
827  $onClick = BackendUtility::viewOnClick($this->id, '', BackendUtility::BEgetRootLine($this->id), '', '', ('&L=' . $lP));
828  $viewLink = '<a href="#" class="btn btn-default btn-sm" onclick="' . htmlspecialchars($onClick) . '" title="' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.showPage', true) . '">' . $this->iconFactory->getIcon('actions-view', Icon::SIZE_SMALL)->render() . '</a>';
829  }
830  // Language overlay page header:
831  if ($lP) {
832  list($lpRecord) = BackendUtility::getRecordsByField('pages_language_overlay', 'pid', $id, 'AND sys_language_uid=' . $lP);
833  BackendUtility::workspaceOL('pages_language_overlay', $lpRecord);
834  $params = '&edit[pages_language_overlay][' . $lpRecord['uid'] . ']=edit&overrideVals[pages_language_overlay][sys_language_uid]=' . $lP;
836  $this->iconFactory->getIconForRecord('pages_language_overlay', $lpRecord, Icon::SIZE_SMALL)->render(),
837  'pages_language_overlay',
838  $lpRecord['uid']
839  );
840  $urlParameters = [
841  'edit' => [
842  'pages_language_overlay' => [
843  $lpRecord['uid'] => 'edit'
844  ]
845  ],
846  'overrideVals' => [
847  'pages_language_overlay' => [
848  'sys_language_uid' => $lP
849  ]
850  ],
851  'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
852  ];
853  $url = BackendUtility::getModuleUrl('record_edit', $urlParameters);
854  $editLink = ($this->getBackendUser()->check('tables_modify', 'pages_language_overlay')
855  ? '<a href="' . htmlspecialchars($url) . '" class="btn btn-default btn-sm"'
856  . ' title="' . $this->getLanguageService()->getLL('edit', true) . '">'
857  . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>'
858  : ''
859  );
860 
861  $lPLabel =
862  '<div class="btn-group">'
863  . $viewLink
864  . $editLink
865  . '</div>'
866  . ' ' . $recordIcon . ' ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($lpRecord['title'], 20));
867  } else {
868  $editLink = '';
869  $recordIcon = '';
870  if ($this->getBackendUser()->checkLanguageAccess(0)) {
872  $this->iconFactory->getIconForRecord('pages', $this->pageRecord,
873  Icon::SIZE_SMALL)->render(),
874  'pages',
875  $this->id
876  );
877  $urlParameters = [
878  'edit' => [
879  'pages' => [
880  $this->id => 'edit'
881  ]
882  ],
883  'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
884  ];
885  $url = BackendUtility::getModuleUrl('record_edit', $urlParameters);
886  $editLink = ($this->getBackendUser()->check('tables_modify', 'pages_language_overlay')
887  ? '<a href="' . htmlspecialchars($url) . '" class="btn btn-default btn-sm"'
888  . ' title="' . $this->getLanguageService()->getLL('edit', true) . '">'
889  . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>'
890  : ''
891  );
892  }
893 
894  $lPLabel =
895  '<div class="btn-group">'
896  . $viewLink
897  . $editLink
898  . '</div>'
899  . ' ' . $recordIcon . ' ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($this->pageRecord['title'], 20));
900  }
901  $sCont[$lP] = '
902  <td nowrap="nowrap" class="t3-page-column t3-page-lang-label">' . $lPLabel . '</td>';
903  }
904  // Add headers:
905  $out .= '<tr>' . implode($cCont) . '</tr>';
906  $out .= '<tr>' . implode($sCont) . '</tr>';
907  unset($cCont, $sCont);
908 
909  // Traverse previously built content for the columns:
910  foreach ($languageColumn as $cKey => $cCont) {
911  $out .= '<tr>';
912  foreach ($cCont as $languageId => $columnContent) {
913  $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>';
914  }
915  $out .= '</tr>';
916  if ($this->defLangBinding) {
917  // "defLangBinding" mode
918  foreach ($defLanguageCount[$cKey] as $defUid) {
919  $cCont = [];
920  foreach ($langListArr as $lP) {
921  $cCont[] = $defLangBinding[$cKey][$lP][$defUid] . $this->newLanguageButton(
922  $this->getNonTranslatedTTcontentUids([$defUid], $id, $lP),
923  $lP,
924  $cKey
925  );
926  }
927  $out .= '
928  <tr>
929  <td valign="top" class="t3-grid-cell">' . implode(('</td>' . '
930  <td valign="top" class="t3-grid-cell">'), $cCont) . '</td>
931  </tr>';
932  }
933  }
934  }
935  // Finally, wrap it all in a table and add the language selector on top of it:
936  $out = $languageSelector . '
937  <div class="t3-grid-container">
938  <table cellpadding="0" cellspacing="0" class="t3-page-columns t3-grid-table t3js-page-columns">
939  ' . $out . '
940  </table>
941  </div>';
942  // CSH:
943  $out .= BackendUtility::cshItem($this->descrTable, 'language_list', null, '<span class="btn btn-default btn-sm">|</span>');
944  }
945 
946  return $out;
947  }
948 
949  /**********************************
950  *
951  * Generic listing of items
952  *
953  **********************************/
964  public function makeOrdinaryList($table, $id, $fList, $icon = false, $addWhere = '')
965  {
966  // Initialize
967  $queryParts = $this->makeQueryArray($table, $id, $addWhere);
968  $this->setTotalItems($queryParts);
969  $dbCount = 0;
970  $result = false;
971  // Make query for records if there were any records found in the count operation
972  if ($this->totalItems) {
973  $result = $this->getDatabase()->exec_SELECT_queryArray($queryParts);
974  // Will return FALSE, if $result is invalid
975  $dbCount = $this->getDatabase()->sql_num_rows($result);
976  }
977  // If records were found, render the list
978  if (!$dbCount) {
979  return '';
980  }
981  // Set fields
982  $out = '';
983  $this->fieldArray = GeneralUtility::trimExplode(',', '__cmds__,' . $fList . ',__editIconLink__', true);
984  $theData = [];
985  $theData = $this->headerFields($this->fieldArray, $table, $theData);
986  // Title row
987  $localizedTableTitle = $this->getLanguageService()->sL($GLOBALS['TCA'][$table]['ctrl']['title'], true);
988  $out .= '<tr class="t3-row-header">' . '<th class="col-icon"></th>'
989  . '<th colspan="' . (count($theData) - 2) . '"><span class="c-table">'
990  . $localizedTableTitle . '</span> (' . $dbCount . ')</td>' . '<td class="col-icon"></td>'
991  . '</tr>';
992  // Column's titles
993  if ($this->doEdit) {
994  $urlParameters = [
995  'edit' => [
996  $table => [
997  $this->id => 'new'
998  ]
999  ],
1000  'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
1001  ];
1002  $url = BackendUtility::getModuleUrl('record_edit', $urlParameters);
1003  $theData['__cmds__'] = '<a href="' . htmlspecialchars($url) . '" '
1004  . 'title="' . $this->getLanguageService()->getLL('new', true) . '">'
1005  . $this->iconFactory->getIcon('actions-document-new', Icon::SIZE_SMALL)->render() . '</a>';
1006  }
1007  $out .= $this->addElement(1, '', $theData, ' class="c-headLine"', 15, '', 'th');
1008  // Render Items
1009  $this->eCounter = $this->firstElementNumber;
1010  while ($row = $this->getDatabase()->sql_fetch_assoc($result)) {
1011  BackendUtility::workspaceOL($table, $row);
1012  if (is_array($row)) {
1013  list($flag, $code) = $this->fwd_rwd_nav();
1014  $out .= $code;
1015  if ($flag) {
1016  $Nrow = [];
1017  // Setting icons links
1018  if ($icon) {
1019  $Nrow['__cmds__'] = $this->getIcon($table, $row);
1020  }
1021  // Get values:
1022  $Nrow = $this->dataFields($this->fieldArray, $table, $row, $Nrow);
1023  // Attach edit icon
1024  if ($this->doEdit && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT)) {
1025  $urlParameters = [
1026  'edit' => [
1027  $table => [
1028  $row['uid'] => 'edit'
1029  ]
1030  ],
1031  'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
1032  ];
1033  $url = BackendUtility::getModuleUrl('record_edit', $urlParameters);
1034  $Nrow['__editIconLink__'] = '<a href="' . htmlspecialchars($url)
1035  . '" title="' . $this->getLanguageService()->getLL('edit', true) . '">'
1036  . $this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)->render() . '</a>';
1037  } else {
1038  $Nrow['__editIconLink__'] = $this->noEditIcon();
1039  }
1040  $out .= $this->addElement(1, '', $Nrow);
1041  }
1042  $this->eCounter++;
1043  }
1044  }
1045  $this->getDatabase()->sql_free_result($result);
1046  // Wrap it all in a table:
1047  $out = '
1048  <!--
1049  Standard list of table "' . $table . '"
1050  -->
1051  <div class="table-fit"><table class="table table-striped">
1052  ' . $out . '
1053  </table></div>';
1054  return $out;
1055  }
1056 
1071  public function dataFields($fieldArr, $table, $row, $out = [])
1072  {
1073  // Check table validity
1074  if (!isset($GLOBALS['TCA'][$table])) {
1075  return $out;
1076  }
1077 
1078  $thumbsCol = $GLOBALS['TCA'][$table]['ctrl']['thumbnail'];
1079  // Traverse fields
1080  foreach ($fieldArr as $fieldName) {
1081  if ($GLOBALS['TCA'][$table]['columns'][$fieldName]) {
1082  // Each field has its own cell (if configured in TCA)
1083  // If the column is a thumbnail column:
1084  if ($fieldName == $thumbsCol) {
1085  $out[$fieldName] = $this->thumbCode($row, $table, $fieldName);
1086  } else {
1087  // ... otherwise just render the output:
1088  $out[$fieldName] = nl2br(htmlspecialchars(trim(GeneralUtility::fixed_lgd_cs(
1089  BackendUtility::getProcessedValue($table, $fieldName, $row[$fieldName], 0, 0, 0, $row['uid']),
1090  250
1091  ))));
1092  }
1093  } else {
1094  // Each field is separated by <br /> and shown in the same cell (If not a TCA field, then explode
1095  // the field name with ";" and check each value there as a TCA configured field)
1096  $theFields = explode(';', $fieldName);
1097  // Traverse fields, separated by ";" (displayed in a single cell).
1098  foreach ($theFields as $fName2) {
1099  if ($GLOBALS['TCA'][$table]['columns'][$fName2]) {
1100  $out[$fieldName] .= '<strong>' . $this->getLanguageService()->sL(
1101  $GLOBALS['TCA'][$table]['columns'][$fName2]['label'],
1102  true
1103  ) . '</strong>' . '&nbsp;&nbsp;' . htmlspecialchars(GeneralUtility::fixed_lgd_cs(
1104  BackendUtility::getProcessedValue($table, $fName2, $row[$fName2], 0, 0, 0, $row['uid']),
1105  25
1106  )) . '<br />';
1107  }
1108  }
1109  }
1110  // If no value, add a nbsp.
1111  if (!$out[$fieldName]) {
1112  $out[$fieldName] = '&nbsp;';
1113  }
1114  // Wrap in dimmed-span tags if record is "disabled"
1115  if ($this->isDisabled($table, $row)) {
1116  $out[$fieldName] = '<span class="text-muted">' . $out[$fieldName] . '</span>';
1117  }
1118  }
1119  return $out;
1120  }
1121 
1131  public function headerFields($fieldArr, $table, $out = [])
1132  {
1133  foreach ($fieldArr as $fieldName) {
1134  $ll = $this->getLanguageService()->sL($GLOBALS['TCA'][$table]['columns'][$fieldName]['label'], true);
1135  $out[$fieldName] = $ll ? $ll : '&nbsp;';
1136  }
1137  return $out;
1138  }
1139 
1150  protected function getContentRecordsPerColumn($table, $id, array $columns, $additionalWhereClause = '')
1151  {
1152  $columns = array_map('intval', $columns);
1153  $contentRecordsPerColumn = array_fill_keys($columns, []);
1154 
1155  $queryParts = $this->makeQueryArray('tt_content', $id, 'AND colPos IN (' . implode(',', $columns) . ')' . $additionalWhereClause);
1156  $result = $this->getDatabase()->exec_SELECT_queryArray($queryParts);
1157  // Traverse any selected elements and render their display code:
1158  $rowArr = $this->getResult($result);
1159 
1160  foreach ($rowArr as $record) {
1161  $columnValue = $record['colPos'];
1162  $contentRecordsPerColumn[$columnValue][] = $record;
1163  }
1164 
1165  return $contentRecordsPerColumn;
1166  }
1167 
1168  /**********************************
1169  *
1170  * Additional functions; Pages
1171  *
1172  **********************************/
1183  public function pages_getTree($theRows, $pid, $qWhere, $treeIcons, $depth)
1184  {
1185  $depth--;
1186  if ($depth >= 0) {
1187  $res = $this->getDatabase()->exec_SELECTquery('*', 'pages', 'pid=' . (int)$pid . $qWhere, '', 'sorting');
1188  $c = 0;
1189  $rc = $this->getDatabase()->sql_num_rows($res);
1190  while ($row = $this->getDatabase()->sql_fetch_assoc($res)) {
1191  BackendUtility::workspaceOL('pages', $row);
1192  if (is_array($row)) {
1193  $c++;
1194  $row['treeIcons'] = $treeIcons . '<span class="treeline-icon treeline-icon-join' . ($rc === $c ? 'bottom' : '') . '"></span>';
1195  $theRows[] = $row;
1196  // Get the branch
1197  $spaceOutIcons = '<span class="treeline-icon treeline-icon-' . ($rc === $c ? 'clear' : 'line') . '"></span>';
1198  $theRows = $this->pages_getTree($theRows, $row['uid'], $qWhere, $treeIcons . $spaceOutIcons, $row['php_tree_stop'] ? 0 : $depth);
1199  }
1200  }
1201  } else {
1202  $count = $this->getDatabase()->exec_SELECTcountRows('uid', 'pages', 'pid=' . (int)$pid . $qWhere);
1203  if ($count) {
1204  $this->plusPages[$pid] = $count;
1205  }
1206  }
1207  return $theRows;
1208  }
1209 
1217  public function pages_drawItem($row, $fieldArr)
1218  {
1219  // Initialization
1220  $theIcon = $this->getIcon('pages', $row);
1221  // Preparing and getting the data-array
1222  $theData = [];
1223  foreach ($fieldArr as $field) {
1224  switch ($field) {
1225  case 'title':
1226  $red = $this->plusPages[$row['uid']] ? '<span class="text-danger"><strong>+</strong></span>' : '';
1227  $pTitle = htmlspecialchars(BackendUtility::getProcessedValue('pages', $field, $row[$field], 20));
1228  if ($red) {
1229  $pTitle = '<a href="'
1230  . htmlspecialchars($this->script . ((strpos($this->script, '?') !== false) ? '&' : '?')
1231  . 'id=' . $row['uid']) . '">' . $pTitle . '</a>';
1232  }
1233  $theData[$field] = $row['treeIcons'] . $theIcon . $red . $pTitle . '&nbsp;&nbsp;';
1234  break;
1235  case 'php_tree_stop':
1236  // Intended fall through
1237  case 'TSconfig':
1238  $theData[$field] = $row[$field] ? '&nbsp;<strong>x</strong>' : '&nbsp;';
1239  break;
1240  case 'uid':
1241  if ($this->getBackendUser()->doesUserHaveAccess($row, 2) && $row['uid'] > 0) {
1242  $urlParameters = [
1243  'edit' => [
1244  'pages' => [
1245  $row['uid'] => 'edit'
1246  ]
1247  ],
1248  'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
1249  ];
1250  $url = BackendUtility::getModuleUrl('record_edit', $urlParameters);
1251  $eI = '<a href="' . htmlspecialchars($url)
1252  . '" title="' . $this->getLanguageService()->getLL('editThisPage', true) . '">'
1253  . $this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)->render() . '</a>';
1254  } else {
1255  $eI = '';
1256  }
1257  $theData[$field] = '<span align="right">' . $row['uid'] . $eI . '</span>';
1258  break;
1259  case 'shortcut':
1260  case 'shortcut_mode':
1261  if ((int)$row['doktype'] === \TYPO3\CMS\Frontend\Page\PageRepository::DOKTYPE_SHORTCUT) {
1262  $theData[$field] = $this->getPagesTableFieldValue($field, $row);
1263  }
1264  break;
1265  default:
1266  if (substr($field, 0, 6) == 'table_') {
1267  $f2 = substr($field, 6);
1268  if ($GLOBALS['TCA'][$f2]) {
1269  $c = $this->numberOfRecords($f2, $row['uid']);
1270  $theData[$field] = '&nbsp;&nbsp;' . ($c ? $c : '');
1271  }
1272  } else {
1273  $theData[$field] = $this->getPagesTableFieldValue($field, $row);
1274  }
1275  }
1276  }
1277  $this->addElement_tdParams['title'] = $row['_CSSCLASS'] ? ' class="' . $row['_CSSCLASS'] . '"' : '';
1278  return $this->addElement(1, '', $theData);
1279  }
1280 
1289  protected function getPagesTableFieldValue($field, array $row)
1290  {
1291  return '&nbsp;&nbsp;' . htmlspecialchars(BackendUtility::getProcessedValue('pages', $field, $row[$field]));
1292  }
1293 
1294  /**********************************
1295  *
1296  * Additional functions; Content Elements
1297  *
1298  **********************************/
1308  public function tt_content_drawColHeader($colName, $editParams, $newParams, array $pasteParams = null)
1309  {
1310  $iconsArr = [];
1311  // Create command links:
1312  if ($this->tt_contentConfig['showCommands']) {
1313  // Edit whole of column:
1314  if ($editParams && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT) && $this->getBackendUser()->checkLanguageAccess(0)) {
1315  $iconsArr['edit'] = '<a href="#" onclick="'
1316  . htmlspecialchars(BackendUtility::editOnClick($editParams)) . '" title="'
1317  . $this->getLanguageService()->getLL('editColumn', true) . '">'
1318  . $this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)->render() . '</a>';
1319  }
1320  if ($pasteParams) {
1321  $elFromTable = $this->clipboard->elFromTable('tt_content');
1322  if (!empty($elFromTable) && $this->getPageLayoutController()->pageIsNotLockedForEditors()) {
1323  $iconsArr['paste'] =
1324  '<a href="' . htmlspecialchars($this->clipboard->pasteUrl('tt_content', $this->id, true, $pasteParams)) . '"'
1325  . ' class="t3js-modal-trigger"'
1326  . ' data-severity="warning"'
1327  . ' data-title="' . $this->getLanguageService()->getLL('pasteIntoColumn', true) . '"'
1328  . ' data-content="' . htmlspecialchars($this->clipboard->confirmMsgText('pages', $this->pageRecord, 'into', $elFromTable, $colName)) . '"'
1329  . ' title="' . $this->getLanguageService()->getLL('pasteIntoColumn', true) . '">'
1330  . $this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL)->render()
1331  . '</a>';
1332  }
1333  }
1334  }
1335  $icons = '';
1336  if (!empty($iconsArr)) {
1337  $icons = '<div class="t3-page-column-header-icons">' . implode('', $iconsArr) . '</div>';
1338  }
1339  // Create header row:
1340  $out = '<div class="t3-page-column-header">
1341  ' . $icons . '
1342  <div class="t3-page-column-header-label">' . htmlspecialchars($colName) . '</div>
1343  </div>';
1344  return $out;
1345  }
1346 
1354  protected function tt_content_drawFooter(array $row)
1355  {
1356  $content = '';
1357  // Get processed values:
1358  $info = [];
1359  $this->getProcessedValue('tt_content', 'starttime,endtime,fe_group,spaceBefore,spaceAfter', $row, $info);
1360 
1361  // Content element annotation
1362  if (!empty($GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn'])) {
1363  $info[] = htmlspecialchars($row[$GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn']]);
1364  }
1365 
1366  // Call drawFooter hooks
1367  $drawFooterHooks = &$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawFooter'];
1368  if (is_array($drawFooterHooks)) {
1369  foreach ($drawFooterHooks as $hookClass) {
1370  $hookObject = GeneralUtility::getUserObj($hookClass);
1371  if (!$hookObject instanceof PageLayoutViewDrawFooterHookInterface) {
1372  throw new \UnexpectedValueException($hookClass . ' must implement interface ' . PageLayoutViewDrawFooterHookInterface::class, 1404378171);
1373  }
1374  $hookObject->preProcess($this, $info, $row);
1375  }
1376  }
1377 
1378  // Display info from records fields:
1379  if (!empty($info)) {
1380  $content = '<div class="t3-page-ce-info">
1381  ' . implode('<br>', $info) . '
1382  </div>';
1383  }
1384  // Wrap it
1385  if (!empty($content)) {
1386  $content = '<div class="t3-page-ce-footer">' . $content . '</div>';
1387  }
1388  return $content;
1389  }
1390 
1401  public function tt_content_drawHeader($row, $space = 0, $disableMoveAndNewButtons = false, $langMode = false, $dragDropEnabled = false)
1402  {
1403  $out = '';
1404  // If show info is set...;
1405  if ($this->tt_contentConfig['showInfo'] && $this->getBackendUser()->recordEditAccessInternals('tt_content', $row)) {
1406  // Render control panel for the element:
1407  if ($this->tt_contentConfig['showCommands'] && $this->doEdit) {
1408  // Edit content element:
1409  $urlParameters = [
1410  'edit' => [
1411  'tt_content' => [
1412  $this->tt_contentData['nextThree'][$row['uid']] => 'edit'
1413  ]
1414  ],
1415  'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI') . '#element-tt_content-' . $row['uid'],
1416  ];
1417  $url = BackendUtility::getModuleUrl('record_edit', $urlParameters) . '#element-tt_content-' . $row['uid'];
1418 
1419  $out .= '<a class="btn btn-default" href="' . htmlspecialchars($url)
1420  . '" title="' . htmlspecialchars($this->nextThree > 1
1421  ? sprintf($this->getLanguageService()->getLL('nextThree'), $this->nextThree)
1422  : $this->getLanguageService()->getLL('edit'))
1423  . '">' . $this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)->render() . '</a>';
1424  // Hide element:
1425  $hiddenField = $GLOBALS['TCA']['tt_content']['ctrl']['enablecolumns']['disabled'];
1426  if ($hiddenField && $GLOBALS['TCA']['tt_content']['columns'][$hiddenField]
1427  && (!$GLOBALS['TCA']['tt_content']['columns'][$hiddenField]['exclude']
1428  || $this->getBackendUser()->check('non_exclude_fields', 'tt_content:' . $hiddenField))
1429  ) {
1430  if ($row[$hiddenField]) {
1431  $value = 0;
1432  $label = 'unHide';
1433  } else {
1434  $value = 1;
1435  $label = 'hide';
1436  }
1437  $params = '&data[tt_content][' . ($row['_ORIG_uid'] ? $row['_ORIG_uid'] : $row['uid'])
1438  . '][' . $hiddenField . ']=' . $value;
1439  $out .= '<a class="btn btn-default" href="' . htmlspecialchars(BackendUtility::getLinkToDataHandlerAction($params))
1440  . '#element-tt_content-' . $row['uid'] . '" title="' . $this->getLanguageService()->getLL($label, true) . '">'
1441  . $this->iconFactory->getIcon('actions-edit-' . strtolower($label), Icon::SIZE_SMALL)->render() . '</a>';
1442  }
1443  // Delete
1444  $disableDeleteTS = $this->getBackendUser()->getTSConfig('options.disableDelete');
1445  $disableDelete = (bool) trim(isset($disableDeleteTS['properties']['tt_content']) ? $disableDeleteTS['properties']['tt_content'] : $disableDeleteTS['value']);
1446  if (!$disableDelete) {
1447  $params = '&cmd[tt_content][' . $row['uid'] . '][delete]=1';
1448  $confirm = $this->getLanguageService()->getLL('deleteWarning')
1449  . BackendUtility::translationCount('tt_content', $row['uid'], (' '
1450  . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.translationsOfRecord')));
1451  $out .= '<a class="btn btn-default t3js-modal-trigger" href="' . htmlspecialchars(BackendUtility::getLinkToDataHandlerAction($params)) . '"'
1452  . ' data-severity="warning"'
1453  . ' data-title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/locallang_alt_doc.xlf:label.confirm.delete_record.title')) . '"'
1454  . ' data-content="' . htmlspecialchars($confirm) . '" '
1455  . ' data-button-close-text="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/locallang_common.xlf:cancel')) . '"'
1456  . ' title="' . $this->getLanguageService()->getLL('deleteItem', true) . '">'
1457  . $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render() . '</a>';
1458  if ($out && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT)) {
1459  $out = '<div class="btn-group btn-group-sm" role="group">' . $out . '</div>';
1460  } else {
1461  $out = '';
1462  }
1463  }
1464  if (!$disableMoveAndNewButtons) {
1465  $moveButtonContent = '';
1466  $displayMoveButtons = false;
1467  // Move element up:
1468  if ($this->tt_contentData['prev'][$row['uid']]) {
1469  $params = '&cmd[tt_content][' . $row['uid'] . '][move]=' . $this->tt_contentData['prev'][$row['uid']];
1470  $moveButtonContent .= '<a class="btn btn-default" href="'
1472  . '" title="' . $this->getLanguageService()->getLL('moveUp', true) . '">'
1473  . $this->iconFactory->getIcon('actions-move-up', Icon::SIZE_SMALL)->render() . '</a>';
1474  if (!$dragDropEnabled) {
1475  $displayMoveButtons = true;
1476  }
1477  } else {
1478  $moveButtonContent .= '<span class="btn btn-default disabled">' . $this->iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
1479  }
1480  // Move element down:
1481  if ($this->tt_contentData['next'][$row['uid']]) {
1482  $params = '&cmd[tt_content][' . $row['uid'] . '][move]= ' . $this->tt_contentData['next'][$row['uid']];
1483  $moveButtonContent .= '<a class="btn btn-default" href="'
1485  . '" title="' . $this->getLanguageService()->getLL('moveDown', true) . '">'
1486  . $this->iconFactory->getIcon('actions-move-down', Icon::SIZE_SMALL)->render() . '</a>';
1487  if (!$dragDropEnabled) {
1488  $displayMoveButtons = true;
1489  }
1490  } else {
1491  $moveButtonContent .= '<span class="btn btn-default disabled">' . $this->iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
1492  }
1493  if ($displayMoveButtons) {
1494  $out .= '<div class="btn-group btn-group-sm" role="group">' . $moveButtonContent . '</div>';
1495  }
1496  }
1497  }
1498  }
1499  $allowDragAndDrop = $this->isDragAndDropAllowed($row);
1500  $additionalIcons = [];
1501  if ($row['sys_language_uid'] > 0 && $this->checkIfTranslationsExistInLanguage([], (int)$row['sys_language_uid'])) {
1502  $disabledClickMenuItems = 'new,move';
1503  $allowDragAndDrop = false;
1504  }
1505  $additionalIcons[] = $this->getIcon('tt_content', $row, $disabledClickMenuItems) . ' ';
1506  $additionalIcons[] = $langMode ? $this->languageFlag($row['sys_language_uid'], false) : '';
1507  // Get record locking status:
1508  if ($lockInfo = BackendUtility::isRecordLocked('tt_content', $row['uid'])) {
1509  $additionalIcons[] = '<a href="#" onclick="alert(' . GeneralUtility::quoteJSvalue($lockInfo['msg'])
1510  . ');return false;" title="' . htmlspecialchars($lockInfo['msg']) . '">'
1511  . $this->iconFactory->getIcon('status-warning-in-use', Icon::SIZE_SMALL)->render() . '</a>';
1512  }
1513  // Call stats information hook
1514  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['recStatInfoHooks'])) {
1515  $_params = ['tt_content', $row['uid'], &$row];
1516  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['recStatInfoHooks'] as $_funcRef) {
1517  $additionalIcons[] = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1518  }
1519  }
1520  // Wrap the whole header
1521  // NOTE: end-tag for <div class="t3-page-ce-body"> is in getTable_tt_content()
1522  return '<div class="t3-page-ce-header ' . ($allowDragAndDrop ? 't3-page-ce-header-draggable t3js-page-ce-draghandle' : '') . '">
1523  <div class="t3-page-ce-header-icons-left">' . implode('', $additionalIcons) . '</div>
1524  <div class="t3-page-ce-header-icons-right">' . ($out ? '<div class="btn-toolbar">' . $out . '</div>' : '') . '</div>
1525  </div>
1526  <div class="t3-page-ce-body">';
1527  }
1528 
1535  protected function isDragAndDropAllowed(array $row)
1536  {
1537  if ($this->getBackendUser()->user['admin']
1538  || ((int)$row['editlock'] === 0 && (int)$this->pageinfo['editlock'] === 0)
1539  && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT)
1540  && $this->getBackendUser()->checkAuthMode('tt_content', 'CType', $row['CType'], $GLOBALS['TYPO3_CONF_VARS']['BE']['explicitADmode'])
1541  ) {
1542  return true;
1543  }
1544  return false;
1545  }
1546 
1554  public function tt_content_drawItem($row)
1555  {
1556  $out = '';
1557  $outHeader = '';
1558  // Make header:
1559 
1560  if ($row['header']) {
1561  $infoArr = [];
1562  $this->getProcessedValue('tt_content', 'header_position,header_layout,header_link', $row, $infoArr);
1563  $hiddenHeaderNote = '';
1564  // If header layout is set to 'hidden', display an accordant note:
1565  if ($row['header_layout'] == 100) {
1566  $hiddenHeaderNote = ' <em>[' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.hidden', true) . ']</em>';
1567  }
1568  $outHeader = $row['date']
1569  ? htmlspecialchars($this->itemLabels['date'] . ' ' . BackendUtility::date($row['date'])) . '<br />'
1570  : '';
1571  $outHeader .= '<strong>' . $this->linkEditContent($this->renderText($row['header']), $row)
1572  . $hiddenHeaderNote . '</strong><br />';
1573  }
1574  // Make content:
1575  $infoArr = [];
1576  $drawItem = true;
1577  // Hook: Render an own preview of a record
1578  $drawItemHooks = &$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawItem'];
1579  if (is_array($drawItemHooks)) {
1580  foreach ($drawItemHooks as $hookClass) {
1581  $hookObject = GeneralUtility::getUserObj($hookClass);
1582  if (!$hookObject instanceof PageLayoutViewDrawItemHookInterface) {
1583  throw new \UnexpectedValueException($hookClass . ' must implement interface ' . PageLayoutViewDrawItemHookInterface::class, 1218547409);
1584  }
1585  $hookObject->preProcess($this, $drawItem, $outHeader, $out, $row);
1586  }
1587  }
1588 
1589  // If the previous hook did not render something,
1590  // then check if a Fluid-based preview template was defined for this CType
1591  // and render it via Fluid. Possible option:
1592  // mod.web_layout.tt_content.preview.media = EXT:site_mysite/Resources/Private/Templates/Preview/Media.html
1593  if ($drawItem) {
1594  $tsConfig = BackendUtility::getModTSconfig($row['pid'], 'mod.web_layout.tt_content.preview');
1595  if (!empty($tsConfig['properties'][$row['CType']])) {
1596  $fluidTemplateFile = $tsConfig['properties'][$row['CType']];
1597  $fluidTemplateFile = GeneralUtility::getFileAbsFileName($fluidTemplateFile);
1598  if ($fluidTemplateFile) {
1599  try {
1601  $view = GeneralUtility::makeInstance(StandaloneView::class);
1602  $view->setTemplatePathAndFilename($fluidTemplateFile);
1603  $view->assignMultiple($row);
1604  if (!empty($row['pi_flexform'])) {
1606  $flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
1607  $view->assign('pi_flexform_transformed', $flexFormService->convertFlexFormContentToArray($row['pi_flexform']));
1608  }
1609  $out = $view->render();
1610  $drawItem = false;
1611  } catch (\Exception $e) {
1612  // Catch any exception to avoid breaking the view
1613  }
1614  }
1615  }
1616  }
1617 
1618  // Draw preview of the item depending on its CType (if not disabled by previous hook):
1619  if ($drawItem) {
1620  switch ($row['CType']) {
1621  case 'header':
1622  if ($row['subheader']) {
1623  $out .= $this->linkEditContent($this->renderText($row['subheader']), $row) . '<br />';
1624  }
1625  break;
1626  case 'bullets':
1627  case 'table':
1628  if ($row['bodytext']) {
1629  $out .= $this->linkEditContent($this->renderText($row['bodytext']), $row) . '<br />';
1630  }
1631  break;
1632  case 'uploads':
1633  if ($row['media']) {
1634  $out .= $this->linkEditContent($this->getThumbCodeUnlinked($row, 'tt_content', 'media'), $row) . '<br />';
1635  }
1636  break;
1637  case 'menu':
1638  $contentType = $this->CType_labels[$row['CType']];
1639  $out .= $this->linkEditContent('<strong>' . htmlspecialchars($contentType) . '</strong>', $row) . '<br />';
1640  // Add Menu Type
1641  $menuTypeLabel = $this->getLanguageService()->sL(
1642  BackendUtility::getLabelFromItemListMerged($row['pid'], 'tt_content', 'menu_type', $row['menu_type'])
1643  );
1644  $menuTypeLabel = $menuTypeLabel ?: 'invalid menu type';
1645  $out .= $this->linkEditContent($menuTypeLabel, $row);
1646  if ($row['menu_type'] !== '2' && ($row['pages'] || $row['selected_categories'])) {
1647  // Show pages if menu type is not "Sitemap"
1648  $out .= ':' . $this->linkEditContent($this->generateListForCTypeMenu($row), $row) . '<br />';
1649  }
1650  break;
1651  case 'shortcut':
1652  if (!empty($row['records'])) {
1653  $shortcutContent = [];
1654  $recordList = explode(',', $row['records']);
1655  foreach ($recordList as $recordIdentifier) {
1656  $split = BackendUtility::splitTable_Uid($recordIdentifier);
1657  $tableName = empty($split[0]) ? 'tt_content' : $split[0];
1658  $shortcutRecord = BackendUtility::getRecord($tableName, $split[1]);
1659  if (is_array($shortcutRecord)) {
1660  $icon = $this->iconFactory->getIconForRecord($tableName, $shortcutRecord, Icon::SIZE_SMALL)->render();
1662  $icon,
1663  $tableName,
1664  $shortcutRecord['uid'],
1665  1,
1666  '',
1667  '+copy,info,edit,view'
1668  );
1669  $shortcutContent[] = $icon
1670  . htmlspecialchars(BackendUtility::getRecordTitle($tableName, $shortcutRecord));
1671  }
1672  }
1673  $out .= implode('<br />', $shortcutContent) . '<br />';
1674  }
1675  break;
1676  case 'list':
1677  $hookArr = [];
1678  $hookOut = '';
1679  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['list_type_Info'][$row['list_type']])) {
1680  $hookArr = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['list_type_Info'][$row['list_type']];
1681  } elseif (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['list_type_Info']['_DEFAULT'])) {
1682  $hookArr = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['list_type_Info']['_DEFAULT'];
1683  }
1684  if (!empty($hookArr)) {
1685  $_params = ['pObj' => &$this, 'row' => $row, 'infoArr' => $infoArr];
1686  foreach ($hookArr as $_funcRef) {
1687  $hookOut .= GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1688  }
1689  }
1690  if ((string)$hookOut !== '') {
1691  $out .= $hookOut;
1692  } elseif (!empty($row['list_type'])) {
1693  $label = BackendUtility::getLabelFromItemListMerged($row['pid'], 'tt_content', 'list_type', $row['list_type']);
1694  if (!empty($label)) {
1695  $out .= $this->linkEditContent('<strong>' . $this->getLanguageService()->sL($label, true) . '</strong>', $row) . '<br />';
1696  } else {
1697  $message = sprintf($this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue'), $row['list_type']);
1698  $out .= '<span class="label label-warning">' . htmlspecialchars($message) . '</span>';
1699  }
1700  } elseif (!empty($row['select_key'])) {
1701  $out .= $this->getLanguageService()->sL(BackendUtility::getItemLabel('tt_content', 'select_key'), true)
1702  . ' ' . htmlspecialchars($row['select_key']) . '<br />';
1703  } else {
1704  $out .= '<strong>' . $this->getLanguageService()->getLL('noPluginSelected') . '</strong>';
1705  }
1706  $out .= $this->getLanguageService()->sL(
1707  BackendUtility::getLabelFromItemlist('tt_content', 'pages', $row['pages']),
1708  true
1709  ) . '<br />';
1710  break;
1711  default:
1712  $contentType = $this->CType_labels[$row['CType']];
1713 
1714  if (isset($contentType)) {
1715  $out .= $this->linkEditContent('<strong>' . htmlspecialchars($contentType) . '</strong>', $row) . '<br />';
1716  if ($row['bodytext']) {
1717  $out .= $this->linkEditContent($this->renderText($row['bodytext']), $row) . '<br />';
1718  }
1719  if ($row['image']) {
1720  $out .= $this->linkEditContent($this->getThumbCodeUnlinked($row, 'tt_content', 'image'), $row) . '<br />';
1721  }
1722  } else {
1723  $message = sprintf(
1724  $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue'),
1725  $row['CType']
1726  );
1727  $out .= '<span class="label label-warning">' . htmlspecialchars($message) . '</span>';
1728  }
1729  }
1730  }
1731  // Wrap span-tags:
1732  $out = '
1733  <span class="exampleContent">' . $out . '</span>';
1734  // Add header:
1735  $out = $outHeader . $out;
1736  // Return values:
1737  if ($this->isDisabled('tt_content', $row)) {
1738  return '<span class="text-muted">' . $out . '</span>';
1739  } else {
1740  return $out;
1741  }
1742  }
1743 
1750  protected function generateListForCTypeMenu(array $row)
1751  {
1752  $table = 'pages';
1753  $field = 'pages';
1754  // get categories instead of pages
1755  if (strpos($row['menu_type'], 'categorized_') !== false) {
1756  $table = 'sys_category';
1757  $field = 'selected_categories';
1758  }
1759  if (trim($row[$field]) === '') {
1760  return '';
1761  }
1762  $content = '';
1763  $uidList = explode(',', $row[$field]);
1764  foreach ($uidList as $uid) {
1765  $uid = (int)$uid;
1766  $record = BackendUtility::getRecord($table, $uid, 'title');
1767  $content .= '<br>' . $record['title'] . ' (' . $uid . ')';
1768  }
1769  return $content;
1770  }
1771 
1782  public function getNonTranslatedTTcontentUids($defLanguageCount, $id, $lP)
1783  {
1784  if ($lP && !empty($defLanguageCount)) {
1785  // Select all translations here:
1786  $where = 'sys_language_uid=' . intval($lP) . ' AND l18n_parent IN ('
1787  . implode(',', $defLanguageCount) . ')'
1788  . BackendUtility::deleteClause('tt_content');
1789  $rowArr = $this->getDatabase()->exec_SELECTgetRows('*', 'tt_content', $where);
1790 
1791  // Flip uids:
1792  $defLanguageCount = array_flip($defLanguageCount);
1793  // Traverse any selected elements and unset original UID if any:
1794  foreach ($rowArr as $row) {
1795  BackendUtility::workspaceOL('tt_content', $row);
1796  unset($defLanguageCount[$row['l18n_parent']]);
1797  }
1798  // Flip again:
1799  $defLanguageCount = array_keys($defLanguageCount);
1800  }
1801  return $defLanguageCount;
1802  }
1803 
1812  public function newLanguageButton($defLanguageCount, $lP, $colPos = 0)
1813  {
1814  $lP = (int)$lP;
1815  if (!$this->doEdit || !$lP) {
1816  return '';
1817  }
1818  $theNewButton = '';
1819 
1820  $allowCopy = true;
1821  $allowTranslate = true;
1822  if (!empty($this->languageHasTranslationsCache[$lP])) {
1823  if (isset($this->languageHasTranslationsCache[$lP]['hasStandAloneContent'])) {
1824  $allowTranslate = false;
1825  }
1826  if (isset($this->languageHasTranslationsCache[$lP]['hasTranslations'])) {
1827  $allowCopy = false;
1828  }
1829  }
1830 
1831  if (isset($this->contentElementCache[$lP][$colPos]) && is_array($this->contentElementCache[$lP][$colPos])) {
1832  foreach ($this->contentElementCache[$lP][$colPos] as $record) {
1833  $key = array_search($record['l18n_parent'] ?: $record['t3_origuid'], $defLanguageCount);
1834  if ($key !== false) {
1835  unset($defLanguageCount[$key]);
1836  }
1837  }
1838  }
1839 
1840  if (!empty($defLanguageCount)) {
1841  $theNewButton =
1842  '<input'
1843  . ' class="btn btn-default t3js-localize"'
1844  . ' type="button"'
1845  . ' disabled'
1846  . ' value="' . htmlspecialchars($this->getLanguageService()->getLL('newPageContent_translate', true)) . '"'
1847  . ' data-has-elements="' . (int)!empty($this->contentElementCache[$lP][$colPos]) . '"'
1848  . ' data-allow-copy="' . (int)$allowCopy . '"'
1849  . ' data-allow-translate="' . (int)$allowTranslate . '"'
1850  . ' data-table="tt_content"'
1851  . ' data-page-id="' . (int)GeneralUtility::_GP('id') . '"'
1852  . ' data-language-id="' . $lP . '"'
1853  . ' data-language-name="' . htmlspecialchars($this->tt_contentConfig['languageCols'][$lP]) . '"'
1854  . ' data-colpos-id="' . $colPos . '"'
1855  . ' data-colpos-name="' . BackendUtility::getProcessedValue('tt_content', 'colPos', $colPos) . '"'
1856  . '/>';
1857  }
1858 
1859  return '<div class="t3-page-lang-copyce">' . $theNewButton . '</div>';
1860  }
1861 
1871  public function newContentElementOnClick($id, $colPos, $sys_language)
1872  {
1873  if ($this->option_newWizard) {
1874  $tsConfig = BackendUtility::getModTSconfig($id, 'mod');
1875  $moduleName = isset($tsConfig['properties']['newContentElementWizard.']['override'])
1876  ? $tsConfig['properties']['newContentElementWizard.']['override']
1877  : 'new_content_element';
1878  $onClick = 'window.location.href=' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl($moduleName) . '&id=' . $id . '&colPos=' . $colPos
1879  . '&sys_language_uid=' . $sys_language . '&uid_pid=' . $id
1880  . '&returnUrl=' . rawurlencode(GeneralUtility::getIndpEnv('REQUEST_URI'))) . ';';
1881  } else {
1882  $onClick = BackendUtility::editOnClick('&edit[tt_content][' . $id . ']=new&defVals[tt_content][colPos]='
1883  . $colPos . '&defVals[tt_content][sys_language_uid]=' . $sys_language);
1884  }
1885  return $onClick;
1886  }
1887 
1897  public function linkEditContent($str, $row)
1898  {
1899  if ($this->doEdit && $this->getBackendUser()->recordEditAccessInternals('tt_content', $row)) {
1900  $urlParameters = [
1901  'edit' => [
1902  'tt_content' => [
1903  $row['uid'] => 'edit'
1904  ]
1905  ],
1906  'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI') . '#element-tt_content-' . $row['uid']
1907  ];
1908  $url = BackendUtility::getModuleUrl('record_edit', $urlParameters);
1909  // Return link
1910  return '<a href="' . htmlspecialchars($url) . '" title="' . $this->getLanguageService()->getLL('edit', true) . '">' . $str . '</a>';
1911  } else {
1912  return $str;
1913  }
1914  }
1915 
1925  public function languageSelector($id)
1926  {
1927  if ($this->getBackendUser()->check('tables_modify', 'pages_language_overlay')) {
1928  // First, select all
1929  $res = $this->getPageLayoutController()->exec_languageQuery(0);
1930  $langSelItems = [];
1931  $langSelItems[0] = '
1932  <option value="0"></option>';
1933  while ($row = $this->getDatabase()->sql_fetch_assoc($res)) {
1934  if ($this->getBackendUser()->checkLanguageAccess($row['uid'])) {
1935  $langSelItems[$row['uid']] = '
1936  <option value="' . $row['uid'] . '">' . htmlspecialchars($row['title']) . '</option>';
1937  }
1938  }
1939  // Then, subtract the languages which are already on the page:
1940  $res = $this->getPageLayoutController()->exec_languageQuery($id);
1941  while ($row = $this->getDatabase()->sql_fetch_assoc($res)) {
1942  unset($langSelItems[$row['uid']]);
1943  }
1944  // Remove disallowed languages
1945  if (count($langSelItems) > 1
1946  && !$this->getBackendUser()->user['admin']
1947  && $this->getBackendUser()->groupData['allowed_languages'] !== ''
1948  ) {
1949  $allowed_languages = array_flip(explode(',', $this->getBackendUser()->groupData['allowed_languages']));
1950  if (!empty($allowed_languages)) {
1951  foreach ($langSelItems as $key => $value) {
1952  if (!isset($allowed_languages[$key]) && $key != 0) {
1953  unset($langSelItems[$key]);
1954  }
1955  }
1956  }
1957  }
1958  // Remove disabled languages
1960  $disableLanguages = isset($modSharedTSconfig['properties']['disableLanguages'])
1961  ? GeneralUtility::trimExplode(',', $modSharedTSconfig['properties']['disableLanguages'], true)
1962  : [];
1963  if (!empty($langSelItems) && !empty($disableLanguages)) {
1964  foreach ($disableLanguages as $language) {
1965  if ($language != 0 && isset($langSelItems[$language])) {
1966  unset($langSelItems[$language]);
1967  }
1968  }
1969  }
1970  // If any languages are left, make selector:
1971  if (count($langSelItems) > 1) {
1972  $url = BackendUtility::getModuleUrl('record_edit', [
1973  'edit[pages_language_overlay][' . $id . ']' => 'new',
1974  'overrideVals[pages_language_overlay][doktype]' => (int)$this->pageRecord['doktype'],
1975  'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
1976  ]);
1977  $onChangeContent = 'window.location.href=' . GeneralUtility::quoteJSvalue($url . '&overrideVals[pages_language_overlay][sys_language_uid]=') . '+this.options[this.selectedIndex].value';
1978  return '<div class="form-inline form-inline-spaced">'
1979  . '<div class="form-group">'
1980  . '<label for="createNewLanguage">'
1981  . $this->getLanguageService()->getLL('new_language', true)
1982  . '</label>'
1983  . '<select class="form-control input-sm" name="createNewLanguage" onchange="' . htmlspecialchars($onChangeContent) . '">'
1984  . implode('', $langSelItems)
1985  . '</select></div></div>';
1986  }
1987  }
1988  return '';
1989  }
1990 
1998  public function getResult($result, $table = 'tt_content')
1999  {
2000  $output = [];
2001  // Traverse the result:
2002  while ($row = $this->getDatabase()->sql_fetch_assoc($result)) {
2003  BackendUtility::workspaceOL($table, $row, -99, true);
2004  if ($row) {
2005  // Add the row to the array:
2006  $output[] = $row;
2007  }
2008  }
2009  $this->generateTtContentDataArray($output);
2010  // Return selected records
2011  return $output;
2012  }
2013 
2014  /********************************
2015  *
2016  * Various helper functions
2017  *
2018  ********************************/
2019 
2029  protected function initializeClipboard()
2030  {
2031  // Start clipboard
2032  $this->clipboard = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Clipboard\Clipboard::class);
2033 
2034  // Initialize - reads the clipboard content from the user session
2035  $this->clipboard->initializeClipboard();
2036 
2037  // This locks the clipboard to the Normal for this request.
2038  $this->clipboard->lockToNormal();
2039 
2040  // Clean up pad
2041  $this->clipboard->cleanCurrent();
2042 
2043  // Save the clipboard content
2044  $this->clipboard->endClipboard();
2045  }
2046 
2053  protected function generateTtContentDataArray(array $rowArray)
2054  {
2055  if (empty($this->tt_contentData)) {
2056  $this->tt_contentData = [
2057  'nextThree' => [],
2058  'next' => [],
2059  'prev' => [],
2060  ];
2061  }
2062  foreach ($rowArray as $key => $value) {
2063  // Create the list of the next three ids (for editing links...)
2064  for ($i = 0; $i < $this->nextThree; $i++) {
2065  if (isset($rowArray[$key - $i])
2066  && !GeneralUtility::inList($this->tt_contentData['nextThree'][$rowArray[$key - $i]['uid']], $value['uid'])
2067  ) {
2068  $this->tt_contentData['nextThree'][$rowArray[$key - $i]['uid']] .= $value['uid'] . ',';
2069  }
2070  }
2071 
2072  // Create information for next and previous content elements
2073  if (isset($rowArray[$key - 1])) {
2074  if (isset($rowArray[$key - 2])) {
2075  $this->tt_contentData['prev'][$value['uid']] = -$rowArray[$key - 2]['uid'];
2076  } else {
2077  $this->tt_contentData['prev'][$value['uid']] = $value['pid'];
2078  }
2079  $this->tt_contentData['next'][$rowArray[$key - 1]['uid']] = -$value['uid'];
2080  }
2081  }
2082  }
2083 
2091  public function numberOfRecords($table, $pid)
2092  {
2093  $count = 0;
2094  if ($GLOBALS['TCA'][$table]) {
2095  $where = 'pid=' . (int)$pid . BackendUtility::deleteClause($table) . BackendUtility::versioningPlaceholderClause($table);
2096  $count = $this->getDatabase()->exec_SELECTcountRows('uid', $table, $where);
2097  }
2098  return (int)$count;
2099  }
2100 
2107  public function renderText($input)
2108  {
2109  $input = strip_tags($input);
2110  $input = GeneralUtility::fixed_lgd_cs($input, 1500);
2111  return nl2br(htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8', false));
2112  }
2113 
2122  public function getIcon($table, $row, $enabledClickMenuItems = '')
2123  {
2124  // Initialization
2125  $toolTip = BackendUtility::getRecordToolTip($row, 'tt_content');
2126  $icon = '<span ' . $toolTip . '>' . $this->iconFactory->getIconForRecord($table, $row, Icon::SIZE_SMALL)->render() . '</span>';
2127  $this->counter++;
2128  // The icon with link
2129  if ($this->getBackendUser()->recordEditAccessInternals($table, $row)) {
2130  $icon = BackendUtility::wrapClickMenuOnIcon($icon, $table, $row['uid'], true, '', $enabledClickMenuItems);
2131  }
2132  return $icon;
2133  }
2134 
2145  public function getProcessedValue($table, $fieldList, array $row, array &$info)
2146  {
2147  // Splitting values from $fieldList
2148  $fieldArr = explode(',', $fieldList);
2149  // Traverse fields from $fieldList
2150  foreach ($fieldArr as $field) {
2151  if ($row[$field]) {
2152  $info[] = '<strong>' . htmlspecialchars($this->itemLabels[$field]) . '</strong> '
2153  . htmlspecialchars(BackendUtility::getProcessedValue($table, $field, $row[$field]));
2154  }
2155  }
2156  }
2157 
2165  public function isDisabled($table, $row)
2166  {
2167  $enableCols = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns'];
2168  return $enableCols['disabled'] && $row[$enableCols['disabled']]
2169  || $enableCols['starttime'] && $row[$enableCols['starttime']] > $GLOBALS['EXEC_TIME']
2170  || $enableCols['endtime'] && $row[$enableCols['endtime']] && $row[$enableCols['endtime']] < $GLOBALS['EXEC_TIME'];
2171  }
2172 
2181  public function noEditIcon($label = 'noEditItems')
2182  {
2183  $title = $this->getLanguageService()->getLL($label, true);
2184  return '<span title="' . $title . '">' . $this->iconFactory->getIcon('status-status-edit-read-only', Icon::SIZE_SMALL)->render() . '</span>';
2185  }
2186 
2194  protected function cleanTableNames()
2195  {
2196  // Get all table names:
2197  $tableNames = array_flip(array_keys($GLOBALS['TCA']));
2198  // Unset common names:
2199  unset($tableNames['pages']);
2200  unset($tableNames['static_template']);
2201  unset($tableNames['sys_filemounts']);
2202  unset($tableNames['sys_action']);
2203  unset($tableNames['sys_workflows']);
2204  unset($tableNames['be_users']);
2205  unset($tableNames['be_groups']);
2206  $allowedTableNames = [];
2207  // Traverse table names and set them in allowedTableNames array IF they can be read-accessed by the user.
2208  if (is_array($tableNames)) {
2209  foreach ($tableNames as $k => $v) {
2210  if (!$GLOBALS['TCA'][$k]['ctrl']['hideTable'] && $this->getBackendUser()->check('tables_select', $k)) {
2211  $allowedTableNames['table_' . $k] = $k;
2212  }
2213  }
2214  }
2215  return $allowedTableNames;
2216  }
2217 
2218  /*****************************************
2219  *
2220  * External renderings
2221  *
2222  *****************************************/
2223 
2232  public function getTableMenu($id)
2233  {
2234  // Initialize:
2235  $this->activeTables = [];
2236  $theTables = ['tt_content'];
2237  // External tables:
2238  if (is_array($this->externalTables)) {
2239  $theTables = array_unique(array_merge($theTables, array_keys($this->externalTables)));
2240  }
2241  $out = '';
2242  // Traverse tables to check:
2243  foreach ($theTables as $tName) {
2244  // Check access and whether the proper extensions are loaded:
2245  if ($this->getBackendUser()->check('tables_select', $tName)
2246  && (isset($this->externalTables[$tName])
2247  || $tName === 'fe_users' || $tName === 'tt_content'
2248  || \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded($tName)
2249  )
2250  ) {
2251  // Make query to count records from page:
2252  $c = $this->getDatabase()->exec_SELECTcountRows('uid', $tName, 'pid=' . (int)$id
2254  // If records were found (or if "tt_content" is the table...):
2255  if ($c || $tName === 'tt_content') {
2256  // Add row to menu:
2257  $out .= '
2258  <td><a href="#' . $tName . '" title="' . $this->getLanguageService()->sL($GLOBALS['TCA'][$tName]['ctrl']['title'], true) . '"></a>'
2259  . $this->iconFactory->getIconForRecord($tName, [], Icon::SIZE_SMALL)->render()
2260  . '</td>';
2261  // ... and to the internal array, activeTables we also add table icon and title (for use elsewhere)
2262  $title = $this->getLanguageService()->sL($GLOBALS['TCA'][$tName]['ctrl']['title'], true)
2263  . ': ' . $c . ' ' . $this->getLanguageService()->getLL('records', true);
2264  $this->activeTables[$tName] = '<span title="' . $title . '">'
2265  . $this->iconFactory->getIconForRecord($tName, [], Icon::SIZE_SMALL)->render()
2266  . '</span>'
2267  . '&nbsp;' . $this->getLanguageService()->sL($GLOBALS['TCA'][$tName]['ctrl']['title'], true);
2268  }
2269  }
2270  }
2271  // Wrap cells in table tags:
2272  $out = '
2273  <!--
2274  Menu of tables on the page (table menu)
2275  -->
2276  <table border="0" cellpadding="0" cellspacing="0" id="typo3-page-tblMenu">
2277  <tr>' . $out . '
2278  </tr>
2279  </table>';
2280  // Return the content:
2281  return $out;
2282  }
2283 
2292  public function getThumbCodeUnlinked($row, $table, $field)
2293  {
2294  return BackendUtility::thumbCode($row, $table, $field, '', '', null, 0, '', '', false);
2295  }
2296 
2305  protected function checkIfTranslationsExistInLanguage(array $contentElements, $language)
2306  {
2307  // If in default language, you may always create new entries
2308  // Also, you may override this strict behavior via user TS Config
2309  // If you do so, you're on your own and cannot rely on any support by the TYPO3 core
2310  // We jump out here since we don't need to do the expensive loop operations
2311  $allowInconsistentLanguageHandling = BackendUtility::getModTSconfig($this->id, 'mod.web_layout.allowInconsistentLanguageHandling');
2312  if ($language === 0 || $allowInconsistentLanguageHandling['value'] === '1') {
2313  return false;
2314  }
2318  if (!isset($this->languageHasTranslationsCache[$language])) {
2319  foreach ($contentElements as $columns) {
2320  foreach ($columns as $contentElement) {
2321  if ((int)$contentElement['l18n_parent'] === 0) {
2322  $this->languageHasTranslationsCache[$language]['hasStandAloneContent'] = true;
2323  }
2324  if ((int)$contentElement['l18n_parent'] > 0) {
2325  $this->languageHasTranslationsCache[$language]['hasTranslations'] = true;
2326  }
2327  }
2328  }
2329  // Check whether we have a mix of both
2330  if ($this->languageHasTranslationsCache[$language]['hasStandAloneContent']
2331  && $this->languageHasTranslationsCache[$language]['hasTranslations']
2332  ) {
2333  $message = GeneralUtility::makeInstance(
2334  FlashMessage::class,
2335  sprintf($this->getLanguageService()->getLL('staleTranslationWarning'), $this->languageIconTitles[$language]['title']),
2336  sprintf($this->getLanguageService()->getLL('staleTranslationWarningTitle'), $this->languageIconTitles[$language]['title']),
2338  );
2339  $service = GeneralUtility::makeInstance(FlashMessageService::class);
2340  $queue = $service->getMessageQueueByIdentifier();
2341  $queue->addMessage($message);
2342  }
2343  }
2344  if ($this->languageHasTranslationsCache[$language]['hasTranslations']) {
2345  return true;
2346  }
2347  return false;
2348  }
2349 
2353  protected function getBackendLayoutView()
2354  {
2355  return GeneralUtility::makeInstance(BackendLayoutView::class);
2356  }
2357 
2361  protected function getBackendUser()
2362  {
2363  return $GLOBALS['BE_USER'];
2364  }
2365 
2369  protected function getDatabase()
2370  {
2371  return $GLOBALS['TYPO3_DB'];
2372  }
2373 
2377  protected function getPageLayoutController()
2378  {
2379  return $GLOBALS['SOBE'];
2380  }
2381 }
pages_getTree($theRows, $pid, $qWhere, $treeIcons, $depth)
static translationCount($table, $ref, $msg='')
getResult($result, $table='tt_content')
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
static readPageAccess($id, $perms_clause)
static editOnClick($params, $_='', $requestUri='')
static getItemLabel($table, $col, $printAllWrap='')
getNonTranslatedTTcontentUids($defLanguageCount, $id, $lP)
newLanguageButton($defLanguageCount, $lP, $colPos=0)
getProcessedValue($table, $fieldList, array $row, array &$info)
static getRecordsByField($theTable, $theField, $theValue, $whereClause='', $groupBy='', $orderBy='', $limit='', $useDeleteClause=true)
checkIfTranslationsExistInLanguage(array $contentElements, $language)
getContentRecordsPerColumn($table, $id, array $columns, $additionalWhereClause='')
static getRecordToolTip(array $row, $table='pages')
static BEgetRootLine($uid, $clause='', $workspaceOL=false)
newContentElementOnClick($id, $colPos, $sys_language)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
makeOrdinaryList($table, $id, $fList, $icon=false, $addWhere='')
tt_content_drawColHeader($colName, $editParams, $newParams, array $pasteParams=null)
static workspaceOL($table, &$row, $wsid=-99, $unsetMovePointers=false)
static callUserFunction($funcName, &$params, &$ref, $checkPrefix='', $errorMode=0)
getIcon($table, $row, $enabledClickMenuItems='')
getThumbCodeUnlinked($row, $table, $field)
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 viewOnClick($pageUid, $backPath='', $rootLine=null, $anchorSection='', $alternativeUrl='', $additionalGetVars='', $switchFocus=true)
static fixed_lgd_cs($string, $chars, $appendString='...')
$uid
Definition: server.php:38
static getFileAbsFileName($filename, $onlyRelative=true, $relToTYPO3_mainDir=false)
static getLabelFromItemListMerged($pageId, $table, $column, $key)
static wrapClickMenuOnIcon( $content, $table, $uid=0, $listFrame=true, $addParams='', $enDisItems='', $returnTagParameters=false)
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']
static getRecordWSOL($table, $uid, $fields=' *', $where='', $useDeleteClause=true, $unsetMovePointers=false)
static getLinkToDataHandlerAction($parameters, $redirectUrl='')
static deleteClause($table, $tableAlias='')
static getLabelFromItemlist($table, $col, $key)