Page module
* @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
*/
class PageLayoutView implements LoggerAwareInterface
{
use LoggerAwareTrait;
/**
* If TRUE, users/groups are shown in the page info box.
*
* @var bool
*/
public $pI_showUser = false;
/**
* The number of successive records to edit when showing content elements.
*
* @var int
*/
public $nextThree = 3;
/**
* If TRUE, disables the edit-column icon for tt_content elements
*
* @var bool
*/
public $pages_noEditColumns = false;
/**
* If TRUE, new-wizards are linked to rather than the regular new-element list.
*
* @var bool
*/
public $option_newWizard = true;
/**
* If set to "1", will link a big button to content element wizard.
*
* @var int
*/
public $ext_function = 0;
/**
* If TRUE, elements will have edit icons (probably this is whether the user has permission to edit the page content). Set externally.
*
* @var bool
*/
public $doEdit = true;
/**
* Age prefixes for displaying times. May be set externally to localized values.
*
* @var string
*/
public $agePrefixes = ' min| hrs| days| yrs| min| hour| day| year';
/**
* Array of tables to be listed by the Web > Page module in addition to the default tables.
*
* @var array
*/
public $externalTables = [];
/**
* "Pseudo" Description -table name
*
* @var string
*/
public $descrTable;
/**
* If set TRUE, the language mode of tt_content elements will be rendered with hard binding between
* default language content elements and their translations!
*
* @var bool
*/
public $defLangBinding = false;
/**
* External, static: Configuration of tt_content element display:
*
* @var array
*/
public $tt_contentConfig = [
// Boolean: Display info-marks or not
'showInfo' => 1,
// Boolean: Display up/down arrows and edit icons for tt_content records
'showCommands' => 1,
'languageCols' => 0,
'languageMode' => 0,
'languageColsPointer' => 0,
'showHidden' => 1,
// Displays hidden records as well
'sys_language_uid' => 0,
// Which language
'cols' => '1,0,2,3',
'activeCols' => '1,0,2,3'
// Which columns can be accessed by current BE user
];
/**
* Contains icon/title of pages which are listed in the tables menu (see getTableMenu() function )
*
* @var array
*/
public $activeTables = [];
/**
* @var array
*/
public $tt_contentData = [
'nextThree' => [],
'prev' => [],
'next' => []
];
/**
* Used to store labels for CTypes for tt_content elements
*
* @var array
*/
public $CType_labels = [];
/**
* Used to store labels for the various fields in tt_content elements
*
* @var array
*/
public $itemLabels = [];
/**
* Indicates if all available fields for a user should be selected or not.
*
* @var int
*/
public $allFields = 0;
/**
* Number of records to show
*
* @var int
*/
public $showLimit = 0;
/**
* Shared module configuration, used by localization features
*
* @var array
*/
public $modSharedTSconfig = [];
/**
* Tables which should not get listed
*
* @var string
*/
public $hideTables = '';
/**
* Containing which fields to display in extended mode
*
* @var string[]
*/
public $displayFields;
/**
* Tables which should not list their translations
*
* @var string
*/
public $hideTranslations = '';
/**
* If set, csvList is outputted.
*
* @var bool
*/
public $csvOutput = false;
/**
* Cache for record path
*
* @var mixed[]
*/
public $recPath_cache = [];
/**
* Field, to sort list by
*
* @var string
*/
public $sortField;
/**
* default Max items shown per table in "multi-table mode", may be overridden by tables.php
*
* @var int
*/
public $itemsLimitPerTable = 20;
/**
* Page select permissions
*
* @var string
*/
public $perms_clause = '';
/**
* Page id
*
* @var int
*/
public $id;
/**
* Return URL
*
* @var string
*/
public $returnUrl = '';
/**
* Tablename if single-table mode
*
* @var string
*/
public $table = '';
/**
* Some permissions...
*
* @var int
*/
public $calcPerms = 0;
/**
* Mode for what happens when a user clicks the title of a record.
*
* @var string
*/
public $clickTitleMode = '';
/**
* Levels to search down.
*
* @var int
*/
public $searchLevels = '';
/**
* "LIMIT " in SQL...
*
* @var int
*/
public $iLimit = 0;
/**
* Set to the total number of items for a table when selecting.
*
* @var string
*/
public $totalItems = '';
/**
* TSconfig which overwrites TCA-Settings
*
* @var mixed[][]
*/
public $tableTSconfigOverTCA = [];
/**
* Loaded with page record with version overlay if any.
*
* @var string[]
*/
public $pageRecord = [];
/**
* Used for tracking duplicate values of fields
*
* @var string[]
*/
public $duplicateStack = [];
/**
* Fields to display for the current table
*
* @var string[]
*/
public $setFields = [];
/**
* Current script name
*
* @var string
*/
public $script = 'index.php';
/**
* If TRUE, records are listed only if a specific table is selected.
*
* @var bool
*/
public $listOnlyInSingleTableMode = false;
/**
* JavaScript code accumulation
*
* @var string
*/
public $JScode = '';
/**
* Pointer for browsing list
*
* @var int
*/
public $firstElementNumber = 0;
/**
* Counting the elements no matter what...
*
* @var int
*/
public $eCounter = 0;
/**
* Search string
*
* @var string
*/
public $searchString = '';
/**
* default Max items shown per table in "single-table mode", may be overridden by tables.php
*
* @var int
*/
public $itemsLimitSingleTable = 100;
/**
* Field, indicating to sort in reverse order.
*
* @var bool
*/
public $sortRev;
/**
* String, can contain the field name from a table which must have duplicate values marked.
*
* @var string
*/
public $duplicateField;
/**
* Specify a list of tables which are the only ones allowed to be displayed.
*
* @var string
*/
public $tableList = '';
/**
* Array of collapsed / uncollapsed tables in multi table view
*
* @var int[][]
*/
public $tablesCollapsed = [];
/**
* @var array[] Module configuration
*/
public $modTSconfig;
/**
* HTML output
*
* @var string
*/
public $HTMLcode = '';
/**
* Thumbnails on records containing files (pictures)
*
* @var bool
*/
public $thumbs = 0;
/**
* Used for tracking next/prev uids
*
* @var int[][]
*/
public $currentTable = [];
/**
* OBSOLETE - NOT USED ANYMORE. leftMargin
*
* @var int
*/
public $leftMargin = 0;
/**
* Decides the columns shown. Filled with values that refers to the keys of the data-array. $this->fieldArray[0] is the title column.
*
* @var array
*/
public $fieldArray = [];
/**
* Set to zero, if you don't want a left-margin with addElement function
*
* @var int
*/
public $setLMargin = 1;
/**
* Contains page translation languages
*
* @var array
*/
public $pageOverlays = [];
/**
* Counter increased for each element. Used to index elements for the JavaScript-code that transfers to the clipboard
*
* @var int
*/
public $counter = 0;
/**
* Contains sys language icons and titles
*
* @var array
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Use site languages instead.
*/
public $languageIconTitles = [];
/**
* Contains site languages for this page ID
*
* @var SiteLanguage[]
*/
protected $siteLanguages = [];
/**
* Script URL
*
* @var string
*/
public $thisScript = '';
/**
* If set this is
CSS-classname for odd columns in addElement. Used with db_layout / pages section
*
* @var string
*/
public $oddColumnsCssClass = '';
/**
* Not used in this class - but maybe extension classes...
* Max length of strings
*
* @var int
*/
public $fixedL = 30;
/**
* @var TranslationConfigurationProvider
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
*/
public $translateTools;
/**
* Keys are fieldnames and values are td-parameters to add in addElement(), please use $addElement_tdCSSClass for CSS-classes;
*
* @var array
*/
public $addElement_tdParams = [];
/**
* @var int
*/
public $no_noWrap = 0;
/**
* @var int
*/
public $showIcon = 1;
/**
* Keys are fieldnames and values are td-css-classes to add in addElement();
*
* @var array
*/
public $addElement_tdCssClass = [];
/**
* @var \TYPO3\CMS\Backend\Clipboard\Clipboard
*/
protected $clipboard;
/**
* User permissions
*
* @var int
*/
public $ext_CALC_PERMS;
/**
* Current ids page record
*
* @var array
*/
protected $pageinfo;
/**
* Caches the available languages in a colPos
*
* @var array
*/
protected $languagesInColumnCache = [];
/**
* Caches the amount of content elements as a matrix
*
* @var array
* @internal
*/
protected $contentElementCache = [];
/**
* @var IconFactory
*/
protected $iconFactory;
/**
* Stores whether a certain language has translations in it
*
* @var array
*/
protected $languageHasTranslationsCache = [];
/**
* @var LocalizationController
*/
protected $localizationController;
/**
* Override the page ids taken into account by getPageIdConstraint()
*
* @var array
*/
protected $overridePageIdList = [];
/**
* Override/add urlparameters in listUrl() method
*
* @var string[]
*/
protected $overrideUrlParameters = [];
/**
* Array with before/after setting for tables
* Structure:
* 'tableName' => [
* 'before' => ['A', ...]
* 'after' => []
* ]
* @var array[]
*/
protected $tableDisplayOrder = [];
/**
* Cache the number of references to a record
*
* @var array
*/
protected $referenceCount = [];
/**
* Construct to initialize class variables.
*/
public function __construct()
{
if (isset($GLOBALS['BE_USER']->uc['titleLen']) && $GLOBALS['BE_USER']->uc['titleLen'] > 0) {
$this->fixedL = $GLOBALS['BE_USER']->uc['titleLen'];
}
$this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
// @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Remove this instance along with the property.
$this->translateTools = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
$this->determineScriptUrl();
$this->localizationController = GeneralUtility::makeInstance(LocalizationController::class);
$pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
$pageRenderer->addInlineLanguageLabelFile('EXT:backend/Resources/Private/Language/locallang_layout.xlf');
$pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Tooltip');
$pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Localization');
}
/*****************************************
*
* Renderings
*
*****************************************/
/**
* Adds the code of a single table
*
* @param string $table Table name
* @param int $id Current page id
* @param string $fields
* @return string HTML for listing.
*/
public function getTable($table, $id, $fields = '')
{
if (isset($this->externalTables[$table])) {
return $this->getExternalTables($id, $table);
}
// Branch out based on table name:
switch ($table) {
case 'pages':
return $this->getTable_pages($id);
case 'tt_content':
return $this->getTable_tt_content($id);
default:
return '';
}
}
/**
* Renders an external table from page id
*
* @param int $id Page id
* @param string $table Name of the table
* @return string HTML for the listing
*/
public function getExternalTables($id, $table)
{
$this->pageinfo = BackendUtility::readPageAccess($id, '');
$type = $this->getPageLayoutController()->MOD_SETTINGS[$table];
if (!isset($type)) {
$type = 0;
}
// eg. "name;title;email;company,image"
$fList = $this->externalTables[$table][$type]['fList'];
// The columns are separeted by comma ','.
// Values separated by semicolon ';' are shown in the same column.
$icon = $this->externalTables[$table][$type]['icon'];
$addWhere = $this->externalTables[$table][$type]['addWhere'];
// Create listing
$out = $this->makeOrdinaryList($table, $id, $fList, $icon, $addWhere);
return $out;
}
/**
* Renders records from the pages table from page id
* (Used to get information about the page tree content by "Web>Info"!)
*
* @param int $id Page id
* @return string HTML for the listing
*/
public function getTable_pages($id)
{
// Initializing:
$out = '';
$lang = $this->getLanguageService();
// Select current page:
if (!$id) {
// The root has a pseudo record in pageinfo...
$row = $this->getPageLayoutController()->pageinfo;
} else {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('pages');
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class));
$row = $queryBuilder
->select('*')
->from('pages')
->where(
$queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)),
$this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW)
)
->execute()
->fetch();
BackendUtility::workspaceOL('pages', $row);
}
// If there was found a page:
if (is_array($row)) {
// Getting select-depth:
$depth = (int)$this->getPageLayoutController()->MOD_SETTINGS['pages_levels'];
// Overriding a few things:
$this->no_noWrap = 0;
// Items
$this->eCounter = $this->firstElementNumber;
// Creating elements:
list($flag, $code) = $this->fwd_rwd_nav();
$out .= $code;
$editUids = [];
if ($flag) {
// Getting children:
$theRows = $this->getPageRecordsRecursive($row['uid'], $depth);
if ($this->getBackendUser()->doesUserHaveAccess($row, 2) && $row['uid'] > 0) {
$editUids[] = $row['uid'];
}
$out .= $this->pages_drawItem($row, $this->fieldArray);
// Traverse all pages selected:
foreach ($theRows as $sRow) {
if ($this->getBackendUser()->doesUserHaveAccess($sRow, 2)) {
$editUids[] = $sRow['uid'];
}
$out .= $this->pages_drawItem($sRow, $this->fieldArray);
}
$this->eCounter++;
}
// Header line is drawn
$theData = [];
$editIdList = implode(',', $editUids);
// Traverse fields (as set above) in order to create header values:
foreach ($this->fieldArray as $field) {
if ($editIdList
&& isset($GLOBALS['TCA']['pages']['columns'][$field])
&& $field !== 'uid'
&& !$this->pages_noEditColumns
) {
$iTitle = sprintf(
$lang->getLL('editThisColumn'),
rtrim(trim($lang->sL(BackendUtility::getItemLabel('pages', $field))), ':')
);
$urlParameters = [
'edit' => [
'pages' => [
$editIdList => 'edit'
]
],
'columnsOnly' => $field,
'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
];
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
$url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
$eI = ''
. $this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)->render() . '';
} else {
$eI = '';
}
switch ($field) {
case 'title':
$theData[$field] = $eI . ' '
. $lang->sL($GLOBALS['TCA']['pages']['columns'][$field]['label'])
. '';
break;
case 'uid':
$theData[$field] = '';
break;
default:
if (strpos($field, 'table_') === 0) {
$f2 = substr($field, 6);
if ($GLOBALS['TCA'][$f2]) {
$theData[$field] = ' ' .
'' .
$this->iconFactory->getIconForRecord($f2, [], Icon::SIZE_SMALL)->render() .
'';
}
} else {
$theData[$field] = $eI . ' '
. htmlspecialchars($lang->sL($GLOBALS['TCA']['pages']['columns'][$field]['label']))
. '';
}
}
}
$out = '
';
// Which tt_content colPos should be displayed inside this cell
$columnKey = 'unused';
// Render the grid cell
$colSpan = (int)$backendLayout['__config']['backend_layout.']['colCount'];
$grid .= '
0 ? ' colspan="' . $colSpan . '"' : '') .
($rowSpan > 0 ? ' rowspan="' . $rowSpan . '"' : '') .
' data-colpos="unused" data-language-uid="' . $lP . '" class="t3js-page-lang-column-' . $lP . ' t3js-page-column t3-grid-cell t3-page-column t3-page-column-' . $columnKey .
($colSpan > 0 ? ' t3-gridCell-width' . $colSpan : '') . '">';
// Draw the pre-generated header with edit and new buttons if a colPos is assigned.
// If not, a new header without any buttons will be generated.
$grid .= $head[$columnKey] . $content[$columnKey];
$grid .= '
';
}
$out .= $grid . '
';
}
}
$elFromTable = $this->clipboard->elFromTable('tt_content');
if (!empty($elFromTable) && $this->isContentEditable()) {
$pasteItem = substr(key($elFromTable), 11);
$pasteRecord = BackendUtility::getRecord('tt_content', (int)$pasteItem);
$pasteTitle = $pasteRecord['header'] ? $pasteRecord['header'] : $pasteItem;
$copyMode = $this->clipboard->clipData['normal']['mode'] ? '-' . $this->clipboard->clipData['normal']['mode'] : '';
$addExtOnReadyCode = '
top.pasteIntoLinkTemplate = '
. $this->tt_content_drawPasteIcon($pasteItem, $pasteTitle, $copyMode, 't3js-paste-into', 'pasteIntoColumn')
. ';
top.pasteAfterLinkTemplate = '
. $this->tt_content_drawPasteIcon($pasteItem, $pasteTitle, $copyMode, 't3js-paste-after', 'pasteAfterRecord')
. ';';
} else {
$addExtOnReadyCode = '
top.pasteIntoLinkTemplate = \'\';
top.pasteAfterLinkTemplate = \'\';';
}
$pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
$pageRenderer->addJsInlineCode('pasteLinkTemplates', $addExtOnReadyCode);
// If language mode, then make another presentation:
// Notice that THIS presentation will override the value of $out!
// But it needs the code above to execute since $languageColumn is filled with content we need!
if ($this->tt_contentConfig['languageMode']) {
// Get language selector:
$languageSelector = $this->languageSelector($id);
// Reset out - we will make new content here:
$out = '';
// Traverse languages found on the page and build up the table displaying them side by side:
$cCont = [];
$sCont = [];
foreach ($langListArr as $lP) {
$languageMode = '';
$labelClass = 'info';
// Header:
$lP = (int)$lP;
// Determine language mode
if ($lP > 0 && isset($this->languageHasTranslationsCache[$lP]['mode'])) {
switch ($this->languageHasTranslationsCache[$lP]['mode']) {
case 'mixed':
$languageMode = $this->getLanguageService()->getLL('languageModeMixed');
$labelClass = 'danger';
break;
case 'connected':
$languageMode = $this->getLanguageService()->getLL('languageModeConnected');
break;
case 'free':
$languageMode = $this->getLanguageService()->getLL('languageModeFree');
break;
default:
// we'll let opcode optimize this intentionally empty case
}
}
$cCont[$lP] = '
';
}
}
}
// Finally, wrap it all in a table and add the language selector on top of it:
$out = $languageSelector . '
' . $out . '
';
}
return $out;
}
/**********************************
*
* Generic listing of items
*
**********************************/
/**
* Creates a standard list of elements from a table.
*
* @param string $table Table name
* @param int $id Page id.
* @param string $fList Comma list of fields to display
* @param bool $icon If TRUE, icon is shown
* @param string $addWhere Additional WHERE-clauses.
* @return string HTML table
*/
public function makeOrdinaryList($table, $id, $fList, $icon = false, $addWhere = '')
{
// Initialize
$addWhere = empty($addWhere) ? [] : [QueryHelper::stripLogicalOperatorPrefix($addWhere)];
$queryBuilder = $this->getQueryBuilder($table, $id, $addWhere);
$this->setTotalItems($table, $id, $addWhere);
$dbCount = 0;
$result = false;
// Make query for records if there were any records found in the count operation
if ($this->totalItems) {
$result = $queryBuilder->execute();
// Will return FALSE, if $result is invalid
$dbCount = $queryBuilder->count('uid')->execute()->fetchColumn(0);
}
// If records were found, render the list
if (!$dbCount) {
return '';
}
// Set fields
$out = '';
$this->fieldArray = GeneralUtility::trimExplode(',', '__cmds__,' . $fList . ',__editIconLink__', true);
$theData = [];
$theData = $this->headerFields($this->fieldArray, $table, $theData);
// Title row
$localizedTableTitle = htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$table]['ctrl']['title']));
$out .= '
';
return $out;
}
/**
* Adds content to all data fields in $out array
*
* Each field name in $fieldArr has a special feature which is that the field name can be specified as more field names.
* Eg. "field1,field2;field3".
* Field 2 and 3 will be shown in the same cell of the table separated by while field1 will have its own cell.
*
* @param array $fieldArr Array of fields to display
* @param string $table Table name
* @param array $row Record array
* @param array $out Array to which the data is added
* @return array $out array returned after processing.
* @see makeOrdinaryList()
*/
public function dataFields($fieldArr, $table, $row, $out = [])
{
// Check table validity
if (!isset($GLOBALS['TCA'][$table])) {
return $out;
}
$thumbsCol = $GLOBALS['TCA'][$table]['ctrl']['thumbnail'];
// Traverse fields
foreach ($fieldArr as $fieldName) {
if ($GLOBALS['TCA'][$table]['columns'][$fieldName]) {
// Each field has its own cell (if configured in TCA)
// If the column is a thumbnail column:
if ($fieldName == $thumbsCol) {
$out[$fieldName] = $this->thumbCode($row, $table, $fieldName);
} else {
// ... otherwise just render the output:
$out[$fieldName] = nl2br(htmlspecialchars(trim(GeneralUtility::fixed_lgd_cs(
BackendUtility::getProcessedValue($table, $fieldName, $row[$fieldName], 0, 0, 0, $row['uid']),
250
))));
}
} else {
// Each field is separated by and shown in the same cell (If not a TCA field, then explode
// the field name with ";" and check each value there as a TCA configured field)
$theFields = explode(';', $fieldName);
// Traverse fields, separated by ";" (displayed in a single cell).
foreach ($theFields as $fName2) {
if ($GLOBALS['TCA'][$table]['columns'][$fName2]) {
$out[$fieldName] .= '' . htmlspecialchars($this->getLanguageService()->sL(
$GLOBALS['TCA'][$table]['columns'][$fName2]['label']
)) . '' . ' ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs(
BackendUtility::getProcessedValue($table, $fName2, $row[$fName2], 0, 0, 0, $row['uid']),
25
)) . ' ';
}
}
}
// If no value, add a nbsp.
if (!$out[$fieldName]) {
$out[$fieldName] = ' ';
}
// Wrap in dimmed-span tags if record is "disabled"
if ($this->isDisabled($table, $row)) {
$out[$fieldName] = '' . $out[$fieldName] . '';
}
}
return $out;
}
/**
* Header fields made for the listing of records
*
* @param array $fieldArr Field names
* @param string $table The table name
* @param array $out Array to which the headers are added.
* @return array $out returned after addition of the header fields.
* @see makeOrdinaryList()
*/
public function headerFields($fieldArr, $table, $out = [])
{
foreach ($fieldArr as $fieldName) {
$ll = htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$table]['columns'][$fieldName]['label']));
$out[$fieldName] = $ll ? $ll : ' ';
}
return $out;
}
/**
* Gets content records per column.
* This is required for correct workspace overlays.
*
* @param string $table UNUSED (will always be queried from tt_content)
* @param int $id Page Id to be used (not used at all, but part of the API, see $this->pidSelect)
* @param array $columns colPos values to be considered to be shown
* @param string $additionalWhereClause Additional where clause for database select
* @return array Associative array for each column (colPos)
*/
protected function getContentRecordsPerColumn($table, $id, array $columns, $additionalWhereClause = '')
{
$contentRecordsPerColumn = array_fill_keys($columns, []);
$columns = array_flip($columns);
$queryBuilder = $this->getQueryBuilder(
'tt_content',
$id,
[
$additionalWhereClause
]
);
// Traverse any selected elements and render their display code:
$results = $this->getResult($queryBuilder->execute());
$unused = [];
$hookArray = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['record_is_used'] ?? [];
foreach ($results as $record) {
$used = isset($columns[$record['colPos']]);
foreach ($hookArray as $_funcRef) {
$_params = ['columns' => $columns, 'record' => $record, 'used' => $used];
$used = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
}
if ($used) {
$columnValue = (string)$record['colPos'];
$contentRecordsPerColumn[$columnValue][] = $record;
} else {
$unused[] = $record;
}
}
if (!empty($unused)) {
$contentRecordsPerColumn['unused'] = $unused;
}
return $contentRecordsPerColumn;
}
/**********************************
*
* Additional functions; Pages
*
**********************************/
/**
* Adds pages-rows to an array, selecting recursively in the page tree.
*
* @param int $pid Starting page id to select from
* @param string $iconPrefix Prefix for icon code.
* @param int $depth Depth (decreasing)
* @param array $rows Array which will accumulate page rows
* @return array $rows with added rows.
*/
protected function getPageRecordsRecursive(int $pid, int $depth, string $iconPrefix = '', array $rows = []): array
{
$depth--;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
$queryBuilder
->select('*')
->from('pages')
->where(
$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)),
$queryBuilder->expr()->eq('sys_language_uid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)),
$this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW)
);
if (!empty($GLOBALS['TCA']['pages']['ctrl']['sortby'])) {
$queryBuilder->orderBy($GLOBALS['TCA']['pages']['ctrl']['sortby']);
}
if ($depth >= 0) {
$result = $queryBuilder->execute();
$rowCount = $queryBuilder->count('uid')->execute()->fetchColumn(0);
$count = 0;
while ($row = $result->fetch()) {
BackendUtility::workspaceOL('pages', $row);
if (is_array($row)) {
$count++;
$row['treeIcons'] = $iconPrefix
. '';
$rows[] = $row;
// Get the branch
$spaceOutIcons = '';
$rows = $this->getPageRecordsRecursive(
$row['uid'],
$row['php_tree_stop'] ? 0 : $depth,
$iconPrefix . $spaceOutIcons,
$rows
);
}
}
}
return $rows;
}
/**
* Adds a list item for the pages-rendering
*
* @param array $row Record array
* @param array $fieldArr Field list
* @return string HTML for the item
*/
public function pages_drawItem($row, $fieldArr)
{
$userTsConfig = $this->getBackendUser()->getTSConfig();
// Initialization
$theIcon = $this->getIcon('pages', $row);
// Preparing and getting the data-array
$theData = [];
foreach ($fieldArr as $field) {
switch ($field) {
case 'title':
$showPageId = !empty($userTsConfig['options.']['pageTree.']['showPageIdWithTitle']);
$pTitle = htmlspecialchars(BackendUtility::getProcessedValue('pages', $field, $row[$field], 20));
$theData[$field] = $row['treeIcons'] . $theIcon . ($showPageId ? '[' . $row['uid'] . '] ' : '') . $pTitle;
break;
case 'php_tree_stop':
// Intended fall through
case 'TSconfig':
$theData[$field] = $row[$field] ? 'x' : ' ';
break;
case 'uid':
if ($this->getBackendUser()->doesUserHaveAccess($row, 2) && $row['uid'] > 0) {
$urlParameters = [
'edit' => [
'pages' => [
$row['uid'] => 'edit'
]
],
'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
];
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
$url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
$onClick = BackendUtility::viewOnClick($row['uid'], '', BackendUtility::BEgetRootLine($row['uid']));
$eI =
'' .
$this->iconFactory->getIcon('actions-view-page', Icon::SIZE_SMALL)->render() .
'';
$eI .=
'' .
$this->iconFactory->getIcon('actions-page-open', Icon::SIZE_SMALL)->render() .
'';
} else {
$eI = '';
}
$theData[$field] = '
' . $eI . '
';
break;
case 'shortcut':
case 'shortcut_mode':
if ((int)$row['doktype'] === \TYPO3\CMS\Frontend\Page\PageRepository::DOKTYPE_SHORTCUT) {
$theData[$field] = $this->getPagesTableFieldValue($field, $row);
}
break;
default:
if (strpos($field, 'table_') === 0) {
$f2 = substr($field, 6);
if ($GLOBALS['TCA'][$f2]) {
$c = $this->numberOfRecords($f2, $row['uid']);
$theData[$field] = ($c ? $c : '');
}
} else {
$theData[$field] = $this->getPagesTableFieldValue($field, $row);
}
}
}
$this->addElement_tdParams['title'] = $row['_CSSCLASS'] ? ' class="' . $row['_CSSCLASS'] . '"' : '';
return $this->addElement(1, '', $theData);
}
/**
* Returns the HTML code for rendering a field in the pages table.
* The row value is processed to a human readable form and the result is parsed through htmlspecialchars().
*
* @param string $field The name of the field of which the value should be rendered.
* @param array $row The pages table row as an associative array.
* @return string The rendered table field value.
*/
protected function getPagesTableFieldValue($field, array $row)
{
return htmlspecialchars(BackendUtility::getProcessedValue('pages', $field, $row[$field]));
}
/**********************************
*
* Additional functions; Content Elements
*
**********************************/
/**
* Draw header for a content element column:
*
* @param string $colName Column name
* @param string $editParams Edit params (Syntax: &edit[...] for FormEngine)
* @return string HTML table
*/
public function tt_content_drawColHeader($colName, $editParams = '')
{
$iconsArr = [];
// Create command links:
if ($this->tt_contentConfig['showCommands']) {
// Edit whole of column:
if ($editParams && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT) && $this->getBackendUser()->checkLanguageAccess(0)) {
$iconsArr['edit'] = ''
. $this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)->render() . '';
}
}
$icons = '';
if (!empty($iconsArr)) {
$icons = '
' . implode('', $iconsArr) . '
';
}
// Create header row:
$out = '
' . $icons . '
' . htmlspecialchars($colName) . '
';
return $out;
}
/**
* Draw a paste icon either for pasting into a column or for pasting after a record
*
* @param int $pasteItem ID of the item in the clipboard
* @param string $pasteTitle Title for the JS modal
* @param string $copyMode copy or cut
* @param string $cssClass CSS class to determine if pasting is done into column or after record
* @param string $title title attribute of the generated link
*
* @return string Generated HTML code with link and icon
*/
protected function tt_content_drawPasteIcon($pasteItem, $pasteTitle, $copyMode, $cssClass, $title)
{
$pasteIcon = json_encode(
' '
. $this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL)->render()
. ''
);
return $pasteIcon;
}
/**
* Draw the footer for a single tt_content element
*
* @param array $row Record array
* @return string HTML of the footer
* @throws \UnexpectedValueException
*/
protected function tt_content_drawFooter(array $row)
{
$content = '';
// Get processed values:
$info = [];
$this->getProcessedValue('tt_content', 'starttime,endtime,fe_group,space_before_class,space_after_class', $row, $info);
// Content element annotation
if (!empty($GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn']) && !empty($row[$GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn']])) {
$info[] = htmlspecialchars($row[$GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn']]);
}
// Call drawFooter hooks
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawFooter'] ?? [] as $className) {
$hookObject = GeneralUtility::makeInstance($className);
if (!$hookObject instanceof PageLayoutViewDrawFooterHookInterface) {
throw new \UnexpectedValueException($className . ' must implement interface ' . PageLayoutViewDrawFooterHookInterface::class, 1404378171);
}
$hookObject->preProcess($this, $info, $row);
}
// Display info from records fields:
if (!empty($info)) {
$content = '
' . implode(' ', $info) . '
';
}
// Wrap it
if (!empty($content)) {
$content = '';
}
return $content;
}
/**
* Draw the header for a single tt_content element
*
* @param array $row Record array
* @param int $space Amount of pixel space above the header. UNUSED
* @param bool $disableMoveAndNewButtons If set the buttons for creating new elements and moving up and down are not shown.
* @param bool $langMode If set, we are in language mode and flags will be shown for languages
* @param bool $dragDropEnabled If set the move button must be hidden
* @return string HTML table with the record header.
*/
public function tt_content_drawHeader($row, $space = 0, $disableMoveAndNewButtons = false, $langMode = false, $dragDropEnabled = false)
{
$backendUser = $this->getBackendUser();
$out = '';
// If show info is set...;
if ($this->tt_contentConfig['showInfo'] && $backendUser->recordEditAccessInternals('tt_content', $row)) {
// Render control panel for the element:
if ($this->tt_contentConfig['showCommands'] && $this->isContentEditable($row['sys_language_uid'])) {
// Edit content element:
$urlParameters = [
'edit' => [
'tt_content' => [
$this->tt_contentData['nextThree'][$row['uid']] => 'edit'
]
],
'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI') . '#element-tt_content-' . $row['uid'],
];
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
$url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters) . '#element-tt_content-' . $row['uid'];
$out .= '' . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '';
// Hide element:
$hiddenField = $GLOBALS['TCA']['tt_content']['ctrl']['enablecolumns']['disabled'];
if ($hiddenField && $GLOBALS['TCA']['tt_content']['columns'][$hiddenField]
&& (!$GLOBALS['TCA']['tt_content']['columns'][$hiddenField]['exclude']
|| $backendUser->check('non_exclude_fields', 'tt_content:' . $hiddenField))
) {
if ($row[$hiddenField]) {
$value = 0;
$label = 'unHide';
} else {
$value = 1;
$label = 'hide';
}
$params = '&data[tt_content][' . ($row['_ORIG_uid'] ? $row['_ORIG_uid'] : $row['uid'])
. '][' . $hiddenField . ']=' . $value;
$out .= ''
. $this->iconFactory->getIcon('actions-edit-' . strtolower($label), Icon::SIZE_SMALL)->render() . '';
}
// Delete
$disableDelete = (bool)\trim(
$backendUser->getTSConfig()['options.']['disableDelete.']['tt_content']
?? $backendUser->getTSConfig()['options.']['disableDelete']
?? '0'
);
if (!$disableDelete) {
$params = '&cmd[tt_content][' . $row['uid'] . '][delete]=1';
$refCountMsg = BackendUtility::referenceCount(
'tt_content',
$row['uid'],
' ' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.referencesToRecord'),
$this->getReferenceCount('tt_content', $row['uid'])
) . BackendUtility::translationCount(
'tt_content',
$row['uid'],
' ' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.translationsOfRecord')
);
$confirm = $this->getLanguageService()->getLL('deleteWarning')
. $refCountMsg;
$out .= ''
. $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render() . '';
if ($out && $backendUser->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT)) {
$out = '