enableFields(), is known to filter hidden-field, start- and endtime
* and fe_groups as well. But that is a job of the frontend, not the backend. If you need filtering
* on those fields as well in the backend you can use ->BEenableFields() though.
*
* @param string $table Table name present in $GLOBALS['TCA']
* @param string $tableAlias Table alias if any
* @return string WHERE clause for filtering out deleted records, eg " AND tablename.deleted=0
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, the DeletedRestriction functionality should be used instead.
*/
public static function deleteClause($table, $tableAlias = '')
{
trigger_error('BackendUtility::deleteClause() will be removed in TYPO3 v10.0. Add the delete statement directly in your SQL statement via the DeletedRestriction.', E_USER_DEPRECATED);
if (empty($GLOBALS['TCA'][$table]['ctrl']['delete'])) {
return '';
}
$expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable($table)
->expr();
return ' AND ' . $expressionBuilder->eq(
($tableAlias ?: $table) . '.' . $GLOBALS['TCA'][$table]['ctrl']['delete'],
0
);
}
/**
* Gets record with uid = $uid from $table
* You can set $field to a list of fields (default is '*')
* Additional WHERE clauses can be added by $where (fx. ' AND some_field = 1')
* Will automatically check if records has been deleted and if so, not return anything.
* $table must be found in $GLOBALS['TCA']
*
* @param string $table Table name present in $GLOBALS['TCA']
* @param int $uid UID of record
* @param string $fields List of fields to select
* @param string $where Additional WHERE clause, eg. ' AND some_field = 0'
* @param bool $useDeleteClause Use the deleteClause to check if a record is deleted (default TRUE)
* @return array|null Returns the row if found, otherwise NULL
*/
public static function getRecord($table, $uid, $fields = '*', $where = '', $useDeleteClause = true)
{
// Ensure we have a valid uid (not 0 and not NEWxxxx) and a valid TCA
if ((int)$uid && !empty($GLOBALS['TCA'][$table])) {
$queryBuilder = static::getQueryBuilderForTable($table);
// do not use enabled fields here
$queryBuilder->getRestrictions()->removeAll();
// should the delete clause be used
if ($useDeleteClause) {
$queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
}
// set table and where clause
$queryBuilder
->select(...GeneralUtility::trimExplode(',', $fields, true))
->from($table)
->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter((int)$uid, \PDO::PARAM_INT)));
// add custom where clause
if ($where) {
$queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($where));
}
$row = $queryBuilder->execute()->fetch();
if ($row) {
return $row;
}
}
return null;
}
/**
* Like getRecord(), but overlays workspace version if any.
*
* @param string $table Table name present in $GLOBALS['TCA']
* @param int $uid UID of record
* @param string $fields List of fields to select
* @param string $where Additional WHERE clause, eg. ' AND some_field = 0'
* @param bool $useDeleteClause Use the deleteClause to check if a record is deleted (default TRUE)
* @param bool $unsetMovePointers If TRUE the function does not return a "pointer" row for moved records in a workspace
* @return array Returns the row if found, otherwise nothing
*/
public static function getRecordWSOL(
$table,
$uid,
$fields = '*',
$where = '',
$useDeleteClause = true,
$unsetMovePointers = false
) {
if ($fields !== '*') {
$internalFields = GeneralUtility::uniqueList($fields . ',uid,pid');
$row = self::getRecord($table, $uid, $internalFields, $where, $useDeleteClause);
self::workspaceOL($table, $row, -99, $unsetMovePointers);
if (is_array($row)) {
foreach ($row as $key => $_) {
if (!GeneralUtility::inList($fields, $key) && $key[0] !== '_') {
unset($row[$key]);
}
}
}
} else {
$row = self::getRecord($table, $uid, $fields, $where, $useDeleteClause);
self::workspaceOL($table, $row, -99, $unsetMovePointers);
}
return $row;
}
/**
* Purges computed properties starting with underscore character ('_').
*
* @param array $record
* @return array
*/
public static function purgeComputedPropertiesFromRecord(array $record): array
{
return array_filter(
$record,
function (string $propertyName): bool {
return $propertyName[0] !== '_';
},
ARRAY_FILTER_USE_KEY
);
}
/**
* Purges computed property names starting with underscore character ('_').
*
* @param array $propertyNames
* @return array
*/
public static function purgeComputedPropertyNames(array $propertyNames): array
{
return array_filter(
$propertyNames,
function (string $propertyName): bool {
return $propertyName[0] !== '_';
}
);
}
/**
* Makes an backwards explode on the $str and returns an array with ($table, $uid).
* Example: tt_content_45 => ['tt_content', 45]
*
* @param string $str [tablename]_[uid] string to explode
* @return array
*/
public static function splitTable_Uid($str)
{
list($uid, $table) = explode('_', strrev($str), 2);
return [strrev($table), strrev($uid)];
}
/**
* Backend implementation of enableFields()
* Notice that "fe_groups" is not selected for - only disabled, starttime and endtime.
* Notice that deleted-fields are NOT filtered - you must ALSO call deleteClause in addition.
* $GLOBALS["SIM_ACCESS_TIME"] is used for date.
*
* @param string $table The table from which to return enableFields WHERE clause. Table name must have a 'ctrl' section in $GLOBALS['TCA'].
* @param bool $inv Means that the query will select all records NOT VISIBLE records (inverted selection)
* @return string WHERE clause part
*/
public static function BEenableFields($table, $inv = false)
{
$ctrl = $GLOBALS['TCA'][$table]['ctrl'];
$expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionForTable($table)
->getExpressionBuilder();
$query = $expressionBuilder->andX();
$invQuery = $expressionBuilder->orX();
if (is_array($ctrl)) {
if (is_array($ctrl['enablecolumns'])) {
if ($ctrl['enablecolumns']['disabled'] ?? false) {
$field = $table . '.' . $ctrl['enablecolumns']['disabled'];
$query->add($expressionBuilder->eq($field, 0));
$invQuery->add($expressionBuilder->neq($field, 0));
}
if ($ctrl['enablecolumns']['starttime'] ?? false) {
$field = $table . '.' . $ctrl['enablecolumns']['starttime'];
$query->add($expressionBuilder->lte($field, (int)$GLOBALS['SIM_ACCESS_TIME']));
$invQuery->add(
$expressionBuilder->andX(
$expressionBuilder->neq($field, 0),
$expressionBuilder->gt($field, (int)$GLOBALS['SIM_ACCESS_TIME'])
)
);
}
if ($ctrl['enablecolumns']['endtime'] ?? false) {
$field = $table . '.' . $ctrl['enablecolumns']['endtime'];
$query->add(
$expressionBuilder->orX(
$expressionBuilder->eq($field, 0),
$expressionBuilder->gt($field, (int)$GLOBALS['SIM_ACCESS_TIME'])
)
);
$invQuery->add(
$expressionBuilder->andX(
$expressionBuilder->neq($field, 0),
$expressionBuilder->lte($field, (int)$GLOBALS['SIM_ACCESS_TIME'])
)
);
}
}
}
if ($query->count() === 0) {
return '';
}
return ' AND ' . ($inv ? $invQuery : $query);
}
/**
* Fetches the localization for a given record.
*
* @param string $table Table name present in $GLOBALS['TCA']
* @param int $uid The uid of the record
* @param int $language The uid of the language record in sys_language
* @param string $andWhereClause Optional additional WHERE clause (default: '')
* @return mixed Multidimensional array with selected records; if none exist, FALSE is returned
*/
public static function getRecordLocalization($table, $uid, $language, $andWhereClause = '')
{
$recordLocalization = false;
if (self::isTableLocalizable($table)) {
$tcaCtrl = $GLOBALS['TCA'][$table]['ctrl'];
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable($table);
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
$queryBuilder->select('*')
->from($table)
->where(
$queryBuilder->expr()->eq(
$tcaCtrl['translationSource'] ?? $tcaCtrl['transOrigPointerField'],
$queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
),
$queryBuilder->expr()->eq(
$tcaCtrl['languageField'],
$queryBuilder->createNamedParameter((int)$language, \PDO::PARAM_INT)
)
)
->setMaxResults(1);
if ($andWhereClause) {
$queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($andWhereClause));
}
$recordLocalization = $queryBuilder->execute()->fetchAll();
}
return $recordLocalization;
}
/*******************************************
*
* Page tree, TCA related
*
*******************************************/
/**
* Returns what is called the 'RootLine'. That is an array with information about the page records from a page id
* ($uid) and back to the root.
* By default deleted pages are filtered.
* This RootLine will follow the tree all the way to the root. This is opposite to another kind of root line known
* from the frontend where the rootline stops when a root-template is found.
*
* @param int $uid Page id for which to create the root line.
* @param string $clause Clause can be used to select other criteria. It would typically be where-clauses that
* stops the process if we meet a page, the user has no reading access to.
* @param bool $workspaceOL If TRUE, version overlay is applied. This must be requested specifically because it is
* usually only wanted when the rootline is used for visual output while for permission checking you want the raw thing!
* @param string[] $additionalFields Additional Fields to select for rootline records
* @return array Root line array, all the way to the page tree root uid=0 (or as far as $clause allows!), including the page given as $uid
*/
public static function BEgetRootLine($uid, $clause = '', $workspaceOL = false, array $additionalFields = [])
{
$runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_runtime');
$beGetRootLineCache = $runtimeCache->get('backendUtilityBeGetRootLine') ?: [];
$output = [];
$pid = $uid;
$ident = $pid . '-' . $clause . '-' . $workspaceOL . ($additionalFields ? '-' . md5(implode(',', $additionalFields)) : '');
if (is_array($beGetRootLineCache[$ident] ?? false)) {
$output = $beGetRootLineCache[$ident];
} else {
$loopCheck = 100;
$theRowArray = [];
while ($uid != 0 && $loopCheck) {
$loopCheck--;
$row = self::getPageForRootline($uid, $clause, $workspaceOL, $additionalFields);
if (is_array($row)) {
$uid = $row['pid'];
$theRowArray[] = $row;
} else {
break;
}
}
$fields = [
'uid',
'pid',
'title',
'doktype',
'slug',
'tsconfig_includes',
'TSconfig',
'is_siteroot',
't3ver_oid',
't3ver_wsid',
't3ver_state',
't3ver_stage',
'backend_layout_next_level',
'hidden',
'starttime',
'endtime',
'fe_group',
'nav_hide',
'content_from_pid',
'module',
'extendToSubpages'
];
$fields = array_merge($fields, $additionalFields);
$rootPage = array_fill_keys($fields, null);
if ($uid == 0) {
$rootPage['uid'] = 0;
$theRowArray[] = $rootPage;
}
$c = count($theRowArray);
foreach ($theRowArray as $val) {
$c--;
$output[$c] = array_intersect_key($val, $rootPage);
if (isset($val['_ORIG_pid'])) {
$output[$c]['_ORIG_pid'] = $val['_ORIG_pid'];
}
}
$beGetRootLineCache[$ident] = $output;
$runtimeCache->set('backendUtilityBeGetRootLine', $beGetRootLineCache);
}
return $output;
}
/**
* Gets the cached page record for the rootline
*
* @param int $uid Page id for which to create the root line.
* @param string $clause Clause can be used to select other criteria. It would typically be where-clauses that stops the process if we meet a page, the user has no reading access to.
* @param bool $workspaceOL If TRUE, version overlay is applied. This must be requested specifically because it is usually only wanted when the rootline is used for visual output while for permission checking you want the raw thing!
* @param string[] $additionalFields AdditionalFields to fetch from the root line
* @return array Cached page record for the rootline
* @see BEgetRootLine
*/
protected static function getPageForRootline($uid, $clause, $workspaceOL, array $additionalFields = [])
{
$runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_runtime');
$pageForRootlineCache = $runtimeCache->get('backendUtilityPageForRootLine') ?: [];
$statementCacheIdent = md5($clause . ($additionalFields ? '-' . implode(',', $additionalFields) : ''));
$ident = $uid . '-' . $workspaceOL . '-' . $statementCacheIdent;
if (is_array($pageForRootlineCache[$ident] ?? false)) {
$row = $pageForRootlineCache[$ident];
} else {
$statement = $runtimeCache->get('getPageForRootlineStatement-' . $statementCacheIdent);
if (!$statement) {
$queryBuilder = static::getQueryBuilderForTable('pages');
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class));
$queryBuilder
->select(
'pid',
'uid',
'title',
'doktype',
'slug',
'tsconfig_includes',
'TSconfig',
'is_siteroot',
't3ver_oid',
't3ver_wsid',
't3ver_state',
't3ver_stage',
'backend_layout_next_level',
'hidden',
'starttime',
'endtime',
'fe_group',
'nav_hide',
'content_from_pid',
'module',
'extendToSubpages',
...$additionalFields
)
->from('pages')
->where(
$queryBuilder->expr()->eq('uid', $queryBuilder->createPositionalParameter($uid, \PDO::PARAM_INT)),
QueryHelper::stripLogicalOperatorPrefix($clause)
);
$statement = $queryBuilder->execute();
$runtimeCache->set('getPageForRootlineStatement-' . $statementCacheIdent, $statement);
} else {
$statement->bindValue(1, (int)$uid);
$statement->execute();
}
$row = $statement->fetch();
$statement->closeCursor();
if ($row) {
$newLocation = false;
if ($workspaceOL) {
self::workspaceOL('pages', $row);
if (is_array($row) && (int)$row['t3ver_state'] === VersionState::MOVE_POINTER) {
$newLocation = self::getMovePlaceholder('pages', $row['uid'], 'pid');
}
}
if (is_array($row)) {
if ($newLocation !== false) {
$row['pid'] = $newLocation['pid'];
} else {
self::fixVersioningPid('pages', $row);
}
$pageForRootlineCache[$ident] = $row;
$runtimeCache->set('backendUtilityPageForRootLine', $pageForRootlineCache);
}
}
}
return $row;
}
/**
* Opens the page tree to the specified page id
*
* @param int $pid Page id.
* @param bool $clearExpansion If set, then other open branches are closed.
*/
public static function openPageTree($pid, $clearExpansion)
{
$beUser = static::getBackendUserAuthentication();
// Get current expansion data:
if ($clearExpansion) {
$expandedPages = [];
} else {
$expandedPages = unserialize($beUser->uc['browseTrees']['browsePages'], ['allowed_classes' => false]);
}
// Get rootline:
$rL = self::BEgetRootLine($pid);
// First, find out what mount index to use (if more than one DB mount exists):
$mountIndex = 0;
$mountKeys = array_flip($beUser->returnWebmounts());
foreach ($rL as $rLDat) {
if (isset($mountKeys[$rLDat['uid']])) {
$mountIndex = $mountKeys[$rLDat['uid']];
break;
}
}
// Traverse rootline and open paths:
foreach ($rL as $rLDat) {
$expandedPages[$mountIndex][$rLDat['uid']] = 1;
}
// Write back:
$beUser->uc['browseTrees']['browsePages'] = serialize($expandedPages);
$beUser->writeUC();
}
/**
* Returns the path (visually) of a page $uid, fx. "/First page/Second page/Another subpage"
* Each part of the path will be limited to $titleLimit characters
* Deleted pages are filtered out.
*
* @param int $uid Page uid for which to create record path
* @param string $clause Clause is additional where clauses, eg.
* @param int $titleLimit Title limit
* @param int $fullTitleLimit Title limit of Full title (typ. set to 1000 or so)
* @return mixed Path of record (string) OR array with short/long title if $fullTitleLimit is set.
*/
public static function getRecordPath($uid, $clause, $titleLimit, $fullTitleLimit = 0)
{
if (!$titleLimit) {
$titleLimit = 1000;
}
$output = $fullOutput = '/';
$clause = trim($clause);
if ($clause !== '' && strpos($clause, 'AND') !== 0) {
$clause = 'AND ' . $clause;
}
$data = self::BEgetRootLine($uid, $clause, true);
foreach ($data as $record) {
if ($record['uid'] === 0) {
continue;
}
$output = '/' . GeneralUtility::fixed_lgd_cs(strip_tags($record['title']), $titleLimit) . $output;
if ($fullTitleLimit) {
$fullOutput = '/' . GeneralUtility::fixed_lgd_cs(strip_tags($record['title']), $fullTitleLimit) . $fullOutput;
}
}
if ($fullTitleLimit) {
return [$output, $fullOutput];
}
return $output;
}
/**
* Gets the original translation pointer table, which is always the same table
*
* @param string $table Name of the table
* @return string Pointer table (if any)
*/
public static function getOriginalTranslationTable($table)
{
trigger_error('Starting with TYPO3 v9, the translation table is always the same as the original table, because pages_language_overlay has been migrated into pages table.', E_USER_DEPRECATED);
return $table;
}
/**
* Determines whether a table is localizable and has the languageField and transOrigPointerField set in $GLOBALS['TCA'].
*
* @param string $table The table to check
* @return bool Whether a table is localizable
*/
public static function isTableLocalizable($table)
{
$isLocalizable = false;
if (isset($GLOBALS['TCA'][$table]['ctrl']) && is_array($GLOBALS['TCA'][$table]['ctrl'])) {
$tcaCtrl = $GLOBALS['TCA'][$table]['ctrl'];
$isLocalizable = isset($tcaCtrl['languageField']) && $tcaCtrl['languageField'] && isset($tcaCtrl['transOrigPointerField']) && $tcaCtrl['transOrigPointerField'];
}
return $isLocalizable;
}
/**
* Returns a page record (of page with $id) with an extra field "_thePath" set to the record path IF the WHERE clause, $perms_clause, selects the record. Thus is works as an access check that returns a page record if access was granted, otherwise not.
* If $id is zero a pseudo root-page with "_thePath" set is returned IF the current BE_USER is admin.
* In any case ->isInWebMount must return TRUE for the user (regardless of $perms_clause)
*
* @param int $id Page uid for which to check read-access
* @param string $perms_clause This is typically a value generated with static::getBackendUserAuthentication()->getPagePermsClause(1);
* @return array|bool Returns page record if OK, otherwise FALSE.
*/
public static function readPageAccess($id, $perms_clause)
{
if ((string)$id !== '') {
$id = (int)$id;
if (!$id) {
if (static::getBackendUserAuthentication()->isAdmin()) {
return ['_thePath' => '/'];
}
} else {
$pageinfo = self::getRecord('pages', $id, '*', $perms_clause);
if ($pageinfo['uid'] && static::getBackendUserAuthentication()->isInWebMount($pageinfo, $perms_clause)) {
self::workspaceOL('pages', $pageinfo);
if (is_array($pageinfo)) {
self::fixVersioningPid('pages', $pageinfo);
list($pageinfo['_thePath'], $pageinfo['_thePathFull']) = self::getRecordPath((int)$pageinfo['uid'], $perms_clause, 15, 1000);
return $pageinfo;
}
}
}
}
return false;
}
/**
* Returns the "types" configuration parsed into an array for the record, $rec, from table, $table
*
* @param string $table Table name (present in TCA)
* @param array $rec Record from $table
* @param bool $useFieldNameAsKey If $useFieldNameAsKey is set, then the fieldname is associative keys in the return array, otherwise just numeric keys.
* @return array|null
* @deprecated since TYPO3 v9.4 will be removed in TYPO3 v10.0.
*/
public static function getTCAtypes($table, $rec, $useFieldNameAsKey = false)
{
trigger_error('BackendUtility::getTCAtypes() will be removed in TYPO3 v10.0. The method is not in use anymore.', E_USER_DEPRECATED);
if (isset($GLOBALS['TCA'][$table])) {
// Get type value:
$fieldValue = self::getTCAtypeValue($table, $rec);
$cacheIdentifier = $table . '-type-' . $fieldValue . '-fnk-' . $useFieldNameAsKey;
// Fetch from first-level-cache if available
if (isset(self::$tcaTableTypeConfigurationCache[$cacheIdentifier])) {
return self::$tcaTableTypeConfigurationCache[$cacheIdentifier];
}
// Get typesConf
$typesConf = $GLOBALS['TCA'][$table]['types'][$fieldValue] ?? null;
// Get fields list and traverse it
$fieldList = explode(',', $typesConf['showitem']);
// Add subtype fields e.g. for a valid RTE transformation
// The RTE runs the DB -> RTE transformation only, if the RTE field is part of the getTCAtypes array
if (isset($typesConf['subtype_value_field'])) {
$subType = $rec[$typesConf['subtype_value_field']];
if (isset($typesConf['subtypes_addlist'][$subType])) {
$subFields = GeneralUtility::trimExplode(',', $typesConf['subtypes_addlist'][$subType], true);
$fieldList = array_merge($fieldList, $subFields);
}
}
// Add palette fields e.g. for a valid RTE transformation
$paletteFieldList = [];
foreach ($fieldList as $fieldData) {
$fieldDataArray = GeneralUtility::trimExplode(';', $fieldData);
// first two entries would be fieldname and altTitle, they are not used here.
$pPalette = $fieldDataArray[2] ?? null;
if ($pPalette
&& isset($GLOBALS['TCA'][$table]['palettes'][$pPalette])
&& is_array($GLOBALS['TCA'][$table]['palettes'][$pPalette])
&& isset($GLOBALS['TCA'][$table]['palettes'][$pPalette]['showitem'])
) {
$paletteFields = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$table]['palettes'][$pPalette]['showitem'], true);
foreach ($paletteFields as $paletteField) {
if ($paletteField !== '--linebreak--') {
$paletteFieldList[] = $paletteField;
}
}
}
}
$fieldList = array_merge($fieldList, $paletteFieldList);
$altFieldList = [];
// Traverse fields in types config and parse the configuration into a nice array:
foreach ($fieldList as $k => $v) {
$vArray = GeneralUtility::trimExplode(';', $v);
$fieldList[$k] = [
'field' => $vArray[0],
'title' => $vArray[1] ?? null,
'palette' => $vArray[2] ?? null,
'spec' => [],
'origString' => $v
];
if ($useFieldNameAsKey) {
$altFieldList[$fieldList[$k]['field']] = $fieldList[$k];
}
}
if ($useFieldNameAsKey) {
$fieldList = $altFieldList;
}
// Add to first-level-cache
self::$tcaTableTypeConfigurationCache[$cacheIdentifier] = $fieldList;
// Return array:
return $fieldList;
}
return null;
}
/**
* Returns the "type" value of $rec from $table which can be used to look up the correct "types" rendering section in $GLOBALS['TCA']
* If no "type" field is configured in the "ctrl"-section of the $GLOBALS['TCA'] for the table, zero is used.
* If zero is not an index in the "types" section of $GLOBALS['TCA'] for the table, then the $fieldValue returned will default to 1 (no matter if that is an index or not)
*
* Note: This method is very similar to the type determination of FormDataProvider/DatabaseRecordTypeValue,
* however, it has two differences:
* 1) The method in TCEForms also takes care of localization (which is difficult to do here as the whole infrastructure for language overlays is only in TCEforms).
* 2) The $row array looks different in TCEForms, as in there it's not the raw record but the prepared data from other providers is handled, which changes e.g. how "select"
* and "group" field values are stored, which makes different processing of the "foreign pointer field" type field variant necessary.
*
* @param string $table Table name present in TCA
* @param array $row Record from $table
* @throws \RuntimeException
* @return string Field value
*/
public static function getTCAtypeValue($table, $row)
{
$typeNum = 0;
if ($GLOBALS['TCA'][$table]) {
$field = $GLOBALS['TCA'][$table]['ctrl']['type'];
if (strpos($field, ':') !== false) {
list($pointerField, $foreignTableTypeField) = explode(':', $field);
// Get field value from database if field is not in the $row array
if (!isset($row[$pointerField])) {
$localRow = self::getRecord($table, $row['uid'], $pointerField);
$foreignUid = $localRow[$pointerField];
} else {
$foreignUid = $row[$pointerField];
}
if ($foreignUid) {
$fieldConfig = $GLOBALS['TCA'][$table]['columns'][$pointerField]['config'];
$relationType = $fieldConfig['type'];
if ($relationType === 'select') {
$foreignTable = $fieldConfig['foreign_table'];
} elseif ($relationType === 'group') {
$allowedTables = explode(',', $fieldConfig['allowed']);
$foreignTable = $allowedTables[0];
} else {
throw new \RuntimeException(
'TCA foreign field pointer fields are only allowed to be used with group or select field types.',
1325862240
);
}
$foreignRow = self::getRecord($foreignTable, $foreignUid, $foreignTableTypeField);
if ($foreignRow[$foreignTableTypeField]) {
$typeNum = $foreignRow[$foreignTableTypeField];
}
}
} else {
$typeNum = $row[$field];
}
// If that value is an empty string, set it to "0" (zero)
if (empty($typeNum)) {
$typeNum = 0;
}
}
// If current typeNum doesn't exist, set it to 0 (or to 1 for historical reasons, if 0 doesn't exist)
if (!isset($GLOBALS['TCA'][$table]['types'][$typeNum]) || !$GLOBALS['TCA'][$table]['types'][$typeNum]) {
$typeNum = isset($GLOBALS['TCA'][$table]['types']['0']) ? 0 : 1;
}
// Force to string. Necessary for eg '-1' to be recognized as a type value.
$typeNum = (string)$typeNum;
return $typeNum;
}
/*******************************************
*
* Caching related
*
*******************************************/
/**
* Stores $data in the 'cache_hash' cache with the hash key, $hash
* and visual/symbolic identification, $ident
*
* @param string $hash 32 bit hash string (eg. a md5 hash of a serialized array identifying the data being stored)
* @param mixed $data The data to store
* @param string $ident $ident is just a textual identification in order to inform about the content!
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, use the Caching Framework directly
*/
public static function storeHash($hash, $data, $ident)
{
trigger_error('BackendUtility::storeHash() will be removed in TYPO3 v10.0, use the Caching Framework directly.', E_USER_DEPRECATED);
$cacheManager = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Cache\CacheManager::class);
$cacheManager->getCache('cache_hash')->set($hash, $data, ['ident_' . $ident], 0);
}
/**
* Returns data stored for the hash string in the cache "cache_hash"
* Can be used to retrieved a cached value, array or object
*
* @param string $hash The hash-string which was used to store the data value
* @return mixed The "data" from the cache
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, use the Caching Framework directly
*/
public static function getHash($hash)
{
trigger_error('BackendUtility::getHash() will be removed in TYPO3 v10.0, use the Caching Framework directly.', E_USER_DEPRECATED);
$cacheManager = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Cache\CacheManager::class);
$cacheEntry = $cacheManager->getCache('cache_hash')->get($hash);
$hashContent = null;
if ($cacheEntry) {
$hashContent = $cacheEntry;
}
return $hashContent;
}
/*******************************************
*
* TypoScript related
*
*******************************************/
/**
* Returns the Page TSconfig for page with id, $id
*
* @param int $id Page uid for which to create Page TSconfig
* @param array $rootLine @deprecated
* @param bool $returnPartArray @deprecated
* @return array Page TSconfig
* @see \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser
*/
public static function getPagesTSconfig($id, $rootLine = null, $returnPartArray = false)
{
$id = (int)$id;
$cache = self::getRuntimeCache();
if ($returnPartArray === false
&& $rootLine === null
&& $cache->has('pagesTsConfigIdToHash' . $id)
) {
return $cache->get('pagesTsConfigHashToContent' . $cache->get('pagesTsConfigIdToHash' . $id));
}
$tsConfig = [];
// No custom rootline, so the results can be cached
if (!is_array($rootLine)) {
$rootLine = self::BEgetRootLine($id, '', true);
$useCacheForCurrentPageId = true;
} else {
trigger_error('Calling BackendUtility::getPagesTSconfig() with a custom rootline handed over as second argument will be removed in TYPO3 v10.0. Use TYPO3\CMS\Backend\Utility\BackendUtility::getRawPagesTSconfig() instead and parse PageTS yourself.', E_USER_DEPRECATED);
$useCacheForCurrentPageId = false;
}
$TSdataArray = static::getRawPagesTSconfig($id, $rootLine);
if ($returnPartArray) {
trigger_error('Calling BackendUtility::getPagesTSconfig() with a third parameter to return the unparsed array directly will be removed in TYPO3 v10.0. Use TYPO3\CMS\Backend\Utility\BackendUtility::getRawPagesTSconfig() instead.', E_USER_DEPRECATED);
return $TSdataArray;
}
// Parsing the page TS-Config
$pageTs = implode(LF . '[GLOBAL]' . LF, $TSdataArray);
/* @var \TYPO3\CMS\Backend\Configuration\TsConfigParser $parseObj */
$parseObj = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Configuration\TsConfigParser::class);
$res = $parseObj->parseTSconfig($pageTs, 'PAGES', $id, $rootLine);
if ($res) {
$tsConfig = $res['TSconfig'];
}
$cacheHash = $res['hash'];
// Get User TSconfig overlay, if no backend user is logged-in, this needs to be checked as well
if (static::getBackendUserAuthentication()) {
$userTSconfig = static::getBackendUserAuthentication()->getTSConfig() ?? [];
} else {
$userTSconfig = [];
}
$isCacheHashExtendedWithUserUid = false;
if (is_array($userTSconfig['page.'] ?? null)) {
ArrayUtility::mergeRecursiveWithOverrule($tsConfig, $userTSconfig['page.']);
$isCacheHashExtendedWithUserUid = true;
$cacheHash .= '_user' . static::getBackendUserAuthentication()->user['uid'];
}
// Overlay page "mod." ts with user ts in a special and deprecated way
if (is_array($userTSconfig['mod.'] ?? null)) {
// @deprecated This entire "if" and variable $isCacheHashExtendedWithUserUid can be deleted in TYPO3 v10.0
trigger_error(
'Overriding page TSconfig "mod." with user TSconfig "mod." is deprecated. Use user TSconfig "page.mod." instead.',
E_USER_DEPRECATED
);
if (!is_array($tsConfig['mod.'])) {
$tsConfig['mod.'] = [];
}
ArrayUtility::mergeRecursiveWithOverrule($tsConfig['mod.'], $userTSconfig['mod.']);
if (!$isCacheHashExtendedWithUserUid) {
$cacheHash .= '_user' . static::getBackendUserAuthentication()->user['uid'];
}
}
if ($useCacheForCurrentPageId) {
// Many pages end up with the same ts config. To reduce memory usage, the cache
// entries are a linked list: One or more pids point to content hashes which then
// contain the cached content.
$cache->set('pagesTsConfigHashToContent' . $cacheHash, $tsConfig, ['pagesTsConfig']);
$cache->set('pagesTsConfigIdToHash' . $id, $cacheHash, ['pagesTsConfig']);
}
return $tsConfig;
}
/**
* Returns the non-parsed Page TSconfig for page with id, $id
*
* @param int $id Page uid for which to create Page TSconfig
* @param array $rootLine If $rootLine is an array, that is used as rootline, otherwise rootline is just calculated
* @return array Non-parsed Page TSconfig
*/
public static function getRawPagesTSconfig($id, array $rootLine = null)
{
if (!is_array($rootLine)) {
$rootLine = self::BEgetRootLine($id, '', true);
}
// Order correctly
ksort($rootLine);
$tsDataArray = [];
// Setting default configuration
$tsDataArray['defaultPageTSconfig'] = $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'];
foreach ($rootLine as $k => $v) {
if (trim($v['tsconfig_includes'])) {
$includeTsConfigFileList = GeneralUtility::trimExplode(',', $v['tsconfig_includes'], true);
// Traversing list
foreach ($includeTsConfigFileList as $key => $includeTsConfigFile) {
if (strpos($includeTsConfigFile, 'EXT:') === 0) {
list($includeTsConfigFileExtensionKey, $includeTsConfigFilename) = explode(
'/',
substr($includeTsConfigFile, 4),
2
);
if ((string)$includeTsConfigFileExtensionKey !== ''
&& ExtensionManagementUtility::isLoaded($includeTsConfigFileExtensionKey)
&& (string)$includeTsConfigFilename !== ''
) {
$extensionPath = ExtensionManagementUtility::extPath($includeTsConfigFileExtensionKey);
$includeTsConfigFileAndPath = PathUtility::getCanonicalPath($extensionPath . $includeTsConfigFilename);
if (strpos($includeTsConfigFileAndPath, $extensionPath) === 0 && file_exists($includeTsConfigFileAndPath)) {
$tsDataArray['uid_' . $v['uid'] . '_static_' . $key] = file_get_contents($includeTsConfigFileAndPath);
}
}
}
}
}
$tsDataArray['uid_' . $v['uid']] = $v['TSconfig'];
}
$tsDataArray = static::emitGetPagesTSconfigPreIncludeSignal($tsDataArray, $id, $rootLine);
$tsDataArray = TypoScriptParser::checkIncludeLines_array($tsDataArray);
return $tsDataArray;
}
/*******************************************
*
* Users / Groups related
*
*******************************************/
/**
* Returns an array with be_users records of all user NOT DELETED sorted by their username
* Keys in the array is the be_users uid
*
* @param string $fields Optional $fields list (default: username,usergroup,usergroup_cached_list,uid) can be used to set the selected fields
* @param string $where Optional $where clause (fx. "AND username='pete'") can be used to limit query
* @return array
*/
public static function getUserNames($fields = 'username,usergroup,usergroup_cached_list,uid', $where = '')
{
return self::getRecordsSortedByTitle(
GeneralUtility::trimExplode(',', $fields, true),
'be_users',
'username',
'AND pid=0 ' . $where
);
}
/**
* Returns an array with be_groups records (title, uid) of all groups NOT DELETED sorted by their title
*
* @param string $fields Field list
* @param string $where WHERE clause
* @return array
*/
public static function getGroupNames($fields = 'title,uid', $where = '')
{
return self::getRecordsSortedByTitle(
GeneralUtility::trimExplode(',', $fields, true),
'be_groups',
'title',
'AND pid=0 ' . $where
);
}
/**
* Returns an array of all non-deleted records of a table sorted by a given title field.
* The value of the title field will be replaced by the return value
* of self::getRecordTitle() before the sorting is performed.
*
* @param array $fields Fields to select
* @param string $table Table name
* @param string $titleField Field that will contain the record title
* @param string $where Additional where clause
* @return array Array of sorted records
*/
protected static function getRecordsSortedByTitle(array $fields, $table, $titleField, $where = '')
{
$fieldsIndex = array_flip($fields);
// Make sure the titleField is amongst the fields when getting sorted
$fieldsIndex[$titleField] = 1;
$result = [];
$queryBuilder = static::getQueryBuilderForTable($table);
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class));
$res = $queryBuilder
->select('*')
->from($table)
->where(QueryHelper::stripLogicalOperatorPrefix($where))
->execute();
while ($record = $res->fetch()) {
// store the uid, because it might be unset if it's not among the requested $fields
$recordId = $record['uid'];
$record[$titleField] = self::getRecordTitle($table, $record);
// include only the requested fields in the result
$result[$recordId] = array_intersect_key($record, $fieldsIndex);
}
// sort records by $sortField. This is not done in the query because the title might have been overwritten by
// self::getRecordTitle();
return ArrayUtility::sortArraysByKey($result, $titleField);
}
/**
* Returns an array with be_groups records (like ->getGroupNames) but:
* - if the current BE_USER is admin, then all groups are returned, otherwise only groups that the current user is member of (usergroup_cached_list) will be returned.
*
* @param string $fields Field list; $fields specify the fields selected (default: title,uid)
* @return array
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
*/
public static function getListGroupNames($fields = 'title, uid')
{
trigger_error('BackendUtility::getListGroupNames() will be removed in TYPO3 v10.0, you should generate the list of backend user groups by yourself.', E_USER_DEPRECATED);
$beUser = static::getBackendUserAuthentication();
$exQ = '';
if (!$beUser->isAdmin()) {
$exQ = ' AND uid IN (' . ($beUser->user['usergroup_cached_list'] ?: 0) . ')';
}
return self::getGroupNames($fields, $exQ);
}
/**
* Returns the array $usernames with the names of all users NOT IN $groupArray changed to the uid (hides the usernames!).
* If $excludeBlindedFlag is set, then these records are unset from the array $usernames
* Takes $usernames (array made by \TYPO3\CMS\Backend\Utility\BackendUtility::getUserNames()) and a $groupArray (array with the groups a certain user is member of) as input
*
* @param array $usernames User names
* @param array $groupArray Group names
* @param bool $excludeBlindedFlag If $excludeBlindedFlag is set, then these records are unset from the array $usernames
* @return array User names, blinded
*/
public static function blindUserNames($usernames, $groupArray, $excludeBlindedFlag = false)
{
if (is_array($usernames) && is_array($groupArray)) {
foreach ($usernames as $uid => $row) {
$userN = $uid;
$set = 0;
if ($row['uid'] != static::getBackendUserAuthentication()->user['uid']) {
foreach ($groupArray as $v) {
if ($v && GeneralUtility::inList($row['usergroup_cached_list'], $v)) {
$userN = $row['username'];
$set = 1;
}
}
} else {
$userN = $row['username'];
$set = 1;
}
$usernames[$uid]['username'] = $userN;
if ($excludeBlindedFlag && !$set) {
unset($usernames[$uid]);
}
}
}
return $usernames;
}
/**
* Corresponds to blindUserNames but works for groups instead
*
* @param array $groups Group names
* @param array $groupArray Group names (reference)
* @param bool $excludeBlindedFlag If $excludeBlindedFlag is set, then these records are unset from the array $usernames
* @return array
*/
public static function blindGroupNames($groups, $groupArray, $excludeBlindedFlag = false)
{
if (is_array($groups) && is_array($groupArray)) {
foreach ($groups as $uid => $row) {
$groupN = $uid;
$set = 0;
if (in_array($uid, $groupArray, false)) {
$groupN = $row['title'];
$set = 1;
}
$groups[$uid]['title'] = $groupN;
if ($excludeBlindedFlag && !$set) {
unset($groups[$uid]);
}
}
}
return $groups;
}
/*******************************************
*
* Output related
*
*******************************************/
/**
* Returns the difference in days between input $tstamp and $EXEC_TIME
*
* @param int $tstamp Time stamp, seconds
* @return int
*/
public static function daysUntil($tstamp)
{
$delta_t = $tstamp - $GLOBALS['EXEC_TIME'];
return ceil($delta_t / (3600 * 24));
}
/**
* Returns $tstamp formatted as "ddmmyy" (According to $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'])
*
* @param int $tstamp Time stamp, seconds
* @return string Formatted time
*/
public static function date($tstamp)
{
return date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], (int)$tstamp);
}
/**
* Returns $tstamp formatted as "ddmmyy hhmm" (According to $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] AND $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'])
*
* @param int $value Time stamp, seconds
* @return string Formatted time
*/
public static function datetime($value)
{
return date(
$GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'],
$value
);
}
/**
* Returns $value (in seconds) formatted as hh:mm:ss
* For instance $value = 3600 + 60*2 + 3 should return "01:02:03"
*
* @param int $value Time stamp, seconds
* @param bool $withSeconds Output hh:mm:ss. If FALSE: hh:mm
* @return string Formatted time
*/
public static function time($value, $withSeconds = true)
{
return gmdate('H:i' . ($withSeconds ? ':s' : ''), (int)$value);
}
/**
* Returns the "age" in minutes / hours / days / years of the number of $seconds inputted.
*
* @param int $seconds Seconds could be the difference of a certain timestamp and time()
* @param string $labels Labels should be something like ' min| hrs| days| yrs| min| hour| day| year'. This value is typically delivered by this function call: $GLOBALS["LANG"]->sL("LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears")
* @return string Formatted time
*/
public static function calcAge($seconds, $labels = 'min|hrs|days|yrs|min|hour|day|year')
{
$labelArr = GeneralUtility::trimExplode('|', $labels, true);
$absSeconds = abs($seconds);
$sign = $seconds < 0 ? -1 : 1;
if ($absSeconds < 3600) {
$val = round($absSeconds / 60);
$seconds = $sign * $val . ' ' . ($val == 1 ? $labelArr[4] : $labelArr[0]);
} elseif ($absSeconds < 24 * 3600) {
$val = round($absSeconds / 3600);
$seconds = $sign * $val . ' ' . ($val == 1 ? $labelArr[5] : $labelArr[1]);
} elseif ($absSeconds < 365 * 24 * 3600) {
$val = round($absSeconds / (24 * 3600));
$seconds = $sign * $val . ' ' . ($val == 1 ? $labelArr[6] : $labelArr[2]);
} else {
$val = round($absSeconds / (365 * 24 * 3600));
$seconds = $sign * $val . ' ' . ($val == 1 ? $labelArr[7] : $labelArr[3]);
}
return $seconds;
}
/**
* Returns a formatted timestamp if $tstamp is set.
* The date/datetime will be followed by the age in parenthesis.
*
* @param int $tstamp Time stamp, seconds
* @param int $prefix 1/-1 depending on polarity of age.
* @param string $date $date=="date" will yield "dd:mm:yy" formatting, otherwise "dd:mm:yy hh:mm
* @return string
*/
public static function dateTimeAge($tstamp, $prefix = 1, $date = '')
{
if (!$tstamp) {
return '';
}
$label = static::getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears');
$age = ' (' . self::calcAge($prefix * ($GLOBALS['EXEC_TIME'] - $tstamp), $label) . ')';
return ($date === 'date' ? self::date($tstamp) : self::datetime($tstamp)) . $age;
}
/**
* Resolves file references for a given record.
*
* @param string $tableName Name of the table of the record
* @param string $fieldName Name of the field of the record
* @param array $element Record data
* @param int|null $workspaceId Workspace to fetch data for
* @return \TYPO3\CMS\Core\Resource\FileReference[]|null
*/
public static function resolveFileReferences($tableName, $fieldName, $element, $workspaceId = null)
{
if (empty($GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'])) {
return null;
}
$configuration = $GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
if (empty($configuration['type']) || $configuration['type'] !== 'inline'
|| empty($configuration['foreign_table']) || $configuration['foreign_table'] !== 'sys_file_reference'
) {
return null;
}
$fileReferences = [];
/** @var RelationHandler $relationHandler */
$relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
if ($workspaceId !== null) {
$relationHandler->setWorkspaceId($workspaceId);
}
$relationHandler->start(
$element[$fieldName],
$configuration['foreign_table'],
$configuration['MM'] ?? '',
$element['uid'],
$tableName,
$configuration
);
$relationHandler->processDeletePlaceholder();
$referenceUids = $relationHandler->tableArray[$configuration['foreign_table']];
foreach ($referenceUids as $referenceUid) {
try {
$fileReference = ResourceFactory::getInstance()->getFileReferenceObject(
$referenceUid,
[],
$workspaceId === 0
);
$fileReferences[$fileReference->getUid()] = $fileReference;
} catch (\TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException $e) {
/**
* We just catch the exception here
* Reasoning: There is nothing an editor or even admin could do
*/
} catch (\InvalidArgumentException $e) {
/**
* The storage does not exist anymore
* Log the exception message for admins as they maybe can restore the storage
*/
self::getLogger()->error($e->getMessage(), ['table' => $tableName, 'fieldName' => $fieldName, 'referenceUid' => $referenceUid, 'exception' => $e]);
}
}
return $fileReferences;
}
/**
* Returns a linked image-tag for thumbnail(s)/fileicons/truetype-font-previews from a database row with a list of image files in a field
* All $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] extension are made to thumbnails + ttf file (renders font-example)
* Thumbsnails are linked to the show_item.php script which will display further details.
*
* @param array $row Row is the database row from the table, $table.
* @param string $table Table name for $row (present in TCA)
* @param string $field Field is pointing to the list of image files
* @param string $backPath Back path prefix for image tag src="" field
* @param string $thumbScript UNUSED since FAL
* @param string $uploaddir Optional: $uploaddir is the directory relative to Environment::getPublicPath() where the image files from the $field value is found (Is by default set to the entry in $GLOBALS['TCA'] for that field! so you don't have to!)
* @param int $abs UNUSED
* @param string $tparams Optional: $tparams is additional attributes for the image tags
* @param int|string $size Optional: $size is [w]x[h] of the thumbnail. 64 is default.
* @param bool $linkInfoPopup Whether to wrap with a link opening the info popup
* @return string Thumbnail image tag.
*/
public static function thumbCode(
$row,
$table,
$field,
$backPath = '',
$thumbScript = '',
$uploaddir = null,
$abs = 0,
$tparams = '',
$size = '',
$linkInfoPopup = true
) {
// Check and parse the size parameter
$size = trim($size);
$sizeParts = [64, 64];
if ($size) {
$sizeParts = explode('x', $size . 'x' . $size);
}
$thumbData = '';
$fileReferences = static::resolveFileReferences($table, $field, $row);
// FAL references
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
if ($fileReferences !== null) {
foreach ($fileReferences as $fileReferenceObject) {
// Do not show previews of hidden references
if ($fileReferenceObject->getProperty('hidden')) {
continue;
}
$fileObject = $fileReferenceObject->getOriginalFile();
if ($fileObject->isMissing()) {
$thumbData .= ''
. htmlspecialchars(
static::getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.file_missing')
)
. ' ' . htmlspecialchars($fileObject->getName()) . ' ';
continue;
}
// Preview web image or media elements
if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['thumbnails']
&& GeneralUtility::inList(
$GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'],
$fileReferenceObject->getExtension()
)
) {
$cropVariantCollection = CropVariantCollection::create((string)$fileReferenceObject->getProperty('crop'));
$cropArea = $cropVariantCollection->getCropArea();
$parameters = json_encode([
'fileId' => $fileObject->getUid(),
'configuration' => [
'width' => $sizeParts[0],
'height' => $sizeParts[1] . 'c',
'crop' => $cropArea->isEmpty() ? null : $cropArea->makeAbsoluteBasedOnFile($fileReferenceObject),
]
]);
$uriParameters = [
'parameters' => $parameters,
'hmac' => GeneralUtility::hmac(
$parameters,
\TYPO3\CMS\Backend\Controller\File\ThumbnailController::class
),
];
$imageUrl = (string)GeneralUtility::makeInstance(UriBuilder::class)
->buildUriFromRoute('thumbnails', $uriParameters);
$attributes = [
'src' => $imageUrl,
'width' => (int)$sizeParts[0],
'height' => (int)$sizeParts[1],
'alt' => $fileReferenceObject->getName(),
];
$imgTag = '';
} else {
// Icon
$imgTag = ''
. $iconFactory->getIconForResource($fileObject, Icon::SIZE_SMALL)->render()
. '';
}
if ($linkInfoPopup) {
$onClick = 'top.TYPO3.InfoWindow.showItem(\'_FILE\',\'' . (int)$fileObject->getUid() . '\'); return false;';
$thumbData .= '' . $imgTag . ' ';
} else {
$thumbData .= $imgTag;
}
}
} else {
// Find uploaddir automatically
if ($uploaddir === null) {
$uploaddir = $GLOBALS['TCA'][$table]['columns'][$field]['config']['uploadfolder'];
}
$uploaddir = rtrim($uploaddir, '/');
// Traverse files:
$thumbs = GeneralUtility::trimExplode(',', $row[$field], true);
$thumbData = '';
foreach ($thumbs as $theFile) {
if ($theFile) {
$fileName = trim($uploaddir . '/' . $theFile, '/');
try {
/** @var File $fileObject */
$fileObject = ResourceFactory::getInstance()->retrieveFileOrFolderObject($fileName);
// Skip the resource if it's not of type AbstractFile. One case where this can happen if the
// storage has been externally modified and the field value now points to a folder
// instead of a file.
if (!$fileObject instanceof AbstractFile) {
continue;
}
if ($fileObject->isMissing()) {
$thumbData .= ''
. htmlspecialchars(static::getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.file_missing'))
. ' ' . htmlspecialchars($fileObject->getName()) . ' ';
continue;
}
} catch (ResourceDoesNotExistException $exception) {
$thumbData .= ''
. htmlspecialchars(static::getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.file_missing'))
. ' ' . htmlspecialchars($fileName) . ' ';
continue;
}
$fileExtension = $fileObject->getExtension();
if ($fileExtension === 'ttf'
|| GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], $fileExtension)
) {
$imageUrl = $fileObject->process(
ProcessedFile::CONTEXT_IMAGEPREVIEW,
[
'width' => $sizeParts[0],
'height' => $sizeParts[1]
]
)->getPublicUrl(true);
$image = '';
if ($linkInfoPopup) {
$onClick = 'top.TYPO3.InfoWindow.showItem(\'_FILE\', ' . GeneralUtility::quoteJSvalue($fileName) . ',\'\');return false;';
$thumbData .= '' . $image . ' ';
} else {
$thumbData .= $image;
}
} else {
// Gets the icon
$fileIcon = ''
. $iconFactory->getIconForResource($fileObject, Icon::SIZE_SMALL)->render()
. '';
if ($linkInfoPopup) {
$onClick = 'top.TYPO3.InfoWindow.showItem(\'_FILE\', ' . GeneralUtility::quoteJSvalue($fileName) . ',\'\'); return false;';
$thumbData .= '' . $fileIcon . ' ';
} else {
$thumbData .= $fileIcon;
}
}
}
}
}
return $thumbData;
}
/**
* Returns title-attribute information for a page-record informing about id, alias, doktype, hidden, starttime, endtime, fe_group etc.
*
* @param array $row Input must be a page row ($row) with the proper fields set (be sure - send the full range of fields for the table)
* @param string $perms_clause This is used to get the record path of the shortcut page, if any (and doktype==4)
* @param bool $includeAttrib If $includeAttrib is set, then the 'title=""' attribute is wrapped about the return value, which is in any case htmlspecialchar()'ed already
* @return string
*/
public static function titleAttribForPages($row, $perms_clause = '', $includeAttrib = true)
{
$lang = static::getLanguageService();
$parts = [];
$parts[] = 'id=' . $row['uid'];
if ($row['uid'] === 0) {
$out = htmlspecialchars($parts[0]);
return $includeAttrib ? 'title="' . $out . '"' : $out;
}
if ($row['alias']) {
$parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['alias']['label']) . ' ' . $row['alias'];
}
if ($row['pid'] < 0) {
$parts[] = 'v#1.' . $row['t3ver_id'];
}
switch (VersionState::cast($row['t3ver_state'])) {
case new VersionState(VersionState::NEW_PLACEHOLDER):
$parts[] = 'PLH WSID#' . $row['t3ver_wsid'];
break;
case new VersionState(VersionState::DELETE_PLACEHOLDER):
$parts[] = 'Deleted element!';
break;
case new VersionState(VersionState::MOVE_PLACEHOLDER):
$parts[] = 'OLD LOCATION (Move Placeholder) WSID#' . $row['t3ver_wsid'];
break;
case new VersionState(VersionState::MOVE_POINTER):
$parts[] = 'NEW LOCATION (Move-to Pointer) WSID#' . $row['t3ver_wsid'];
break;
case new VersionState(VersionState::NEW_PLACEHOLDER_VERSION):
$parts[] = 'New element!';
break;
}
if ($row['doktype'] == PageRepository::DOKTYPE_LINK) {
$parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['url']['label']) . ' ' . $row['url'];
} elseif ($row['doktype'] == PageRepository::DOKTYPE_SHORTCUT) {
if ($perms_clause) {
$label = self::getRecordPath((int)$row['shortcut'], $perms_clause, 20);
} else {
$row['shortcut'] = (int)$row['shortcut'];
$lRec = self::getRecordWSOL('pages', $row['shortcut'], 'title');
$label = $lRec['title'] . ' (id=' . $row['shortcut'] . ')';
}
if ($row['shortcut_mode'] != PageRepository::SHORTCUT_MODE_NONE) {
$label .= ', ' . $lang->sL($GLOBALS['TCA']['pages']['columns']['shortcut_mode']['label']) . ' '
. $lang->sL(self::getLabelFromItemlist('pages', 'shortcut_mode', $row['shortcut_mode']));
}
$parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['shortcut']['label']) . ' ' . $label;
} elseif ($row['doktype'] == PageRepository::DOKTYPE_MOUNTPOINT) {
if ((int)$row['mount_pid'] > 0) {
if ($perms_clause) {
$label = self::getRecordPath((int)$row['mount_pid'], $perms_clause, 20);
} else {
$lRec = self::getRecordWSOL('pages', (int)$row['mount_pid'], 'title');
$label = $lRec['title'] . ' (id=' . $row['mount_pid'] . ')';
}
$parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['mount_pid']['label']) . ' ' . $label;
if ($row['mount_pid_ol']) {
$parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['mount_pid_ol']['label']);
}
} else {
$parts[] = $lang->sl('LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:no_mount_pid');
}
}
if ($row['nav_hide']) {
$parts[] = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:pages.nav_hide');
}
if ($row['hidden']) {
$parts[] = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.hidden');
}
if ($row['starttime']) {
$parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['starttime']['label'])
. ' ' . self::dateTimeAge($row['starttime'], -1, 'date');
}
if ($row['endtime']) {
$parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['endtime']['label']) . ' '
. self::dateTimeAge($row['endtime'], -1, 'date');
}
if ($row['fe_group']) {
$fe_groups = [];
foreach (GeneralUtility::intExplode(',', $row['fe_group']) as $fe_group) {
if ($fe_group < 0) {
$fe_groups[] = $lang->sL(self::getLabelFromItemlist('pages', 'fe_group', $fe_group));
} else {
$lRec = self::getRecordWSOL('fe_groups', $fe_group, 'title');
$fe_groups[] = $lRec['title'];
}
}
$label = implode(', ', $fe_groups);
$parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['fe_group']['label']) . ' ' . $label;
}
$out = htmlspecialchars(implode(' - ', $parts));
return $includeAttrib ? 'title="' . $out . '"' : $out;
}
/**
* Returns the combined markup for Bootstraps tooltips
*
* @param array $row
* @param string $table
* @return string
*/
public static function getRecordToolTip(array $row, $table = 'pages')
{
$toolTipText = self::getRecordIconAltText($row, $table);
$toolTipCode = 'data-toggle="tooltip" data-title=" '
. str_replace(' - ', ' ', $toolTipText)
. '" data-html="true" data-placement="right"';
return $toolTipCode;
}
/**
* Returns title-attribute information for ANY record (from a table defined in TCA of course)
* The included information depends on features of the table, but if hidden, starttime, endtime and fe_group fields are configured for, information about the record status in regard to these features are is included.
* "pages" table can be used as well and will return the result of ->titleAttribForPages() for that page.
*
* @param array $row Table row; $row is a row from the table, $table
* @param string $table Table name
* @return string
*/
public static function getRecordIconAltText($row, $table = 'pages')
{
if ($table === 'pages') {
$out = self::titleAttribForPages($row, '', 0);
} else {
$out = !empty(trim($GLOBALS['TCA'][$table]['ctrl']['descriptionColumn'])) ? $row[$GLOBALS['TCA'][$table]['ctrl']['descriptionColumn']] . ' ' : '';
$ctrl = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns'];
// Uid is added
$out .= 'id=' . $row['uid'];
if ($table === 'pages' && $row['alias']) {
$out .= ' / ' . $row['alias'];
}
if (static::isTableWorkspaceEnabled($table) && $row['pid'] < 0) {
$out .= ' - v#1.' . $row['t3ver_id'];
}
if (static::isTableWorkspaceEnabled($table)) {
switch (VersionState::cast($row['t3ver_state'])) {
case new VersionState(VersionState::NEW_PLACEHOLDER):
$out .= ' - PLH WSID#' . $row['t3ver_wsid'];
break;
case new VersionState(VersionState::DELETE_PLACEHOLDER):
$out .= ' - Deleted element!';
break;
case new VersionState(VersionState::MOVE_PLACEHOLDER):
$out .= ' - OLD LOCATION (Move Placeholder) WSID#' . $row['t3ver_wsid'];
break;
case new VersionState(VersionState::MOVE_POINTER):
$out .= ' - NEW LOCATION (Move-to Pointer) WSID#' . $row['t3ver_wsid'];
break;
case new VersionState(VersionState::NEW_PLACEHOLDER_VERSION):
$out .= ' - New element!';
break;
}
}
// Hidden
$lang = static::getLanguageService();
if ($ctrl['disabled']) {
$out .= $row[$ctrl['disabled']] ? ' - ' . $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.hidden') : '';
}
if ($ctrl['starttime']) {
if ($row[$ctrl['starttime']] > $GLOBALS['EXEC_TIME']) {
$out .= ' - ' . $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.starttime') . ':' . self::date($row[$ctrl['starttime']]) . ' (' . self::daysUntil($row[$ctrl['starttime']]) . ' ' . $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.days') . ')';
}
}
if ($row[$ctrl['endtime']]) {
$out .= ' - ' . $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.endtime') . ': ' . self::date($row[$ctrl['endtime']]) . ' (' . self::daysUntil($row[$ctrl['endtime']]) . ' ' . $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.days') . ')';
}
}
return htmlspecialchars($out);
}
/**
* Returns the label of the first found entry in an "items" array from $GLOBALS['TCA'] (tablename = $table/fieldname = $col) where the value is $key
*
* @param string $table Table name, present in $GLOBALS['TCA']
* @param string $col Field name, present in $GLOBALS['TCA']
* @param string $key items-array value to match
* @return string Label for item entry
*/
public static function getLabelFromItemlist($table, $col, $key)
{
// Check, if there is an "items" array:
if (is_array($GLOBALS['TCA'][$table]['columns'][$col]['config']['items'] ?? false)) {
// Traverse the items-array...
foreach ($GLOBALS['TCA'][$table]['columns'][$col]['config']['items'] as $v) {
// ... and return the first found label where the value was equal to $key
if ((string)$v[1] === (string)$key) {
return $v[0];
}
}
}
return '';
}
/**
* Return the label of a field by additionally checking TsConfig values
*
* @param int $pageId Page id
* @param string $table Table name
* @param string $column Field Name
* @param string $key item value
* @return string Label for item entry
*/
public static function getLabelFromItemListMerged($pageId, $table, $column, $key)
{
$pageTsConfig = static::getPagesTSconfig($pageId);
$label = '';
if (isset($pageTsConfig['TCEFORM.'])
&& \is_array($pageTsConfig['TCEFORM.'])
&& \is_array($pageTsConfig['TCEFORM.'][$table . '.'])
&& \is_array($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.'])
) {
if (\is_array($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['addItems.'])
&& isset($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['addItems.'][$key])
) {
$label = $pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['addItems.'][$key];
} elseif (\is_array($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['altLabels.'])
&& isset($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['altLabels.'][$key])
) {
$label = $pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['altLabels.'][$key];
}
}
if (empty($label)) {
$tcaValue = self::getLabelFromItemlist($table, $column, $key);
if (!empty($tcaValue)) {
$label = $tcaValue;
}
}
return $label;
}
/**
* Splits the given key with commas and returns the list of all the localized items labels, separated by a comma.
* NOTE: this does not take itemsProcFunc into account
*
* @param string $table Table name, present in TCA
* @param string $column Field name
* @param string $keyList Key or comma-separated list of keys.
* @param array $columnTsConfig page TSConfig for $column (TCEMAIN.
.)
* @return string Comma-separated list of localized labels
*/
public static function getLabelsFromItemsList($table, $column, $keyList, array $columnTsConfig = [])
{
// Check if there is an "items" array
if (
!isset($GLOBALS['TCA'][$table]['columns'][$column]['config']['items'])
|| !is_array($GLOBALS['TCA'][$table]['columns'][$column]['config']['items'])
|| $keyList === ''
) {
return '';
}
$keys = GeneralUtility::trimExplode(',', $keyList, true);
$labels = [];
// Loop on all selected values
foreach ($keys as $key) {
$label = null;
if ($columnTsConfig) {
// Check if label has been defined or redefined via pageTsConfig
if (isset($columnTsConfig['addItems.'][$key])) {
$label = $columnTsConfig['addItems.'][$key];
} elseif (isset($columnTsConfig['altLabels.'][$key])) {
$label = $columnTsConfig['altLabels.'][$key];
}
}
if ($label === null) {
// Otherwise lookup the label in TCA items list
foreach ($GLOBALS['TCA'][$table]['columns'][$column]['config']['items'] as $itemConfiguration) {
list($currentLabel, $currentKey) = $itemConfiguration;
if ((string)$key === (string)$currentKey) {
$label = $currentLabel;
break;
}
}
}
if ($label !== null) {
$labels[] = static::getLanguageService()->sL($label);
}
}
return implode(', ', $labels);
}
/**
* Returns the label-value for fieldname $col in table, $table
* If $printAllWrap is set (to a "wrap") then it's wrapped around the $col value IF THE COLUMN $col DID NOT EXIST in TCA!, eg. $printAllWrap = '|' and the fieldname was 'not_found_field' then the return value would be 'not_found_field'
*
* @param string $table Table name, present in $GLOBALS['TCA']
* @param string $col Field name
* @return string or NULL if $col is not found in the TCA table
*/
public static function getItemLabel($table, $col)
{
// Check if column exists
if (is_array($GLOBALS['TCA'][$table]) && is_array($GLOBALS['TCA'][$table]['columns'][$col])) {
return $GLOBALS['TCA'][$table]['columns'][$col]['label'];
}
return null;
}
/**
* Returns the "title"-value in record, $row, from table, $table
* The field(s) from which the value is taken is determined by the "ctrl"-entries 'label', 'label_alt' and 'label_alt_force'
*
* @param string $table Table name, present in TCA
* @param array $row Row from table
* @param bool $prep If set, result is prepared for output: The output is cropped to a limited length (depending on BE_USER->uc['titleLen']) and if no value is found for the title, '[No title]' is returned (localized). Further, the output is htmlspecialchars()'ed
* @param bool $forceResult If set, the function always returns an output. If no value is found for the title, '[No title]' is returned (localized).
* @return string
*/
public static function getRecordTitle($table, $row, $prep = false, $forceResult = true)
{
$recordTitle = '';
if (isset($GLOBALS['TCA'][$table]) && is_array($GLOBALS['TCA'][$table])) {
// If configured, call userFunc
if (!empty($GLOBALS['TCA'][$table]['ctrl']['label_userFunc'])) {
$params['table'] = $table;
$params['row'] = $row;
$params['title'] = '';
$params['options'] = $GLOBALS['TCA'][$table]['ctrl']['label_userFunc_options'] ?? [];
// Create NULL-reference
$null = null;
GeneralUtility::callUserFunction($GLOBALS['TCA'][$table]['ctrl']['label_userFunc'], $params, $null);
$recordTitle = $params['title'];
} else {
// No userFunc: Build label
$recordTitle = self::getProcessedValue(
$table,
$GLOBALS['TCA'][$table]['ctrl']['label'],
$row[$GLOBALS['TCA'][$table]['ctrl']['label']],
0,
0,
false,
$row['uid'],
$forceResult
);
if (!empty($GLOBALS['TCA'][$table]['ctrl']['label_alt'])
&& (!empty($GLOBALS['TCA'][$table]['ctrl']['label_alt_force']) || (string)$recordTitle === '')
) {
$altFields = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$table]['ctrl']['label_alt'], true);
$tA = [];
if (!empty($recordTitle)) {
$tA[] = $recordTitle;
}
foreach ($altFields as $fN) {
$recordTitle = trim(strip_tags($row[$fN]));
if ((string)$recordTitle !== '') {
$recordTitle = self::getProcessedValue($table, $fN, $recordTitle, 0, 0, false, $row['uid']);
if (!$GLOBALS['TCA'][$table]['ctrl']['label_alt_force']) {
break;
}
$tA[] = $recordTitle;
}
}
if ($GLOBALS['TCA'][$table]['ctrl']['label_alt_force']) {
$recordTitle = implode(', ', $tA);
}
}
}
// If the current result is empty, set it to '[No title]' (localized) and prepare for output if requested
if ($prep || $forceResult) {
if ($prep) {
$recordTitle = self::getRecordTitlePrep($recordTitle);
}
if (trim($recordTitle) === '') {
$recordTitle = self::getNoRecordTitle($prep);
}
}
}
return $recordTitle;
}
/**
* Crops a title string to a limited length and if it really was cropped, wrap it in a |,
* which offers a tooltip with the original title when moving mouse over it.
*
* @param string $title The title string to be cropped
* @param int $titleLength Crop title after this length - if not set, BE_USER->uc['titleLen'] is used
* @return string The processed title string, wrapped in | if cropped
*/
public static function getRecordTitlePrep($title, $titleLength = 0)
{
// If $titleLength is not a valid positive integer, use BE_USER->uc['titleLen']:
if (!$titleLength || !MathUtility::canBeInterpretedAsInteger($titleLength) || $titleLength < 0) {
$titleLength = static::getBackendUserAuthentication()->uc['titleLen'];
}
$titleOrig = htmlspecialchars($title);
$title = htmlspecialchars(GeneralUtility::fixed_lgd_cs($title, $titleLength));
// If title was cropped, offer a tooltip:
if ($titleOrig != $title) {
$title = '' . $title . '';
}
return $title;
}
/**
* Get a localized [No title] string, wrapped in | if $prep is TRUE.
*
* @param bool $prep Wrap result in |
* @return string Localized [No title] string
*/
public static function getNoRecordTitle($prep = false)
{
$noTitle = '[' .
htmlspecialchars(static::getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.no_title'))
. ']';
if ($prep) {
$noTitle = '' . $noTitle . '';
}
return $noTitle;
}
/**
* Returns a human readable output of a value from a record
* For instance a database record relation would be looked up to display the title-value of that record. A checkbox with a "1" value would be "Yes", etc.
* $table/$col is tablename and fieldname
* REMEMBER to pass the output through htmlspecialchars() if you output it to the browser! (To protect it from XSS attacks and be XHTML compliant)
*
* @param string $table Table name, present in TCA
* @param string $col Field name, present in TCA
* @param string $value The value of that field from a selected record
* @param int $fixed_lgd_chars The max amount of characters the value may occupy
* @param bool $defaultPassthrough Flag means that values for columns that has no conversion will just be pass through directly (otherwise cropped to 200 chars or returned as "N/A")
* @param bool $noRecordLookup If set, no records will be looked up, UIDs are just shown.
* @param int $uid Uid of the current record
* @param bool $forceResult If BackendUtility::getRecordTitle is used to process the value, this parameter is forwarded.
* @param int $pid Optional page uid is used to evaluate page TSConfig for the given field
* @throws \InvalidArgumentException
* @return string|null
*/
public static function getProcessedValue(
$table,
$col,
$value,
$fixed_lgd_chars = 0,
$defaultPassthrough = false,
$noRecordLookup = false,
$uid = 0,
$forceResult = true,
$pid = 0
) {
if ($col === 'uid') {
// uid is not in TCA-array
return $value;
}
// Check if table and field is configured
if (!isset($GLOBALS['TCA'][$table]['columns'][$col]) || !is_array($GLOBALS['TCA'][$table]['columns'][$col])) {
return null;
}
// Depending on the fields configuration, make a meaningful output value.
$theColConf = $GLOBALS['TCA'][$table]['columns'][$col]['config'] ?? [];
/*****************
*HOOK: pre-processing the human readable output from a record
****************/
$null = null;
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['preProcessValue'] ?? [] as $_funcRef) {
GeneralUtility::callUserFunction($_funcRef, $theColConf, $null);
}
$l = '';
$lang = static::getLanguageService();
switch ((string)($theColConf['type'] ?? '')) {
case 'radio':
$l = self::getLabelFromItemlist($table, $col, $value);
$l = $lang->sL($l);
break;
case 'inline':
case 'select':
if (!empty($theColConf['MM'])) {
if ($uid) {
// Display the title of MM related records in lists
if ($noRecordLookup) {
$MMfields = [];
$MMfields[] = $theColConf['foreign_table'] . '.uid';
} else {
$MMfields = [$theColConf['foreign_table'] . '.' . $GLOBALS['TCA'][$theColConf['foreign_table']]['ctrl']['label']];
if (isset($GLOBALS['TCA'][$theColConf['foreign_table']]['ctrl']['label_alt'])) {
foreach (GeneralUtility::trimExplode(
',',
$GLOBALS['TCA'][$theColConf['foreign_table']]['ctrl']['label_alt'],
true
) as $f) {
$MMfields[] = $theColConf['foreign_table'] . '.' . $f;
}
}
}
/** @var RelationHandler $dbGroup */
$dbGroup = GeneralUtility::makeInstance(RelationHandler::class);
$dbGroup->start(
$value,
$theColConf['foreign_table'],
$theColConf['MM'],
$uid,
$table,
$theColConf
);
$selectUids = $dbGroup->tableArray[$theColConf['foreign_table']];
if (is_array($selectUids) && !empty($selectUids)) {
$queryBuilder = static::getQueryBuilderForTable($theColConf['foreign_table']);
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class));
$result = $queryBuilder
->select('uid', ...$MMfields)
->from($theColConf['foreign_table'])
->where(
$queryBuilder->expr()->in(
'uid',
$queryBuilder->createNamedParameter($selectUids, Connection::PARAM_INT_ARRAY)
)
)
->execute();
$mmlA = [];
while ($MMrow = $result->fetch()) {
// Keep sorting of $selectUids
$selectedUid = array_search($MMrow['uid'], $selectUids);
$mmlA[$selectedUid] = $MMrow['uid'];
if (!$noRecordLookup) {
$mmlA[$selectedUid] = static::getRecordTitle(
$theColConf['foreign_table'],
$MMrow,
false,
$forceResult
);
}
}
if (!empty($mmlA)) {
ksort($mmlA);
$l = implode('; ', $mmlA);
} else {
$l = 'N/A';
}
} else {
$l = 'N/A';
}
} else {
$l = 'N/A';
}
} else {
$columnTsConfig = [];
if ($pid) {
$pageTsConfig = self::getPagesTSconfig($pid);
if (isset($pageTsConfig['TCEFORM.'][$table . '.'][$col . '.']) && is_array($pageTsConfig['TCEFORM.'][$table . '.'][$col . '.'])) {
$columnTsConfig = $pageTsConfig['TCEFORM.'][$table . '.'][$col . '.'];
}
}
$l = self::getLabelsFromItemsList($table, $col, $value, $columnTsConfig);
if (!empty($theColConf['foreign_table']) && !$l && !empty($GLOBALS['TCA'][$theColConf['foreign_table']])) {
if ($noRecordLookup) {
$l = $value;
} else {
$rParts = [];
if ($uid && isset($theColConf['foreign_field']) && $theColConf['foreign_field'] !== '') {
$queryBuilder = static::getQueryBuilderForTable($theColConf['foreign_table']);
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
$constraints = [
$queryBuilder->expr()->eq(
$theColConf['foreign_field'],
$queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
)
];
if (!empty($theColConf['foreign_table_field'])) {
$constraints[] = $queryBuilder->expr()->eq(
$theColConf['foreign_table_field'],
$queryBuilder->createNamedParameter($table, \PDO::PARAM_STR)
);
}
// Add additional where clause if foreign_match_fields are defined
$foreignMatchFields = [];
if (is_array($theColConf['foreign_match_fields'])) {
$foreignMatchFields = $theColConf['foreign_match_fields'];
}
foreach ($foreignMatchFields as $matchField => $matchValue) {
$constraints[] = $queryBuilder->expr()->eq(
$matchField,
$queryBuilder->createNamedParameter($matchValue)
);
}
$result = $queryBuilder
->select('*')
->from($theColConf['foreign_table'])
->where(...$constraints)
->execute();
while ($record = $result->fetch()) {
$rParts[] = $record['uid'];
}
}
if (empty($rParts)) {
$rParts = GeneralUtility::trimExplode(',', $value, true);
}
$lA = [];
foreach ($rParts as $rVal) {
$rVal = (int)$rVal;
$r = self::getRecordWSOL($theColConf['foreign_table'], $rVal);
if (is_array($r)) {
$lA[] = $lang->sL($theColConf['foreign_table_prefix'])
. self::getRecordTitle($theColConf['foreign_table'], $r, false, $forceResult);
} else {
$lA[] = $rVal ? '[' . $rVal . '!]' : '';
}
}
$l = implode(', ', $lA);
}
}
if (empty($l) && !empty($value)) {
// Use plain database value when label is empty
$l = $value;
}
}
break;
case 'group':
// resolve the titles for DB records
if (isset($theColConf['internal_type']) && $theColConf['internal_type'] === 'db') {
if (isset($theColConf['MM']) && $theColConf['MM']) {
if ($uid) {
// Display the title of MM related records in lists
if ($noRecordLookup) {
$MMfields = [];
$MMfields[] = $theColConf['foreign_table'] . '.uid';
} else {
$MMfields = [$theColConf['foreign_table'] . '.' . $GLOBALS['TCA'][$theColConf['foreign_table']]['ctrl']['label']];
$altLabelFields = explode(
',',
$GLOBALS['TCA'][$theColConf['foreign_table']]['ctrl']['label_alt']
);
foreach ($altLabelFields as $f) {
$f = trim($f);
if ($f !== '') {
$MMfields[] = $theColConf['foreign_table'] . '.' . $f;
}
}
}
/** @var RelationHandler $dbGroup */
$dbGroup = GeneralUtility::makeInstance(RelationHandler::class);
$dbGroup->start(
$value,
$theColConf['foreign_table'],
$theColConf['MM'],
$uid,
$table,
$theColConf
);
$selectUids = $dbGroup->tableArray[$theColConf['foreign_table']];
if (!empty($selectUids) && is_array($selectUids)) {
$queryBuilder = static::getQueryBuilderForTable($theColConf['foreign_table']);
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class));
$result = $queryBuilder
->select('uid', ...$MMfields)
->from($theColConf['foreign_table'])
->where(
$queryBuilder->expr()->in(
'uid',
$queryBuilder->createNamedParameter(
$selectUids,
Connection::PARAM_INT_ARRAY
)
)
)
->execute();
$mmlA = [];
while ($MMrow = $result->fetch()) {
// Keep sorting of $selectUids
$selectedUid = array_search($MMrow['uid'], $selectUids);
$mmlA[$selectedUid] = $MMrow['uid'];
if (!$noRecordLookup) {
$mmlA[$selectedUid] = static::getRecordTitle(
$theColConf['foreign_table'],
$MMrow,
false,
$forceResult
);
}
}
if (!empty($mmlA)) {
ksort($mmlA);
$l = implode('; ', $mmlA);
} else {
$l = 'N/A';
}
} else {
$l = 'N/A';
}
} else {
$l = 'N/A';
}
} else {
$finalValues = [];
$relationTableName = $theColConf['allowed'];
$explodedValues = GeneralUtility::trimExplode(',', $value, true);
foreach ($explodedValues as $explodedValue) {
if (MathUtility::canBeInterpretedAsInteger($explodedValue)) {
$relationTableNameForField = $relationTableName;
} else {
list($relationTableNameForField, $explodedValue) = self::splitTable_Uid($explodedValue);
}
$relationRecord = static::getRecordWSOL($relationTableNameForField, $explodedValue);
$finalValues[] = static::getRecordTitle($relationTableNameForField, $relationRecord);
}
$l = implode(', ', $finalValues);
}
} else {
$l = implode(', ', GeneralUtility::trimExplode(',', $value, true));
}
break;
case 'check':
if (!is_array($theColConf['items'])) {
$l = $value ? $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:yes') : $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:no');
} elseif (count($theColConf['items']) === 1) {
reset($theColConf['items']);
$invertStateDisplay = current($theColConf['items'])['invertStateDisplay'] ?? false;
if ($invertStateDisplay) {
$value = !$value;
}
$l = $value ? $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:yes') : $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:no');
} else {
$lA = [];
foreach ($theColConf['items'] as $key => $val) {
if ($value & pow(2, $key)) {
$lA[] = $lang->sL($val[0]);
}
}
$l = implode(', ', $lA);
}
break;
case 'input':
// Hide value 0 for dates, but show it for everything else
if (isset($value)) {
$dateTimeFormats = QueryHelper::getDateTimeFormats();
if (GeneralUtility::inList($theColConf['eval'] ?? '', 'date')) {
// Handle native date field
if (isset($theColConf['dbType']) && $theColConf['dbType'] === 'date') {
$value = $value === $dateTimeFormats['date']['empty'] ? 0 : (int)strtotime($value);
} else {
$value = (int)$value;
}
if (!empty($value)) {
$ageSuffix = '';
$dateColumnConfiguration = $GLOBALS['TCA'][$table]['columns'][$col]['config'];
$ageDisplayKey = 'disableAgeDisplay';
// generate age suffix as long as not explicitly suppressed
if (!isset($dateColumnConfiguration[$ageDisplayKey])
// non typesafe comparison on intention
|| $dateColumnConfiguration[$ageDisplayKey] == false
) {
$ageSuffix = ' (' . ($GLOBALS['EXEC_TIME'] - $value > 0 ? '-' : '')
. self::calcAge(
abs($GLOBALS['EXEC_TIME'] - $value),
$lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears')
)
. ')';
}
$l = self::date($value) . $ageSuffix;
}
} elseif (GeneralUtility::inList($theColConf['eval'] ?? '', 'time')) {
// Handle native time field
if (isset($theColConf['dbType']) && $theColConf['dbType'] === 'time') {
$value = $value === $dateTimeFormats['time']['empty'] ? 0 : (int)strtotime('1970-01-01 ' . $value);
} else {
$value = (int)$value;
}
if (!empty($value)) {
$l = gmdate('H:i', (int)$value);
}
} elseif (GeneralUtility::inList($theColConf['eval'] ?? '', 'timesec')) {
// Handle native time field
if (isset($theColConf['dbType']) && $theColConf['dbType'] === 'time') {
$value = $value === $dateTimeFormats['time']['empty'] ? 0 : (int)strtotime('1970-01-01 ' . $value);
} else {
$value = (int)$value;
}
if (!empty($value)) {
$l = gmdate('H:i:s', (int)$value);
}
} elseif (GeneralUtility::inList($theColConf['eval'] ?? '', 'datetime')) {
// Handle native datetime field
if (isset($theColConf['dbType']) && $theColConf['dbType'] === 'datetime') {
$value = $value === $dateTimeFormats['datetime']['empty'] ? 0 : (int)strtotime($value);
} else {
$value = (int)$value;
}
if (!empty($value)) {
$l = self::datetime($value);
}
} else {
$l = $value;
}
}
break;
case 'flex':
$l = strip_tags($value);
break;
default:
if ($defaultPassthrough) {
$l = $value;
} elseif (isset($theColConf['MM'])) {
$l = 'N/A';
} elseif ($value) {
$l = GeneralUtility::fixed_lgd_cs(strip_tags($value), 200);
}
}
// If this field is a password field, then hide the password by changing it to a random number of asterisk (*)
if (!empty($theColConf['eval']) && stristr($theColConf['eval'], 'password')) {
$l = '';
$randomNumber = rand(5, 12);
for ($i = 0; $i < $randomNumber; $i++) {
$l .= '*';
}
}
/*****************
*HOOK: post-processing the human readable output from a record
****************/
$null = null;
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['postProcessValue'] ?? [] as $_funcRef) {
$params = [
'value' => $l,
'colConf' => $theColConf
];
$l = GeneralUtility::callUserFunction($_funcRef, $params, $null);
}
if ($fixed_lgd_chars) {
return GeneralUtility::fixed_lgd_cs($l, $fixed_lgd_chars);
}
return $l;
}
/**
* Same as ->getProcessedValue() but will go easy on fields like "tstamp" and "pid" which are not configured in TCA - they will be formatted by this function instead.
*
* @param string $table Table name, present in TCA
* @param string $fN Field name
* @param string $fV Field value
* @param int $fixed_lgd_chars The max amount of characters the value may occupy
* @param int $uid Uid of the current record
* @param bool $forceResult If BackendUtility::getRecordTitle is used to process the value, this parameter is forwarded.
* @param int $pid Optional page uid is used to evaluate page TSConfig for the given field
* @return string
* @see getProcessedValue()
*/
public static function getProcessedValueExtra(
$table,
$fN,
$fV,
$fixed_lgd_chars = 0,
$uid = 0,
$forceResult = true,
$pid = 0
) {
$fVnew = self::getProcessedValue($table, $fN, $fV, $fixed_lgd_chars, 1, 0, $uid, $forceResult, $pid);
if (!isset($fVnew)) {
if (is_array($GLOBALS['TCA'][$table])) {
if ($fN == $GLOBALS['TCA'][$table]['ctrl']['tstamp'] || $fN == $GLOBALS['TCA'][$table]['ctrl']['crdate']) {
$fVnew = self::datetime($fV);
} elseif ($fN === 'pid') {
// Fetches the path with no regard to the users permissions to select pages.
$fVnew = self::getRecordPath($fV, '1=1', 20);
} else {
$fVnew = $fV;
}
}
}
return $fVnew;
}
/**
* Returns fields for a table, $table, which would typically be interesting to select
* This includes uid, the fields defined for title, icon-field.
* Returned as a list ready for query ($prefix can be set to eg. "pages." if you are selecting from the pages table and want the table name prefixed)
*
* @param string $table Table name, present in $GLOBALS['TCA']
* @param string $prefix Table prefix
* @param array $fields Preset fields (must include prefix if that is used)
* @return string List of fields.
*/
public static function getCommonSelectFields($table, $prefix = '', $fields = [])
{
$fields[] = $prefix . 'uid';
if (isset($GLOBALS['TCA'][$table]['ctrl']['label']) && $GLOBALS['TCA'][$table]['ctrl']['label'] != '') {
$fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['label'];
}
if (!empty($GLOBALS['TCA'][$table]['ctrl']['label_alt'])) {
$secondFields = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$table]['ctrl']['label_alt'], true);
foreach ($secondFields as $fieldN) {
$fields[] = $prefix . $fieldN;
}
}
if (static::isTableWorkspaceEnabled($table)) {
$fields[] = $prefix . 't3ver_id';
$fields[] = $prefix . 't3ver_state';
$fields[] = $prefix . 't3ver_wsid';
$fields[] = $prefix . 't3ver_count';
}
if (!empty($GLOBALS['TCA'][$table]['ctrl']['selicon_field'])) {
$fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['selicon_field'];
}
if (!empty($GLOBALS['TCA'][$table]['ctrl']['typeicon_column'])) {
$fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['typeicon_column'];
}
if (!empty($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'])) {
$fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'];
}
if (!empty($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['starttime'])) {
$fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['starttime'];
}
if (!empty($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['endtime'])) {
$fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['endtime'];
}
if (!empty($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['fe_group'])) {
$fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['fe_group'];
}
return implode(',', array_unique($fields));
}
/*******************************************
*
* Backend Modules API functions
*
*******************************************/
/**
* Returns CSH help text (description), if configured for, as an array (title, description)
*
* @param string $table Table name
* @param string $field Field name
* @return array With keys 'description' (raw, as available in locallang), 'title' (optional), 'moreInfo'
*/
public static function helpTextArray($table, $field)
{
if (!isset($GLOBALS['TCA_DESCR'][$table]['columns'])) {
static::getLanguageService()->loadSingleTableDescription($table);
}
$output = [
'description' => null,
'title' => null,
'moreInfo' => false
];
if (isset($GLOBALS['TCA_DESCR'][$table]['columns'][$field]) && is_array($GLOBALS['TCA_DESCR'][$table]['columns'][$field])) {
$data = $GLOBALS['TCA_DESCR'][$table]['columns'][$field];
// Add alternative title, if defined
if ($data['alttitle']) {
$output['title'] = $data['alttitle'];
}
// If we have more information to show and access to the cshmanual
if (($data['image_descr'] || $data['seeAlso'] || $data['details'] || $data['syntax'])
&& static::getBackendUserAuthentication()->check('modules', 'help_CshmanualCshmanual')
) {
$output['moreInfo'] = true;
}
// Add description
if ($data['description']) {
$output['description'] = $data['description'];
}
}
return $output;
}
/**
* Returns CSH help text
*
* @param string $table Table name
* @param string $field Field name
* @return string HTML content for help text
* @see cshItem()
*/
public static function helpText($table, $field)
{
$helpTextArray = self::helpTextArray($table, $field);
$output = '';
$arrow = '';
// Put header before the rest of the text
if ($helpTextArray['title'] !== null) {
$output .= '
' . $helpTextArray['title'] . '
';
}
// Add see also arrow if we have more info
if ($helpTextArray['moreInfo']) {
/** @var IconFactory $iconFactory */
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
$arrow = $iconFactory->getIcon('actions-view-go-forward', Icon::SIZE_SMALL)->render();
}
// Wrap description and arrow in p tag
if ($helpTextArray['description'] !== null || $arrow) {
$output .= '
';
}
return $output;
}
/**
* API function that wraps the text / html in help text, so if a user hovers over it
* the help text will show up
*
* @param string $table The table name for which the help should be shown
* @param string $field The field name for which the help should be shown
* @param string $text The text which should be wrapped with the help text
* @param array $overloadHelpText Array with text to overload help text
* @return string the HTML code ready to render
*/
public static function wrapInHelp($table, $field, $text = '', array $overloadHelpText = [])
{
// Initialize some variables
$helpText = '';
$abbrClassAdd = '';
$hasHelpTextOverload = !empty($overloadHelpText);
// Get the help text that should be shown on hover
if (!$hasHelpTextOverload) {
$helpText = self::helpText($table, $field);
}
// If there's a help text or some overload information, proceed with preparing an output
if (!empty($helpText) || $hasHelpTextOverload) {
// If no text was given, just use the regular help icon
if ($text == '') {
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
$text = $iconFactory->getIcon('actions-system-help-open', Icon::SIZE_SMALL)->render();
$abbrClassAdd = '-icon';
}
$text = '' . $text . '';
$wrappedText = '';
return $wrappedText;
}
return $text;
}
/**
* API for getting CSH icons/text for use in backend modules.
* TCA_DESCR will be loaded if it isn't already
*
* @param string $table Table name ('_MOD_'+module name)
* @param string $field Field name (CSH locallang main key)
* @param string $_ (unused)
* @param string $wrap Wrap code for icon-mode, splitted by "|". Not used for full-text mode.
* @return string HTML content for help text
*/
public static function cshItem($table, $field, $_ = '', $wrap = '')
{
static::getLanguageService()->loadSingleTableDescription($table);
if (is_array($GLOBALS['TCA_DESCR'][$table])
&& is_array($GLOBALS['TCA_DESCR'][$table]['columns'][$field])
) {
// Creating short description
$output = self::wrapInHelp($table, $field);
if ($output && $wrap) {
$wrParts = explode('|', $wrap);
$output = $wrParts[0] . $output . $wrParts[1];
}
return $output;
}
return '';
}
/**
* Returns a JavaScript string (for an onClick handler) which will load the EditDocumentController script that shows the form for editing of the record(s) you have send as params.
* REMEMBER to always htmlspecialchar() content in href-properties to ampersands get converted to entities (XHTML requirement and XSS precaution)
*
* @param string $params Parameters sent along to EditDocumentController. This requires a much more details description which you must seek in Inside TYPO3s documentation of the FormEngine API. And example could be '&edit[pages][123] = edit' which will show edit form for page record 123.
* @param string $_ (unused)
* @param string $requestUri An optional returnUrl you can set - automatically set to REQUEST_URI.
*
* @return string
*/
public static function editOnClick($params, $_ = '', $requestUri = '')
{
if ($requestUri == -1) {
$returnUrl = 'T3_THIS_LOCATION';
} else {
$returnUrl = GeneralUtility::quoteJSvalue(rawurlencode($requestUri ?: GeneralUtility::getIndpEnv('REQUEST_URI')));
}
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
return 'window.location.href=' . GeneralUtility::quoteJSvalue((string)$uriBuilder->buildUriFromRoute('record_edit') . $params . '&returnUrl=') . '+' . $returnUrl . '; return false;';
}
/**
* Returns a JavaScript string for viewing the page id, $id
* It will re-use any window already open.
*
* @param int $pageUid Page UID
* @param string $backPath Must point back to TYPO3_mainDir (where the site is assumed to be one level above)
* @param array|null $rootLine If root line is supplied the function will look for the first found domain record and use that URL instead (if found)
* @param string $anchorSection Optional anchor to the URL
* @param string $alternativeUrl An alternative URL that, if set, will ignore other parameters except $switchFocus: It will return the window.open command wrapped around this URL!
* @param string $additionalGetVars Additional GET variables.
* @param bool $switchFocus If TRUE, then the preview window will gain the focus.
* @return string
*/
public static function viewOnClick(
$pageUid,
$backPath = '',
$rootLine = null,
$anchorSection = '',
$alternativeUrl = '',
$additionalGetVars = '',
$switchFocus = true
) {
$previewUrl = self::getPreviewUrl(
$pageUid,
$backPath,
$rootLine,
$anchorSection,
$alternativeUrl,
$additionalGetVars,
$switchFocus
);
$onclickCode = 'var previewWin = window.open(' . GeneralUtility::quoteJSvalue($previewUrl) . ',\'newTYPO3frontendWindow\');'
. ($switchFocus ? 'previewWin.focus();' : '') . LF
. 'if (previewWin.location.href === ' . GeneralUtility::quoteJSvalue($previewUrl) . ') { previewWin.location.reload(); };';
return $onclickCode;
}
/**
* Returns the preview url
*
* It will detect the correct domain name if needed and provide the link with the right back path.
*
* @param int $pageUid Page UID
* @param string $backPath Must point back to TYPO3_mainDir (where the site is assumed to be one level above)
* @param array|null $rootLine If root line is supplied the function will look for the first found domain record and use that URL instead (if found)
* @param string $anchorSection Optional anchor to the URL
* @param string $alternativeUrl An alternative URL that, if set, will ignore other parameters except $switchFocus: It will return the window.open command wrapped around this URL!
* @param string $additionalGetVars Additional GET variables.
* @param bool $switchFocus If TRUE, then the preview window will gain the focus.
* @return string
*/
public static function getPreviewUrl(
$pageUid,
$backPath = '',
$rootLine = null,
$anchorSection = '',
$alternativeUrl = '',
$additionalGetVars = '',
&$switchFocus = true
): string {
$viewScript = '/index.php?id=';
if ($alternativeUrl) {
$viewScript = $alternativeUrl;
}
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['viewOnClickClass'] ?? [] as $className) {
$hookObj = GeneralUtility::makeInstance($className);
if (method_exists($hookObj, 'preProcess')) {
$hookObj->preProcess(
$pageUid,
$backPath,
$rootLine,
$anchorSection,
$viewScript,
$additionalGetVars,
$switchFocus
);
}
}
// If there is an alternative URL or the URL has been modified by a hook, use that one.
if ($alternativeUrl || $viewScript !== '/index.php?id=') {
$previewUrl = $viewScript;
} else {
$permissionClause = $GLOBALS['BE_USER']->getPagePermsClause(Permission::PAGE_SHOW);
$pageInfo = self::readPageAccess($pageUid, $permissionClause);
// prepare custom context for link generation (to allow for example time based previews)
$context = clone GeneralUtility::makeInstance(Context::class);
$additionalGetVars .= self::ADMCMD_previewCmds($pageInfo, $context);
// Build the URL with a site as prefix, if configured
$siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
// Check if the page (= its rootline) has a site attached, otherwise just keep the URL as is
$rootLine = $rootLine ?? BackendUtility::BEgetRootLine($pageUid);
try {
$site = $siteFinder->getSiteByPageId((int)$pageUid, $rootLine);
// Create a multi-dimensional array out of the additional get vars
$additionalQueryParams = [];
parse_str($additionalGetVars, $additionalQueryParams);
if (isset($additionalQueryParams['L'])) {
$additionalQueryParams['_language'] = $additionalQueryParams['_language'] ?? $additionalQueryParams['L'];
unset($additionalQueryParams['L']);
}
$previewUrl = (string)$site->getRouter($context)->generateUri(
$pageUid,
$additionalQueryParams,
$anchorSection,
RouterInterface::ABSOLUTE_URL
);
} catch (SiteNotFoundException | \InvalidArgumentException | InvalidRouteArgumentsException $e) {
$previewUrl = self::createPreviewUrl($pageUid, $rootLine, $anchorSection, $additionalGetVars, $viewScript);
}
}
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['viewOnClickClass'] ?? [] as $className) {
$hookObj = GeneralUtility::makeInstance($className);
if (method_exists($hookObj, 'postProcess')) {
$previewUrl = $hookObj->postProcess(
$previewUrl,
$pageUid,
$rootLine,
$anchorSection,
$viewScript,
$additionalGetVars,
$switchFocus
);
}
}
return $previewUrl;
}
/**
* Makes click menu link (context sensitive menu)
*
* Returns $str wrapped in a link which will activate the context sensitive
* menu for the record ($table/$uid) or file ($table = file)
* The link will load the top frame with the parameter "&item" which is the table, uid
* and context arguments imploded by "|": rawurlencode($table.'|'.$uid.'|'.$context)
*
* @param string $content String to be wrapped in link, typ. image tag.
* @param string $table Table name/File path. If the icon is for a database
* record, enter the tablename from $GLOBALS['TCA']. If a file then enter
* the absolute filepath
* @param int|string $uid If icon is for database record this is the UID for the
* record from $table or identifier for sys_file record
* @param string $context Set tree if menu is called from tree view
* @param string $_addParams NOT IN USE
* @param string $_enDisItems NOT IN USE
* @param bool $returnTagParameters If set, will return only the onclick
* JavaScript, not the whole link.
*
* @return string The link wrapped input string.
*/
public static function wrapClickMenuOnIcon(
$content,
$table,
$uid = 0,
$context = '',
$_addParams = '',
$_enDisItems = '',
$returnTagParameters = false
) {
$tagParameters = [
'class' => 't3js-contextmenutrigger',
'data-table' => $table,
'data-uid' => $uid,
'data-context' => $context
];
if ($returnTagParameters) {
return $tagParameters;
}
return '' . $content . '';
}
/**
* Returns a URL with a command to TYPO3 Datahandler
*
* @param string $parameters Set of GET params to send. Example: "&cmd[tt_content][123][move]=456" or "&data[tt_content][123][hidden]=1&data[tt_content][123][title]=Hello%20World
* @param string|int $redirectUrl Redirect URL, default is to use GeneralUtility::getIndpEnv('REQUEST_URI'), -1 means to generate an URL for JavaScript using T3_THIS_LOCATION
* @return string URL to BackendUtility::getModuleUrl('tce_db') + parameters
*/
public static function getLinkToDataHandlerAction($parameters, $redirectUrl = '')
{
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
$url = (string)$uriBuilder->buildUriFromRoute('tce_db') . $parameters . '&redirect=';
if ((int)$redirectUrl === -1) {
$url = GeneralUtility::quoteJSvalue($url) . '+T3_THIS_LOCATION';
} else {
$url .= rawurlencode($redirectUrl ?: GeneralUtility::getIndpEnv('REQUEST_URI'));
}
return $url;
}
/**
* Creates the view-on-click preview URL without any alternative URL.
*
* @param int $pageUid Page UID
* @param array $rootLine If rootline is supplied, the function will look for the first found domain record and use that URL instead
* @param string $anchorSection Optional anchor to the URL
* @param string $additionalGetVars Additional GET variables.
* @param string $viewScript The path to the script used to view the page
*
* @return string The preview URL
*/
protected static function createPreviewUrl($pageUid, $rootLine, $anchorSection, $additionalGetVars, $viewScript)
{
// Look if a fixed preview language should be added:
$beUser = static::getBackendUserAuthentication();
$viewLanguageOrder = (string)($beUser->getTSConfig()['options.']['view.']['languageOrder'] ?? '');
if (!empty($viewLanguageOrder)) {
$suffix = '';
// Find allowed languages (if none, all are allowed!)
$allowedLanguages = null;
if (!$beUser->isAdmin() && $beUser->groupData['allowed_languages'] !== '') {
$allowedLanguages = array_flip(explode(',', $beUser->groupData['allowed_languages']));
}
// Traverse the view order, match first occurrence:
$languageOrder = GeneralUtility::intExplode(',', $viewLanguageOrder);
foreach ($languageOrder as $langUid) {
if (is_array($allowedLanguages) && !empty($allowedLanguages)) {
// Choose if set.
if (isset($allowedLanguages[$langUid])) {
$suffix = '&L=' . $langUid;
break;
}
} else {
// All allowed since no lang. are listed.
$suffix = '&L=' . $langUid;
break;
}
}
// Add it
$additionalGetVars .= $suffix;
}
// Check a mount point needs to be previewed
$pageRepository = GeneralUtility::makeInstance(PageRepository::class);
$mountPointInfo = $pageRepository->getMountPointInfo($pageUid);
if ($mountPointInfo && $mountPointInfo['overlay']) {
$pageUid = $mountPointInfo['mount_pid'];
$additionalGetVars .= '&MP=' . $mountPointInfo['MPvar'];
}
$viewDomain = self::getViewDomain($pageUid, $rootLine);
return $viewDomain . $viewScript . $pageUid . $additionalGetVars . $anchorSection;
}
/**
* Builds the frontend view domain for a given page ID with a given root
* line.
*
* @param int $pageId The page ID to use, must be > 0
* @param array|null $rootLine The root line structure to use
* @return string The full domain including the protocol http:// or https://, but without the trailing '/'
*/
public static function getViewDomain($pageId, $rootLine = null)
{
$domain = rtrim(GeneralUtility::getIndpEnv('TYPO3_SITE_URL'), '/');
if (!is_array($rootLine)) {
$rootLine = self::BEgetRootLine($pageId);
}
// Checks alternate domains
if (!empty($rootLine)) {
$protocol = GeneralUtility::getIndpEnv('TYPO3_SSL') ? 'https' : 'http';
$previewDomainConfig = self::getPagesTSconfig($pageId)['TCEMAIN.']['previewDomain'] ?? '';
$domainName = null;
if (!empty($previewDomainConfig)) {
if (strpos($previewDomainConfig, '://') !== false) {
list($protocol, $domainName) = explode('://', $previewDomainConfig);
} else {
$domainName = $previewDomainConfig;
}
} else {
$domainResolver = GeneralUtility::makeInstance(LegacyDomainResolver::class);
foreach ($rootLine as $row) {
$domainRecord = $domainResolver->matchRootPageId((int)$row['uid']);
if (is_array($domainRecord)) {
$domainName = rtrim($domainRecord['domainName'], '/');
break;
}
}
}
if ($domainName === null) {
// Fetch the "sys_domain" record: First, check for the given domain,
// and find the "root page" = PseudoSite to that domain, then fetch the first
// available sys_domain record.
$siteMatcher = GeneralUtility::makeInstance(SiteMatcher::class);
$result = $siteMatcher->matchRequest(new ServerRequest($domain));
$site = $result->getSite();
if ($site instanceof PseudoSite) {
$domainName = (string)$site->getBase();
$domainName = ltrim($domainName, '/');
}
}
if ($domainName) {
$domain = $protocol . '://' . $domainName;
}
// Append port number if lockSSLPort is not the standard port 443
$portNumber = (int)$GLOBALS['TYPO3_CONF_VARS']['BE']['lockSSLPort'];
if ($portNumber > 0 && $portNumber !== 443 && $portNumber < 65536 && $protocol === 'https') {
$domain .= ':' . strval($portNumber);
}
}
return $domain;
}
/**
* Returns the merged User/Page TSconfig for page id, $id.
* Please read details about module programming elsewhere!
*
* @param int $id Page uid
* @param string $TSref An object string which determines the path of the TSconfig to return.
* @return array
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, use getPagesTSconfig() instead
*/
public static function getModTSconfig($id, $TSref)
{
trigger_error(
'BackendUtility::getModTSconfig() will be removed in TYPO3 v10.0.'
. ' Use BackendUtility::getPagesTSconfig() to retrieve the full page TSconfig array instead.',
E_USER_DEPRECATED
);
$beUser = static::getBackendUserAuthentication();
$pageTS_modOptions = $beUser->getTSConfig($TSref, static::getPagesTSconfig($id));
$BE_USER_modOptions = $beUser->getTSConfig($TSref);
if ($BE_USER_modOptions['value'] === null) {
unset($BE_USER_modOptions['value']);
}
ArrayUtility::mergeRecursiveWithOverrule($pageTS_modOptions, $BE_USER_modOptions);
return $pageTS_modOptions;
}
/**
* Returns a selector box "function menu" for a module
* Requires the JS function jumpToUrl() to be available
* See Inside TYPO3 for details about how to use / make Function menus
*
* @param mixed $mainParams The "&id=" parameter value to be sent to the module, but it can be also a parameter array which will be passed instead of the &id=...
* @param string $elementName The form elements name, probably something like "SET[...]
* @param string $currentValue The value to be selected currently.
* @param array $menuItems An array with the menu items for the selector box
* @param string $script The script to send the &id to, if empty it's automatically found
* @param string $addParams Additional parameters to pass to the script.
* @return string HTML code for selector box
*/
public static function getFuncMenu(
$mainParams,
$elementName,
$currentValue,
$menuItems,
$script = '',
$addParams = ''
) {
if (!is_array($menuItems) || count($menuItems) <= 1) {
return '';
}
$scriptUrl = self::buildScriptUrl($mainParams, $addParams, $script);
$options = [];
foreach ($menuItems as $value => $label) {
$options[] = '';
}
$dataMenuIdentifier = str_replace(['SET[', ']'], '', $elementName);
$dataMenuIdentifier = GeneralUtility::camelCaseToLowerCaseUnderscored($dataMenuIdentifier);
$dataMenuIdentifier = str_replace('_', '-', $dataMenuIdentifier);
if (!empty($options)) {
$onChange = 'jumpToUrl(' . GeneralUtility::quoteJSvalue($scriptUrl . '&' . $elementName . '=') . '+this.options[this.selectedIndex].value,this);';
return '
';
}
return '';
}
/**
* Returns a selector box to switch the view
* Requires the JS function jumpToUrl() to be available
* Based on BackendUtility::getFuncMenu() but done as new function because it has another purpose.
* Mingling with getFuncMenu would harm the docHeader Menu.
*
* @param mixed $mainParams The "&id=" parameter value to be sent to the module, but it can be also a parameter array which will be passed instead of the &id=...
* @param string $elementName The form elements name, probably something like "SET[...]
* @param string $currentValue The value to be selected currently.
* @param array $menuItems An array with the menu items for the selector box
* @param string $script The script to send the &id to, if empty it's automatically found
* @param string $addParams Additional parameters to pass to the script.
* @return string HTML code for selector box
*/
public static function getDropdownMenu(
$mainParams,
$elementName,
$currentValue,
$menuItems,
$script = '',
$addParams = ''
) {
if (!is_array($menuItems) || count($menuItems) <= 1) {
return '';
}
$scriptUrl = self::buildScriptUrl($mainParams, $addParams, $script);
$options = [];
foreach ($menuItems as $value => $label) {
$options[] = '';
}
$dataMenuIdentifier = str_replace(['SET[', ']'], '', $elementName);
$dataMenuIdentifier = GeneralUtility::camelCaseToLowerCaseUnderscored($dataMenuIdentifier);
$dataMenuIdentifier = str_replace('_', '-', $dataMenuIdentifier);
if (!empty($options)) {
$onChange = 'jumpToUrl(' . GeneralUtility::quoteJSvalue($scriptUrl . '&' . $elementName . '=') . '+this.options[this.selectedIndex].value,this);';
return '