‪TYPO3CMS  ‪main
BackendUtility.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
18 use Doctrine\DBAL\Statement;
19 use Doctrine\DBAL\Types\Type;
20 use Psr\Log\LoggerInterface;
30 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
39 use TYPO3\CMS\Core\Imaging\IconSize;
58 
67 class BackendUtility
68 {
69  /*******************************************
70  *
71  * SQL-related, selecting records, searching
72  *
73  *******************************************/
88  public static function getRecord($table, ‪$uid, ‪$fields = '*', $where = '', $useDeleteClause = true): ?array
89  {
90  // Ensure we have a valid uid (not 0 and not NEWxxxx) and a valid TCA
91  if ((int)‪$uid && !empty(‪$GLOBALS['TCA'][$table])) {
92  $queryBuilder = static::getQueryBuilderForTable($table);
93 
94  // do not use enabled fields here
95  $queryBuilder->getRestrictions()->removeAll();
96 
97  // should the delete clause be used
98  if ($useDeleteClause) {
99  $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
100  }
101 
102  // set table and where clause
103  $queryBuilder
104  ->select(...‪GeneralUtility::trimExplode(',', ‪$fields, true))
105  ->from($table)
106  ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter((int)‪$uid, ‪Connection::PARAM_INT)));
107 
108  // add custom where clause
109  if ($where) {
110  $queryBuilder->andWhere(‪QueryHelper::stripLogicalOperatorPrefix($where));
111  }
112 
113  $row = $queryBuilder->executeQuery()->fetchAssociative();
114  if ($row) {
115  return $row;
116  }
117  }
118  return null;
119  }
120 
132  public static function getRecordWSOL(
133  $table,
134  ‪$uid,
135  ‪$fields = '*',
136  $where = '',
137  $useDeleteClause = true,
138  $unsetMovePointers = false
139  ): ?array {
140  if (‪$fields !== '*') {
141  $internalFields = ‪StringUtility::uniqueList(‪$fields . ',uid,pid');
142  $row = self::getRecord($table, ‪$uid, $internalFields, $where, $useDeleteClause);
143  self::workspaceOL($table, $row, -99, $unsetMovePointers);
144  if (is_array($row)) {
145  foreach ($row as $key => $_) {
146  if (!‪GeneralUtility::inList(‪$fields, $key) && $key[0] !== '_') {
147  unset($row[$key]);
148  }
149  }
150  }
151  } else {
152  $row = self::getRecord($table, ‪$uid, ‪$fields, $where, $useDeleteClause);
153  self::workspaceOL($table, $row, -99, $unsetMovePointers);
154  }
155  return $row;
156  }
157 
165  public static function purgeComputedPropertiesFromRecord(array ‪$record): array
166  {
167  return array_filter(
168  ‪$record,
169  static function (string $propertyName): bool {
170  return $propertyName[0] !== '_';
171  },
172  ARRAY_FILTER_USE_KEY
173  );
174  }
175 
181  public static function purgeComputedPropertyNames(array $propertyNames): array
182  {
183  return array_filter(
184  $propertyNames,
185  static function (string $propertyName): bool {
186  return $propertyName[0] !== '_';
187  }
188  );
189  }
190 
199  public static function splitTable_Uid($str)
200  {
201  $split = explode('_', strrev($str), 2);
202  ‪$uid = $split[0];
203  $table = $split[1] ?? '';
204  return [strrev($table), strrev(‪$uid)];
205  }
206 
218  public static function BEenableFields($table, $inv = false)
219  {
220  $ctrl = ‪$GLOBALS['TCA'][$table]['ctrl'] ?? [];
221  $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
222  ->getConnectionForTable($table)
223  ->getExpressionBuilder();
224  $query = $expressionBuilder->and();
225  $invQuery = $expressionBuilder->or();
226 
227  $ctrl += [
228  'enablecolumns' => [],
229  ];
230 
231  if (is_array($ctrl)) {
232  if ($ctrl['enablecolumns']['disabled'] ?? false) {
233  $field = $table . '.' . $ctrl['enablecolumns']['disabled'];
234  $query = $query->with($expressionBuilder->eq($field, 0));
235  $invQuery = $invQuery->with($expressionBuilder->neq($field, 0));
236  }
237  if ($ctrl['enablecolumns']['starttime'] ?? false) {
238  $field = $table . '.' . $ctrl['enablecolumns']['starttime'];
239  $query = $query->with($expressionBuilder->lte($field, (int)‪$GLOBALS['SIM_ACCESS_TIME']));
240  $invQuery = $invQuery->with(
241  $expressionBuilder->and(
242  $expressionBuilder->neq($field, 0),
243  $expressionBuilder->gt($field, (int)‪$GLOBALS['SIM_ACCESS_TIME'])
244  )
245  );
246  }
247  if ($ctrl['enablecolumns']['endtime'] ?? false) {
248  $field = $table . '.' . $ctrl['enablecolumns']['endtime'];
249  $query = $query->with(
250  $expressionBuilder->or(
251  $expressionBuilder->eq($field, 0),
252  $expressionBuilder->gt($field, (int)‪$GLOBALS['SIM_ACCESS_TIME'])
253  )
254  );
255  $invQuery = $invQuery->with(
256  $expressionBuilder->and(
257  $expressionBuilder->neq($field, 0),
258  $expressionBuilder->lte($field, (int)‪$GLOBALS['SIM_ACCESS_TIME'])
259  )
260  );
261  }
262  }
263 
264  if ($query->count() === 0) {
265  return '';
266  }
267 
268  return ' AND ' . ($inv ? $invQuery : $query);
269  }
270 
280  public static function getRecordLocalization($table, ‪$uid, $language, $andWhereClause = '')
281  {
282  $recordLocalization = false;
283 
284  if (self::isTableLocalizable($table)) {
285  $tcaCtrl = ‪$GLOBALS['TCA'][$table]['ctrl'];
286 
287  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
288  ->getQueryBuilderForTable($table);
289  $queryBuilder->getRestrictions()
290  ->removeAll()
291  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
292  ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, static::getBackendUserAuthentication()->workspace));
293 
294  $queryBuilder->select('*')
295  ->from($table)
296  ->where(
297  $queryBuilder->expr()->eq(
298  $tcaCtrl['translationSource'] ?? $tcaCtrl['transOrigPointerField'],
299  $queryBuilder->createNamedParameter(‪$uid, ‪Connection::PARAM_INT)
300  ),
301  $queryBuilder->expr()->eq(
302  $tcaCtrl['languageField'],
303  $queryBuilder->createNamedParameter((int)$language, ‪Connection::PARAM_INT)
304  )
305  )
306  ->setMaxResults(1);
307 
308  if ($andWhereClause) {
309  $queryBuilder->andWhere(‪QueryHelper::stripLogicalOperatorPrefix($andWhereClause));
310  }
311 
312  $recordLocalization = $queryBuilder->executeQuery()->fetchAllAssociative();
313  }
314 
315  return $recordLocalization;
316  }
317 
318  /*******************************************
319  *
320  * Page tree, TCA related
321  *
322  *******************************************/
338  public static function BEgetRootLine(‪$uid, $clause = '', $workspaceOL = false, array $additionalFields = [])
339  {
340  $runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');
341  $beGetRootLineCache = $runtimeCache->get('backendUtilityBeGetRootLine') ?: [];
342  ‪$output = [];
343  $pid = ‪$uid;
344  $ident = $pid . '-' . $clause . '-' . $workspaceOL . ($additionalFields ? '-' . md5(implode(',', $additionalFields)) : '');
345  if (is_array($beGetRootLineCache[$ident] ?? false)) {
346  ‪$output = $beGetRootLineCache[$ident];
347  } else {
348  $loopCheck = 100;
349  $theRowArray = [];
350  while (‪$uid != 0 && $loopCheck) {
351  $loopCheck--;
352  $row = self::getPageForRootline(‪$uid, $clause, $workspaceOL, $additionalFields);
353  if (is_array($row)) {
354  ‪$uid = $row['pid'];
355  $theRowArray[] = $row;
356  } else {
357  break;
358  }
359  }
360  ‪$fields = [
361  'uid',
362  'pid',
363  'title',
364  'doktype',
365  'slug',
366  'tsconfig_includes',
367  'TSconfig',
368  'is_siteroot',
369  't3ver_oid',
370  't3ver_wsid',
371  't3ver_state',
372  't3ver_stage',
373  'backend_layout',
374  'backend_layout_next_level',
375  'hidden',
376  'starttime',
377  'endtime',
378  'fe_group',
379  'nav_hide',
380  'content_from_pid',
381  'module',
382  'extendToSubpages',
383  ];
384  ‪$fields = array_merge(‪$fields, $additionalFields);
385  $rootPage = array_fill_keys(‪$fields, null);
386  if (‪$uid == 0) {
387  $rootPage['uid'] = 0;
388  $theRowArray[] = $rootPage;
389  }
390  $c = count($theRowArray);
391  foreach ($theRowArray as $val) {
392  $c--;
393  ‪$output[$c] = array_intersect_key($val, $rootPage);
394  if (isset($val['_ORIG_pid'])) {
395  ‪$output[$c]['_ORIG_pid'] = $val['_ORIG_pid'];
396  }
397  }
398  $beGetRootLineCache[$ident] = ‪$output;
399  $runtimeCache->set('backendUtilityBeGetRootLine', $beGetRootLineCache);
400  }
401  return ‪$output;
402  }
403 
414  protected static function getPageForRootline(‪$uid, $clause, $workspaceOL, array $additionalFields = [])
415  {
416  $runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');
417  $pageForRootlineCache = $runtimeCache->get('backendUtilityPageForRootLine') ?: [];
418  $statementCacheIdent = md5($clause . ($additionalFields ? '-' . implode(',', $additionalFields) : ''));
419  $ident = ‪$uid . '-' . $workspaceOL . '-' . $statementCacheIdent;
420  if (is_array($pageForRootlineCache[$ident] ?? false)) {
421  $row = $pageForRootlineCache[$ident];
422  } else {
424  $statement = $runtimeCache->get('getPageForRootlineStatement-' . $statementCacheIdent);
425  if (!$statement) {
426  $queryBuilder = static::getQueryBuilderForTable('pages');
427  $queryBuilder->getRestrictions()
428  ->removeAll()
429  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
430 
431  $queryBuilder
432  ->select(
433  'pid',
434  'uid',
435  'title',
436  'doktype',
437  'slug',
438  'tsconfig_includes',
439  'TSconfig',
440  'is_siteroot',
441  't3ver_oid',
442  't3ver_wsid',
443  't3ver_state',
444  't3ver_stage',
445  'backend_layout',
446  'backend_layout_next_level',
447  'hidden',
448  'starttime',
449  'endtime',
450  'fe_group',
451  'nav_hide',
452  'content_from_pid',
453  'module',
454  'extendToSubpages',
455  ...$additionalFields
456  )
457  ->from('pages')
458  ->where(
459  $queryBuilder->expr()->eq('uid', $queryBuilder->createPositionalParameter(‪$uid, ‪Connection::PARAM_INT)),
461  );
462  $statement = $queryBuilder->prepare();
463  $runtimeCache->set('getPageForRootlineStatement-' . $statementCacheIdent, $statement);
464  }
465 
466  $statement->bindValue(1, (int)‪$uid, ‪Connection::PARAM_INT);
467  $result = $statement->executeQuery();
468  $row = $result->fetchAssociative();
469  $result->free();
470 
471  if ($row) {
472  if ($workspaceOL) {
473  self::workspaceOL('pages', $row);
474  }
475  if (is_array($row)) {
476  $pageForRootlineCache[$ident] = $row;
477  $runtimeCache->set('backendUtilityPageForRootLine', $pageForRootlineCache);
478  }
479  }
480  }
481  return $row;
482  }
483 
490  public static function getExistingPageTranslations(int $pageUid): array
491  {
492  if ($pageUid === 0) {
493  return [];
494  }
495  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
496  $queryBuilder->getRestrictions()->removeAll()
497  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
498  ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, self::getBackendUserAuthentication()->workspace));
499  $result = $queryBuilder
500  ->select('*')
501  ->from('pages')
502  ->where(
503  $queryBuilder->expr()->eq(
504  ‪$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
505  $queryBuilder->createNamedParameter($pageUid, ‪Connection::PARAM_INT)
506  )
507  )
508  ->executeQuery();
509 
510  $rows = [];
511  while ($row = $result->fetchAssociative()) {
512  BackendUtility::workspaceOL('pages', $row, self::getBackendUserAuthentication()->workspace);
513  if ($row && VersionState::tryFrom($row['t3ver_state']) !== VersionState::DELETE_PLACEHOLDER) {
514  $rows[] = $row;
515  }
516  }
517  return $rows;
518  }
519 
527  public static function openPageTree($pid, $clearExpansion)
528  {
529  $beUser = static::getBackendUserAuthentication();
530  // Get current expansion data:
531  if ($clearExpansion) {
532  $expandedPages = [];
533  } else {
534  $expandedPages = $beUser->uc['BackendComponents']['States']['Pagetree']['stateHash'] ?? [];
535  }
536  // Get rootline:
537  $rL = self::BEgetRootLine($pid);
538  // First, find out what mount index to use (if more than one DB mount exists):
539  $mountIndex = 0;
540  $mountKeys = $beUser->returnWebmounts();
541 
542  foreach ($rL as $rLDat) {
543  if (isset($mountKeys[$rLDat['uid']])) {
544  $mountIndex = $mountKeys[$rLDat['uid']];
545  break;
546  }
547  }
548  // Traverse rootline and open paths:
549  foreach ($rL as $rLDat) {
550  $expandedPages[$mountIndex . '_' . $rLDat['uid']] = '1';
551  }
552  // Write back:
553  $beUser->uc['BackendComponents']['States']['Pagetree']['stateHash'] = $expandedPages;
554  $beUser->writeUC();
555  }
556 
568  public static function getRecordPath(‪$uid, $clause, $titleLimit, $fullTitleLimit = 0)
569  {
570  if (!$titleLimit) {
571  $titleLimit = 1000;
572  }
573  ‪$output = $fullOutput = '/';
574  $clause = trim($clause);
575  if ($clause !== '' && !str_starts_with($clause, 'AND')) {
576  $clause = 'AND ' . $clause;
577  }
578  $data = self::BEgetRootLine(‪$uid, $clause, true);
579  foreach ($data as ‪$record) {
580  if (‪$record['uid'] === 0) {
581  continue;
582  }
583  ‪$output = '/' . ‪GeneralUtility::fixed_lgd_cs(strip_tags(‪$record['title']), (int)$titleLimit) . ‪$output;
584  if ($fullTitleLimit) {
585  $fullOutput = '/' . ‪GeneralUtility::fixed_lgd_cs(strip_tags(‪$record['title']), (int)$fullTitleLimit) . $fullOutput;
586  }
587  }
588  if ($fullTitleLimit) {
589  return [‪$output, $fullOutput];
590  }
591  return ‪$output;
592  }
593 
600  public static function isTableLocalizable($table)
601  {
602  $isLocalizable = false;
603  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']) && is_array(‪$GLOBALS['TCA'][$table]['ctrl'])) {
604  $tcaCtrl = ‪$GLOBALS['TCA'][$table]['ctrl'];
605  $isLocalizable = isset($tcaCtrl['languageField']) && $tcaCtrl['languageField'] && isset($tcaCtrl['transOrigPointerField']) && $tcaCtrl['transOrigPointerField'];
606  }
607  return $isLocalizable;
608  }
609 
621  public static function readPageAccess($id, $perms_clause)
622  {
623  if ((string)$id !== '') {
624  $id = (int)$id;
625  if (!$id) {
626  if (static::getBackendUserAuthentication()->isAdmin()) {
627  return ['_thePath' => '/'];
628  }
629  } else {
630  $pageinfo = self::getRecord('pages', $id, '*', $perms_clause);
631  if (($pageinfo['uid'] ?? false) && static::getBackendUserAuthentication()->isInWebMount($pageinfo, $perms_clause)) {
632  self::workspaceOL('pages', $pageinfo);
633  if (is_array($pageinfo)) {
634  [$pageinfo['_thePath'], $pageinfo['_thePathFull']] = self::getRecordPath((int)$pageinfo['uid'], $perms_clause, 15, 1000);
635  return $pageinfo;
636  }
637  }
638  }
639  }
640  return false;
641  }
642 
659  public static function getTCAtypeValue($table, $row)
660  {
661  $typeNum = 0;
662  if (‪$GLOBALS['TCA'][$table] ?? false) {
663  $field = ‪$GLOBALS['TCA'][$table]['ctrl']['type'] ?? '';
664  if (str_contains($field, ':')) {
665  [$pointerField, $foreignTableTypeField] = explode(':', $field);
666  // Check if the record has been persisted already
667  $foreignUid = 0;
668  if (isset($row['uid'])) {
669  // Get field value from database if field is not in the $row array
670  if (!isset($row[$pointerField])) {
671  $localRow = self::getRecord($table, $row['uid'], $pointerField);
672  $foreignUid = $localRow[$pointerField] ?? 0;
673  } else {
674  $foreignUid = $row[$pointerField];
675  }
676  }
677  if ($foreignUid) {
678  $fieldConfig = ‪$GLOBALS['TCA'][$table]['columns'][$pointerField]['config'];
679  $relationType = $fieldConfig['type'];
680  if ($relationType === 'select' || $relationType === 'category') {
681  $foreignTable = $fieldConfig['foreign_table'];
682  } elseif ($relationType === 'group') {
683  $allowedTables = explode(',', $fieldConfig['allowed']);
684  $foreignTable = $allowedTables[0];
685  } else {
686  throw new \RuntimeException(
687  'TCA foreign field pointer fields are only allowed to be used with group or select field types.',
688  1325862240
689  );
690  }
691  $foreignRow = self::getRecord($foreignTable, $foreignUid, $foreignTableTypeField);
692  if ($foreignRow[$foreignTableTypeField] ?? false) {
693  $typeNum = $foreignRow[$foreignTableTypeField];
694  }
695  }
696  } else {
697  $typeNum = $row[$field] ?? 0;
698  }
699  // If that value is an empty string, set it to "0" (zero)
700  if (empty($typeNum)) {
701  $typeNum = 0;
702  }
703  }
704  // If current typeNum doesn't exist, set it to 0 (or to 1 for historical reasons, if 0 doesn't exist)
705  if (!isset(‪$GLOBALS['TCA'][$table]['types'][$typeNum]) || !‪$GLOBALS['TCA'][$table]['types'][$typeNum]) {
706  $typeNum = isset(‪$GLOBALS['TCA'][$table]['types']['0']) ? 0 : 1;
707  }
708  // Force to string. Necessary for eg '-1' to be recognized as a type value.
709  $typeNum = (string)$typeNum;
710  return $typeNum;
711  }
712 
713  /*******************************************
714  *
715  * TypoScript related
716  *
717  *******************************************/
718 
741  public static function getPagesTSconfig($pageUid): array
742  {
743  $runtimeCache = static::getRuntimeCache();
744  $pageTsConfigHash = $runtimeCache->get('pageTsConfig-pid-to-hash-' . $pageUid);
745  if ($pageTsConfigHash) {
746  $pageTsConfig = $runtimeCache->get('pageTsConfig-hash-to-object-' . $pageTsConfigHash);
747  if ($pageTsConfig instanceof PageTsConfig) {
748  return $pageTsConfig->getPageTsConfigArray();
749  }
750  }
751 
752  $pageUid = (int)$pageUid;
753  $fullRootLine = self::BEgetRootLine($pageUid, '', true);
754  // Order correctly
755  ksort($fullRootLine);
756 
757  try {
758  $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($pageUid);
759  } catch (SiteNotFoundException) {
760  $site = new NullSite();
761  }
762 
763  $cacheRelevantData = $site->getIdentifier();
764  foreach ($fullRootLine as $rootLine) {
765  if (!empty($rootLine['TSconfig'])) {
766  $cacheRelevantData .= (string)$rootLine['TSconfig'];
767  }
768  if (!empty($rootLine['tsconfig_includes'])) {
769  $cacheRelevantData .= (string)$rootLine['tsconfig_includes'];
770  }
771  }
772  $cacheRelevantDataHash = hash('xxh3', $cacheRelevantData);
773 
774  $pageTsConfig = $runtimeCache->get('pageTsConfig-hash-to-object-' . $cacheRelevantDataHash);
775  if ($pageTsConfig instanceof PageTsConfig) {
776  $runtimeCache->set('pageTsConfig-pid-to-hash-' . $pageUid, $cacheRelevantDataHash);
777  return $pageTsConfig->getPageTsConfigArray();
778  }
779 
780  $pageTsConfigFactory = GeneralUtility::makeInstance(PageTsConfigFactory::class);
781  $pageTsConfig = $pageTsConfigFactory->create($fullRootLine, $site, static::getBackendUserAuthentication()?->getUserTsConfig());
782 
783  $runtimeCache->set('pageTsConfig-pid-to-hash-' . $pageUid, $cacheRelevantDataHash);
784  $runtimeCache->set('pageTsConfig-hash-to-object-' . $cacheRelevantDataHash, $pageTsConfig);
785  return $pageTsConfig->getPageTsConfigArray();
786  }
787 
788  /*******************************************
789  *
790  * Users / Groups related
791  *
792  *******************************************/
802  public static function getUserNames(‪$fields = 'username,usergroup,uid', $where = '')
803  {
804  return self::getRecordsSortedByTitle(
806  'be_users',
807  'username',
808  'AND pid=0 ' . $where
809  );
810  }
811 
820  public static function getGroupNames(‪$fields = 'title,uid', $where = '')
821  {
822  return self::getRecordsSortedByTitle(
824  'be_groups',
825  'title',
826  'AND pid=0 ' . $where
827  );
828  }
829 
841  protected static function getRecordsSortedByTitle(array ‪$fields, $table, $titleField, $where = '')
842  {
843  $fieldsIndex = array_flip(‪$fields);
844  // Make sure the titleField is amongst the fields when getting sorted
845  $fieldsIndex[$titleField] = 1;
846 
847  $result = [];
848 
849  $queryBuilder = static::getQueryBuilderForTable($table);
850  $queryBuilder->getRestrictions()
851  ->removeAll()
852  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
853 
854  $res = $queryBuilder
855  ->select('*')
856  ->from($table)
858  ->executeQuery();
859 
860  while (‪$record = $res->fetchAssociative()) {
861  // store the uid, because it might be unset if it's not among the requested $fields
862  $recordId = (int)‪$record['uid'];
863  ‪$record[$titleField] = self::getRecordTitle($table, ‪$record);
864 
865  // include only the requested fields in the result
866  $result[$recordId] = array_intersect_key(‪$record, $fieldsIndex);
867  }
868 
869  // sort records by $sortField. This is not done in the query because the title might have been overwritten by
870  // self::getRecordTitle();
871  return ‪ArrayUtility::sortArraysByKey($result, $titleField);
872  }
873 
874  /*******************************************
875  *
876  * Output related
877  *
878  *******************************************/
885  public static function daysUntil($tstamp)
886  {
887  $delta_t = $tstamp - ‪$GLOBALS['EXEC_TIME'];
888  return ceil($delta_t / (3600 * 24));
889  }
890 
897  public static function date($tstamp)
898  {
899  return date(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], (int)$tstamp);
900  }
901 
908  public static function datetime($value)
909  {
910  return date(
911  ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'],
912  $value
913  );
914  }
915 
924  public static function time($value, $withSeconds = true)
925  {
926  return gmdate('H:i' . ($withSeconds ? ':s' : ''), (int)$value);
927  }
928 
936  public static function calcAge($seconds, $labels = 'min|hrs|days|yrs|min|hour|day|year')
937  {
938  $labelArr = ‪GeneralUtility::trimExplode('|', $labels, true);
939  $absSeconds = abs($seconds);
940  $sign = $seconds < 0 ? -1 : 1;
941  if ($absSeconds < 3600) {
942  $val = round($absSeconds / 60);
943  $seconds = $sign * $val . ' ' . ($val == 1 ? $labelArr[4] : $labelArr[0]);
944  } elseif ($absSeconds < 24 * 3600) {
945  $val = round($absSeconds / 3600);
946  $seconds = $sign * $val . ' ' . ($val == 1 ? $labelArr[5] : $labelArr[1]);
947  } elseif ($absSeconds < 365 * 24 * 3600) {
948  $val = round($absSeconds / (24 * 3600));
949  $seconds = $sign * $val . ' ' . ($val == 1 ? $labelArr[6] : $labelArr[2]);
950  } else {
951  $val = round($absSeconds / (365 * 24 * 3600));
952  $seconds = $sign * $val . ' ' . ($val == 1 ? $labelArr[7] : $labelArr[3]);
953  }
954  return $seconds;
955  }
956 
966  public static function dateTimeAge($tstamp, $prefix = 1, $date = '')
967  {
968  if (!$tstamp) {
969  return '';
970  }
971  $label = static::getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears');
972  $age = ' (' . self::calcAge($prefix * (‪$GLOBALS['EXEC_TIME'] - $tstamp), $label) . ')';
973  return ($date === 'date' ? self::date($tstamp) : self::datetime($tstamp)) . $age;
974  }
975 
985  public static function resolveFileReferences($tableName, $fieldName, $element, $workspaceId = null)
986  {
987  if (empty(‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'])) {
988  return null;
989  }
990  $configuration = ‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
991  if (($configuration['type'] ?? '') !== 'file') {
992  return null;
993  }
994 
995  $fileReferences = [];
996  $relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
997  if ($workspaceId !== null) {
998  $relationHandler->setWorkspaceId($workspaceId);
999  }
1000  $relationHandler->start(
1001  $element[$fieldName],
1002  $configuration['foreign_table'],
1003  $configuration['MM'] ?? '',
1004  $element['uid'],
1005  $tableName,
1006  $configuration
1007  );
1008  $relationHandler->processDeletePlaceholder();
1009  $referenceUids = $relationHandler->tableArray[$configuration['foreign_table']];
1010 
1011  foreach ($referenceUids as $referenceUid) {
1012  try {
1013  $fileReference = GeneralUtility::makeInstance(ResourceFactory::class)->getFileReferenceObject(
1014  $referenceUid,
1015  [],
1016  $workspaceId === 0
1017  );
1018  $fileReferences[$fileReference->getUid()] = $fileReference;
1019  } catch (FileDoesNotExistException $e) {
1024  } catch (\InvalidArgumentException $e) {
1029  self::getLogger()->error($e->getMessage(), [
1030  'table' => $tableName,
1031  'fieldName' => $fieldName,
1032  'referenceUid' => $referenceUid,
1033  'exception' => $e,
1034  ]);
1035  }
1036  }
1037 
1038  return $fileReferences;
1039  }
1040 
1058  public static function thumbCode(
1059  $row,
1060  $table,
1061  $field,
1062  $backPath = '',
1063  $thumbScript = '',
1064  $uploaddir = null,
1065  $abs = 0,
1066  $tparams = '',
1067  $size = '',
1068  $linkInfoPopup = true
1069  ) {
1070  $size = (int)(trim((string)$size) ?: 64);
1071  $targetDimension = new ImageDimension($size, $size);
1072  $thumbData = '';
1073  $fileReferences = static::resolveFileReferences($table, $field, $row);
1074  // FAL references
1075  $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
1076  if ($fileReferences !== null) {
1077  foreach ($fileReferences as $fileReferenceObject) {
1078  // Do not show previews of hidden references
1079  if ($fileReferenceObject->getProperty('hidden')) {
1080  continue;
1081  }
1082  $fileObject = $fileReferenceObject->getOriginalFile();
1083 
1084  if ($fileObject->isMissing()) {
1085  $thumbData .= ''
1086  . '<div class="preview-thumbnails-element">'
1087  . '<div class="preview-thumbnails-element-image">'
1088  . $iconFactory
1089  ->getIcon('mimetypes-other-other', IconSize::MEDIUM, 'overlay-missing')
1090  ->setTitle(static::getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.file_missing') . ' ' . $fileObject->getName())
1091  ->render()
1092  . '</div>'
1093  . '</div>';
1094  continue;
1095  }
1096 
1097  // Preview web image or media elements
1098  if (‪$GLOBALS['TYPO3_CONF_VARS']['GFX']['thumbnails']
1099  && ($fileReferenceObject->getOriginalFile()->isImage() || $fileReferenceObject->getOriginalFile()->isMediaFile())
1100  ) {
1101  $cropVariantCollection = ‪CropVariantCollection::create((string)$fileReferenceObject->getProperty('crop'));
1102  $cropArea = $cropVariantCollection->getCropArea();
1104  $processingConfiguration = [
1105  'width' => $targetDimension->getWidth(),
1106  'height' => $targetDimension->getHeight(),
1107  ];
1108  if (!$cropArea->isEmpty()) {
1110  $processingConfiguration = [
1111  'maxWidth' => $targetDimension->getWidth(),
1112  'maxHeight' => $targetDimension->getHeight(),
1113  'crop' => $cropArea->makeAbsoluteBasedOnFile($fileReferenceObject),
1114  ];
1115  }
1116  $processedImage = $fileObject->process($taskType, $processingConfiguration);
1117  $attributes = [
1118  'src' => $processedImage->getPublicUrl() ?? '',
1119  'width' => $processedImage->getProperty('width'),
1120  'height' => $processedImage->getProperty('height'),
1121  'alt' => $fileReferenceObject->getAlternative() ?: $fileReferenceObject->getName(),
1122  'loading' => 'lazy',
1123  ];
1124  $imgTag = '<img ' . GeneralUtility::implodeAttributes($attributes, true) . $tparams . '/>';
1125  } else {
1126  // Icon
1127  $imgTag = $iconFactory
1128  ->getIconForResource($fileObject, IconSize::MEDIUM)
1129  ->setTitle($fileObject->getName())
1130  ->render();
1131  }
1132  if ($linkInfoPopup) {
1133  // relies on module 'TYPO3/CMS/Backend/ActionDispatcher'
1134  $attributes = GeneralUtility::implodeAttributes([
1135  'data-dispatch-action' => 'TYPO3.InfoWindow.showItem',
1136  'data-dispatch-args-list' => '_FILE,' . (int)$fileObject->getUid(),
1137  ], true);
1138  $thumbData .= ''
1139  . '<div class="preview-thumbnails-element">'
1140  . '<a href="#" ' . $attributes . '>'
1141  . '<div class="preview-thumbnails-element-image">'
1142  . $imgTag
1143  . '</div>'
1144  . '</a>'
1145  . '</div>';
1146  } else {
1147  $thumbData .= ''
1148  . '<div class="preview-thumbnails-element">'
1149  . '<div class="preview-thumbnails-element-image">'
1150  . $imgTag
1151  . '</div>'
1152  . '</div>';
1153  }
1154  }
1155  }
1156  return $thumbData ? '<div class="preview-thumbnails" style="--preview-thumbnails-size: ' . $size . 'px">' . $thumbData . '</div>' : '';
1157  }
1158 
1168  public static function titleAttribForPages($row, $perms_clause = '', $includeAttrib = true, bool $preferNavTitle = false)
1169  {
1170  if (!isset($row['uid'])) {
1171  return '';
1172  }
1173  $lang = static::getLanguageService();
1174  $parts = [];
1175  $parts[] = 'id=' . $row['uid'];
1176  if ($preferNavTitle && trim($row['nav_title'] ?? '') !== '') {
1177  $parts[] = $row['nav_title'];
1178  } else {
1179  $parts[] = $row['title'];
1180  }
1181  if ($row['uid'] === 0) {
1182  $out = htmlspecialchars(implode(' - ', $parts));
1183  return $includeAttrib ? 'title="' . $out . '"' : $out;
1184  }
1185  switch (VersionState::tryFrom($row['t3ver_state'] ?? 0)) {
1186  case VersionState::DELETE_PLACEHOLDER:
1187  $parts[] = 'Deleted element!';
1188  break;
1189  case VersionState::MOVE_POINTER:
1190  $parts[] = 'NEW LOCATION (Move-to Pointer) WSID#' . $row['t3ver_wsid'];
1191  break;
1192  case VersionState::NEW_PLACEHOLDER:
1193  $parts[] = 'New element!';
1194  break;
1195  }
1196  if ($row['doktype'] == ‪PageRepository::DOKTYPE_LINK) {
1197  $parts[] = $lang->sL(‪$GLOBALS['TCA']['pages']['columns']['url']['label'] ?? '') . ' ' . ($row['url'] ?? '');
1198  } elseif ($row['doktype'] == ‪PageRepository::DOKTYPE_SHORTCUT) {
1199  if ($perms_clause) {
1200  $label = self::getRecordPath((int)($row['shortcut'] ?? 0), $perms_clause, 20);
1201  } else {
1202  $row['shortcut'] = (int)($row['shortcut'] ?? 0);
1203  $lRec = self::getRecordWSOL('pages', $row['shortcut'], 'title');
1204  $label = ($lRec['title'] ?? '') . ' (id=' . $row['shortcut'] . ')';
1205  }
1206  if (($row['shortcut_mode'] ?? 0) != ‪PageRepository::SHORTCUT_MODE_NONE) {
1207  $label .= ', ' . $lang->sL(‪$GLOBALS['TCA']['pages']['columns']['shortcut_mode']['label']) . ' '
1208  . $lang->sL(self::getLabelFromItemlist('pages', 'shortcut_mode', $row['shortcut_mode'], $row));
1209  }
1210  $parts[] = $lang->sL(‪$GLOBALS['TCA']['pages']['columns']['shortcut']['label']) . ' ' . $label;
1211  } elseif ($row['doktype'] == ‪PageRepository::DOKTYPE_MOUNTPOINT) {
1212  if ((int)$row['mount_pid'] > 0) {
1213  if ($perms_clause) {
1214  $label = self::getRecordPath((int)$row['mount_pid'], $perms_clause, 20);
1215  } else {
1216  $lRec = self::getRecordWSOL('pages', (int)$row['mount_pid'], 'title');
1217  $label = ($lRec['title'] ?? '') . ' (id=' . $row['mount_pid'] . ')';
1218  }
1219  $parts[] = $lang->sL(‪$GLOBALS['TCA']['pages']['columns']['mount_pid']['label']) . ' ' . $label;
1220  if ($row['mount_pid_ol'] ?? 0) {
1221  $parts[] = $lang->sL(‪$GLOBALS['TCA']['pages']['columns']['mount_pid_ol']['label']);
1222  }
1223  } else {
1224  $parts[] = $lang->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:no_mount_pid');
1225  }
1226  }
1227  if ($row['nav_hide']) {
1228  $parts[] = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:pages.nav_hide');
1229  }
1230  if ($row['hidden']) {
1231  $parts[] = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.hidden');
1232  }
1233  if ($row['starttime']) {
1234  $parts[] = $lang->sL(‪$GLOBALS['TCA']['pages']['columns']['starttime']['label'])
1235  . ' ' . self::dateTimeAge($row['starttime'], -1, 'date');
1236  }
1237  if ($row['endtime']) {
1238  $parts[] = $lang->sL(‪$GLOBALS['TCA']['pages']['columns']['endtime']['label']) . ' '
1239  . self::dateTimeAge($row['endtime'], -1, 'date');
1240  }
1241  if ($row['fe_group']) {
1242  $fe_groups = [];
1243  foreach (‪GeneralUtility::intExplode(',', (string)$row['fe_group']) as $fe_group) {
1244  if ($fe_group < 0) {
1245  $fe_groups[] = $lang->sL(self::getLabelFromItemlist('pages', 'fe_group', (string)$fe_group, $row));
1246  } else {
1247  $lRec = self::getRecordWSOL('fe_groups', $fe_group, 'title');
1248  if (is_array($lRec)) {
1249  $fe_groups[] = $lRec['title'] ?? '';
1250  }
1251  }
1252  }
1253  $label = implode(', ', $fe_groups);
1254  $parts[] = $lang->sL(‪$GLOBALS['TCA']['pages']['columns']['fe_group']['label']) . ' ' . $label;
1255  }
1256  $out = htmlspecialchars(implode(' - ', $parts));
1257  return $includeAttrib ? 'title="' . $out . '"' : $out;
1258  }
1259 
1269  public static function getRecordIconAltText($row, $table = 'pages')
1270  {
1271  if ($table === 'pages') {
1272  $out = self::titleAttribForPages($row, '', false);
1273  } else {
1274  $out = !empty(trim(‪$GLOBALS['TCA'][$table]['ctrl']['descriptionColumn'] ?? ''))
1275  ? ($row[‪$GLOBALS['TCA'][$table]['ctrl']['descriptionColumn']] ?? '') . ' '
1276  : '';
1277  $ctrl = ‪$GLOBALS['TCA'][$table]['ctrl']['enablecolumns'] ?? [];
1278  // Uid is added
1279  $out .= 'id=' . ($row['uid'] ?? 0);
1280  if (static::isTableWorkspaceEnabled($table)) {
1281  switch (VersionState::tryFrom($row['t3ver_state'] ?? 0)) {
1282  case VersionState::DELETE_PLACEHOLDER:
1283  $out .= ' - Deleted element!';
1284  break;
1285  case VersionState::MOVE_POINTER:
1286  $out .= ' - NEW LOCATION (Move-to Pointer) WSID#' . $row['t3ver_wsid'];
1287  break;
1288  case VersionState::NEW_PLACEHOLDER:
1289  $out .= ' - New element!';
1290  break;
1291  }
1292  }
1293  // Hidden
1294  $lang = static::getLanguageService();
1295  if ($ctrl['disabled'] ?? false) {
1296  $out .= ($row[$ctrl['disabled']] ?? false) ? ' - ' . $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.hidden') : '';
1297  }
1298  if (($ctrl['starttime'] ?? false) && ($row[$ctrl['starttime']] ?? 0) > ‪$GLOBALS['EXEC_TIME']) {
1299  $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') . ')';
1300  }
1301  if (($ctrl['endtime'] ?? false) && ($row[$ctrl['endtime']] ?? false)) {
1302  $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') . ')';
1303  }
1304  }
1305  return htmlspecialchars($out);
1306  }
1307 
1316  public static function getLabelFromItemlist($table, $col, $key, array $row = [])
1317  {
1318  $columnConfig = ‪$GLOBALS['TCA'][$table]['columns'][$col]['config'] ?? [];
1319 
1320  if (isset($columnConfig['items']) && !is_array($columnConfig['items'])) {
1321  return '';
1322  }
1323 
1324  $items = $columnConfig['items'] ?? [];
1325 
1326  if ($columnConfig['itemsProcFunc'] ?? false) {
1327  $processingService = GeneralUtility::makeInstance(ItemProcessingService::class);
1328  $items = $processingService->getProcessingItems(
1329  $table,
1330  $row['pid'] ?? 0,
1331  $col,
1332  $row,
1333  $columnConfig,
1334  $items
1335  );
1336  }
1337 
1338  foreach ($items as $itemConfiguration) {
1339  if ((string)$itemConfiguration['value'] === (string)$key) {
1340  return $itemConfiguration['label'];
1341  }
1342  }
1343 
1344  return '';
1345  }
1346 
1355  public static function getLabelFromItemListMerged(int $pageId, $table, $column, $key, array $row = [])
1356  {
1357  $pageTsConfig = static::getPagesTSconfig($pageId);
1358  $label = '';
1359  if (isset($pageTsConfig['TCEFORM.'])
1360  && is_array($pageTsConfig['TCEFORM.'] ?? null)
1361  && is_array($pageTsConfig['TCEFORM.'][$table . '.'] ?? null)
1362  && is_array($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.'] ?? null)
1363  ) {
1364  if (is_array($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['addItems.'] ?? null)
1365  && isset($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['addItems.'][$key])
1366  ) {
1367  $label = $pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['addItems.'][$key];
1368  } elseif (is_array($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['altLabels.'] ?? null)
1369  && isset($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['altLabels.'][$key])
1370  ) {
1371  $label = $pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['altLabels.'][$key];
1372  }
1373  }
1374  if (empty($label)) {
1375  $tcaValue = self::getLabelFromItemlist($table, $column, $key, $row);
1376  if (!empty($tcaValue)) {
1377  $label = $tcaValue;
1378  }
1379  }
1380  return $label;
1381  }
1382 
1392  public static function getLabelsFromItemsList($table, $column, $keyList, array $columnTsConfig = [], array $row = [])
1393  {
1394  $columnConfig = ‪$GLOBALS['TCA'][$table]['columns'][$column]['config'] ?? [];
1395 
1396  if ($keyList === '' || (isset($columnConfig['items']) && !is_array($columnConfig['items']))) {
1397  return '';
1398  }
1399 
1400  $items = $columnConfig['items'] ?? [];
1401 
1402  if ($columnConfig['itemsProcFunc'] ?? false) {
1403  $processingService = GeneralUtility::makeInstance(ItemProcessingService::class);
1404  $items = $processingService->getProcessingItems(
1405  $table,
1406  $row['pid'] ?? 0,
1407  $column,
1408  $row,
1409  $columnConfig,
1410  $items
1411  );
1412  }
1413 
1414  $keys = ‪GeneralUtility::trimExplode(',', $keyList, true);
1415  $labels = [];
1416  // Loop on all selected values
1417  foreach ($keys as $key) {
1418  $label = null;
1419  if ($columnTsConfig) {
1420  // Check if label has been defined or redefined via pageTsConfig
1421  if (isset($columnTsConfig['addItems.'][$key])) {
1422  $label = $columnTsConfig['addItems.'][$key];
1423  } elseif (isset($columnTsConfig['altLabels.'][$key])) {
1424  $label = $columnTsConfig['altLabels.'][$key];
1425  }
1426  }
1427  if ($label === null) {
1428  // Otherwise lookup the label in TCA items list
1429  foreach ($items as $itemConfiguration) {
1430  if ($key === (string)$itemConfiguration['value']) {
1431  $label = $itemConfiguration['label'];
1432  break;
1433  }
1434  }
1435  }
1436  if ($label !== null) {
1437  $labels[] = static::getLanguageService()->sL($label);
1438  }
1439  }
1440  return implode(', ', $labels);
1441  }
1442 
1450  public static function getItemLabel(string $table, string $column): ?string
1451  {
1452  return ‪$GLOBALS['TCA'][$table]['columns'][$column]['label'] ?? null;
1453  }
1454 
1465  public static function getRecordTitle($table, $row, $prep = false, $forceResult = true)
1466  {
1467  $params = [];
1468  $recordTitle = '';
1469  if (isset(‪$GLOBALS['TCA'][$table]) && is_array(‪$GLOBALS['TCA'][$table])) {
1470  // If configured, call userFunc
1471  if (!empty(‪$GLOBALS['TCA'][$table]['ctrl']['label_userFunc'])) {
1472  $params['table'] = $table;
1473  $params['row'] = $row;
1474  $params['title'] = '';
1475  $params['options'] = ‪$GLOBALS['TCA'][$table]['ctrl']['label_userFunc_options'] ?? [];
1476 
1477  // Create NULL-reference
1478  $null = null;
1479  GeneralUtility::callUserFunction(‪$GLOBALS['TCA'][$table]['ctrl']['label_userFunc'], $params, $null);
1480  // Ensure that result of called userFunc still have title set, and it is a string.
1481  $recordTitle = (string)($params['title'] ?? '');
1482  } else {
1483  // No userFunc: Build label
1484  $ctrlLabel = ‪$GLOBALS['TCA'][$table]['ctrl']['label'] ?? '';
1485  $recordTitle = self::getProcessedValue(
1486  $table,
1487  $ctrlLabel,
1488  (string)($row[$ctrlLabel] ?? ''),
1489  0,
1490  false,
1491  false,
1492  $row['uid'] ?? null,
1493  $forceResult
1494  ) ?? '';
1495  if (!empty(‪$GLOBALS['TCA'][$table]['ctrl']['label_alt'])
1496  && (!empty(‪$GLOBALS['TCA'][$table]['ctrl']['label_alt_force']) || $recordTitle === '')
1497  ) {
1498  $altFields = ‪GeneralUtility::trimExplode(',', ‪$GLOBALS['TCA'][$table]['ctrl']['label_alt'], true);
1499  $tA = [];
1500  if (!empty($recordTitle)) {
1501  $tA[] = $recordTitle;
1502  }
1503  foreach ($altFields as $fN) {
1504  $recordTitle = trim(strip_tags((string)($row[$fN] ?? '')));
1505  if ($recordTitle !== '') {
1506  $recordTitle = self::getProcessedValue($table, $fN, $recordTitle, 0, false, false, $row['uid'] ?? 0);
1507  if (!(‪$GLOBALS['TCA'][$table]['ctrl']['label_alt_force'] ?? false)) {
1508  break;
1509  }
1510  $tA[] = $recordTitle;
1511  }
1512  }
1513  if (‪$GLOBALS['TCA'][$table]['ctrl']['label_alt_force'] ?? false) {
1514  $recordTitle = implode(', ', $tA);
1515  }
1516  }
1517  }
1518  // If the current result is empty, set it to '[No title]' (localized) and prepare for output if requested
1519  if ($prep || $forceResult) {
1520  if ($prep) {
1521  $recordTitle = self::getRecordTitlePrep($recordTitle);
1522  }
1523  if (trim($recordTitle) === '') {
1524  $recordTitle = self::getNoRecordTitle($prep);
1525  }
1526  }
1527  }
1528 
1529  return $recordTitle;
1530  }
1531 
1540  public static function getRecordTitlePrep($title, $titleLength = 0)
1541  {
1542  // If $titleLength is not a valid positive integer, use BE_USER->uc['titleLen']:
1543  if (!$titleLength || !‪MathUtility::canBeInterpretedAsInteger($titleLength) || $titleLength < 0) {
1544  $titleLength = (int)static::getBackendUserAuthentication()->uc['titleLen'];
1545  }
1546  $titleOrig = htmlspecialchars($title);
1547  $title = htmlspecialchars(‪GeneralUtility::fixed_lgd_cs($title, (int)$titleLength));
1548  // If title was cropped, offer a tooltip:
1549  if ($titleOrig != $title) {
1550  $title = '<span title="' . $titleOrig . '">' . $title . '</span>';
1551  }
1552  return $title;
1553  }
1554 
1561  public static function getNoRecordTitle($prep = false)
1562  {
1563  $noTitle = '[' .
1564  htmlspecialchars(static::getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.no_title'))
1565  . ']';
1566  if ($prep) {
1567  $noTitle = '<em>' . $noTitle . '</em>';
1568  }
1569  return $noTitle;
1570  }
1571 
1590  public static function getProcessedValue(
1591  $table,
1592  $col,
1593  $value,
1594  $fixed_lgd_chars = 0,
1595  $defaultPassthrough = false,
1596  $noRecordLookup = false,
1597  ‪$uid = 0,
1598  $forceResult = true,
1599  $pid = 0
1600  ) {
1601  if ($col === 'uid') {
1602  // uid is not in TCA-array
1603  return $value;
1604  }
1605  // Check if table and field is configured
1606  if (!isset(‪$GLOBALS['TCA'][$table]['columns'][$col]) || !is_array(‪$GLOBALS['TCA'][$table]['columns'][$col])) {
1607  return null;
1608  }
1609  // Depending on the fields configuration, make a meaningful output value.
1610  $theColConf = ‪$GLOBALS['TCA'][$table]['columns'][$col]['config'] ?? [];
1611  /*****************
1612  *HOOK: pre-processing the human readable output from a record
1613  ****************/
1614  $referenceObject = new \stdClass();
1615  $referenceObject->table = $table;
1616  $referenceObject->fieldName = $col;
1617  $referenceObject->uid = ‪$uid;
1618  $referenceObject->value = &$value;
1619  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['preProcessValue'] ?? [] as $_funcRef) {
1620  GeneralUtility::callUserFunction($_funcRef, $theColConf, $referenceObject);
1621  }
1622 
1623  $l = '';
1624  $lang = static::getLanguageService();
1625  switch ((string)($theColConf['type'] ?? '')) {
1626  case 'radio':
1627  $l = $lang->sL(self::getLabelFromItemlist($table, $col, $value, ['uid' => (int)‪$uid, 'pid' => (int)$pid]));
1628  if ($l === '' && !empty($value)) {
1629  // Use plain database value when label is empty
1630  $l = $value;
1631  }
1632  break;
1633  case 'inline':
1634  case 'file':
1635  if (‪$uid) {
1636  $finalValues = static::resolveRelationLabels($theColConf, $table, ‪$uid, $value, $noRecordLookup);
1637  $l = implode(', ', $finalValues);
1638  } else {
1639  $l = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:notAvailableAbbreviation');
1640  }
1641  break;
1642  case 'select':
1643  case 'category':
1644  if (!empty($theColConf['MM'])) {
1645  if (‪$uid) {
1646  $finalValues = static::resolveRelationLabels($theColConf, $table, ‪$uid, $value, $noRecordLookup);
1647  $l = implode(', ', $finalValues);
1648  } else {
1649  $l = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:notAvailableAbbreviation');
1650  }
1651  } else {
1652  $columnTsConfig = [];
1653  if ($pid) {
1654  $pageTsConfig = self::getPagesTSconfig($pid);
1655  if (isset($pageTsConfig['TCEFORM.'][$table . '.'][$col . '.']) && is_array($pageTsConfig['TCEFORM.'][$table . '.'][$col . '.'])) {
1656  $columnTsConfig = $pageTsConfig['TCEFORM.'][$table . '.'][$col . '.'];
1657  }
1658  }
1659  $l = self::getLabelsFromItemsList($table, $col, (string)$value, $columnTsConfig, ['uid' => (int)‪$uid, 'pid' => (int)$pid]);
1660  if (!empty($theColConf['foreign_table']) && !$l && !empty(‪$GLOBALS['TCA'][$theColConf['foreign_table']])) {
1661  if ($noRecordLookup) {
1662  $l = $value;
1663  } else {
1664  $finalValues = [];
1665  if (‪$uid) {
1666  $finalValues = static::resolveRelationLabels($theColConf, $table, ‪$uid, $value, $noRecordLookup);
1667  }
1668  $l = implode(', ', $finalValues);
1669  }
1670  }
1671  if (empty($l) && !empty($value)) {
1672  // Use plain database value when label is empty
1673  $l = $value;
1674  }
1675  }
1676  break;
1677  case 'group':
1678  // resolve titles of DB records
1679  $finalValues = static::resolveRelationLabels($theColConf, $table, ‪$uid, $value, $noRecordLookup);
1680  if ($finalValues !== []) {
1681  $l = implode(', ', $finalValues);
1682  } else {
1683  $l = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:notAvailableAbbreviation');
1684  }
1685  break;
1686  case 'folder':
1687  $l = implode(', ', ‪GeneralUtility::trimExplode(',', (string)$value, true));
1688  break;
1689  case 'check':
1690  if (!is_array($theColConf['items'] ?? null)) {
1691  $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');
1692  } elseif (count($theColConf['items']) === 1) {
1693  reset($theColConf['items']);
1694  $invertStateDisplay = current($theColConf['items'])['invertStateDisplay'] ?? false;
1695  if ($invertStateDisplay) {
1696  $value = !$value;
1697  }
1698  $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');
1699  } else {
1700  $lA = [];
1701  foreach ($theColConf['items'] as $key => $val) {
1702  if ((int)$value & 2 ** $key) {
1703  $lA[] = $lang->sL($val['label']);
1704  }
1705  }
1706  $l = implode(', ', $lA);
1707  }
1708  break;
1709  case 'input':
1710  case 'number':
1711  // todo: As soon as more strict types are used, this isset check must be replaced with a more
1712  // appropriate check.
1713  if (isset($value)) {
1714  $l = $value;
1715  }
1716  break;
1717  case 'datetime':
1718  $format = (string)($theColConf['format'] ?? 'datetime');
1719  $dateTimeFormats = ‪QueryHelper::getDateTimeFormats();
1720  if ($format === 'date') {
1721  // Handle native date field
1722  if (($theColConf['dbType'] ?? '') === 'date') {
1723  $value = $value === $dateTimeFormats['date']['empty'] ? 0 : (int)strtotime((string)$value);
1724  } else {
1725  $value = (int)$value;
1726  }
1727  if (!empty($value)) {
1728  $ageSuffix = '';
1729  // Generate age suffix as long as not explicitly suppressed
1730  if (!($theColConf['disableAgeDisplay'] ?? false)) {
1731  $ageDelta = ‪$GLOBALS['EXEC_TIME'] - $value;
1732  $calculatedAge = self::calcAge(
1733  (int)abs($ageDelta),
1734  $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears')
1735  );
1736  $ageSuffix = ' (' . ($ageDelta > 0 ? '-' : '') . $calculatedAge . ')';
1737  }
1738  $l = self::date($value) . $ageSuffix;
1739  }
1740  } elseif ($format === 'time') {
1741  // Handle native time field
1742  if (($theColConf['dbType'] ?? '') === 'time') {
1743  $value = $value === $dateTimeFormats['time']['empty'] ? 0 : (int)strtotime('1970-01-01 ' . $value . ' UTC');
1744  } else {
1745  $value = (int)$value;
1746  }
1747  if (!empty($value)) {
1748  $l = gmdate('H:i', (int)$value);
1749  }
1750  } elseif ($format === 'timesec') {
1751  // Handle native time field
1752  if (($theColConf['dbType'] ?? '') === 'time') {
1753  $value = $value === $dateTimeFormats['time']['empty'] ? 0 : (int)strtotime('1970-01-01 ' . $value . ' UTC');
1754  } else {
1755  $value = (int)$value;
1756  }
1757  if (!empty($value)) {
1758  $l = gmdate('H:i:s', (int)$value);
1759  }
1760  } elseif ($format === 'datetime') {
1761  // Handle native datetime field
1762  if (($theColConf['dbType'] ?? '') === 'datetime') {
1763  $value = $value === $dateTimeFormats['datetime']['empty'] ? 0 : (int)strtotime((string)$value);
1764  } else {
1765  $value = (int)$value;
1766  }
1767  if (!empty($value)) {
1768  $l = self::datetime($value);
1769  }
1770  } elseif (isset($value)) {
1771  // todo: As soon as more strict types are used, this isset check must be replaced with a more
1772  // appropriate check.
1773  $l = $value;
1774  }
1775  break;
1776  case 'password':
1777  // Hide the password by changing it to asterisk (*) - if anything is set at all
1778  if ($value) {
1779  $l = '********';
1780  }
1781  break;
1782  case 'flex':
1783  if (is_string($value)) {
1784  $l = strip_tags($value);
1785  }
1786  break;
1787  case 'language':
1788  $l = $value;
1789  if (‪$uid) {
1790  $pageId = (int)($table === 'pages' ? ‪$uid : (static::getRecordWSOL($table, (int)‪$uid, 'pid')['pid'] ?? 0));
1791  $languageTitle = GeneralUtility::makeInstance(TranslationConfigurationProvider::class)
1792  ->getSystemLanguages($pageId)[(int)$value]['title'] ?? '';
1793  if ($languageTitle !== '') {
1794  $l = $languageTitle;
1795  }
1796  }
1797  break;
1798  case 'json':
1799  // For database type "JSON" the value in decoded state is most likely an array. This is not compatible with
1800  // the "human-readable" processing and returning promise of this method. Thus, we ensure to handle value for
1801  // this field as json encoded string. This should be the best readable version of the value data.
1802  if (
1803  (
1804  is_string($value)
1805  && !str_starts_with($value, '{')
1806  && !str_starts_with($value, '[')
1807  )
1808  || !is_string($value)
1809  ) {
1810  // @todo Consider to pretty print the json value, as this would match the "human readable" goal.
1811  $value = json_encode($value);
1812  }
1813  // no break intended.
1814  default:
1815  if ($defaultPassthrough) {
1816  $l = $value;
1817  } elseif (isset($theColConf['MM'])) {
1818  $l = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:notAvailableAbbreviation');
1819  } elseif ($value) {
1820  $l = ‪GeneralUtility::fixed_lgd_cs(strip_tags($value), 200);
1821  }
1822  }
1823  /*****************
1824  *HOOK: post-processing the human readable output from a record
1825  ****************/
1826  $null = null;
1827  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['postProcessValue'] ?? [] as $_funcRef) {
1828  $params = [
1829  'value' => $l,
1830  'colConf' => $theColConf,
1831  ];
1832  $l = GeneralUtility::callUserFunction($_funcRef, $params, $null);
1833  }
1834  if ($fixed_lgd_chars && $l) {
1835  return ‪GeneralUtility::fixed_lgd_cs((string)$l, (int)$fixed_lgd_chars);
1836  }
1837  return $l;
1838  }
1839 
1846  protected static function resolveRelationLabels(array $theColConf, string $table, $recordId, $value, bool $noRecordLookup): array
1847  {
1848  $finalValues = [];
1849 
1850  $relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
1851  $relationHandler->registerNonTableValues = (bool)($theColConf['allowNonIdValues'] ?? false);
1852  $relationHandler->start(
1853  $value,
1854  $theColConf['allowed'] ?? $theColConf['foreign_table'] ?? '',
1855  $theColConf['MM'] ?? '',
1856  $recordId,
1857  $table,
1858  $theColConf
1859  );
1860 
1861  if ($noRecordLookup) {
1862  $finalValues = array_column($relationHandler->itemArray, 'id');
1863  } else {
1864  $relationHandler->getFromDB();
1865  foreach ($relationHandler->getResolvedItemArray() as $item) {
1866  $relationRecord = $item['record'];
1867  static::workspaceOL($item['table'], $relationRecord);
1868  if (!is_array($relationRecord)) {
1869  $finalValues[] = '[' . $item['uid'] . ']';
1870  } else {
1871  $title = static::getRecordTitle($item['table'], $relationRecord);
1872  if ($theColConf['foreign_table_prefix'] ?? null) {
1873  $title = static::getLanguageService()->sL($theColConf['foreign_table_prefix']) . $title;
1874  }
1875  $finalValues[] = $title;
1876  }
1877  }
1878  }
1879 
1880  return $finalValues;
1881  }
1882 
1896  public static function getProcessedValueExtra(
1897  $table,
1898  $fN,
1899  $fV,
1900  $fixed_lgd_chars = 0,
1901  ‪$uid = 0,
1902  $forceResult = true,
1903  $pid = 0
1904  ) {
1905  $fVnew = self::getProcessedValue($table, $fN, $fV, $fixed_lgd_chars, true, false, ‪$uid, $forceResult, $pid);
1906  if (!isset($fVnew)) {
1907  if (is_array(‪$GLOBALS['TCA'][$table])) {
1908  if ($fN == (‪$GLOBALS['TCA'][$table]['ctrl']['tstamp'] ?? 0) || $fN == (‪$GLOBALS['TCA'][$table]['ctrl']['crdate'] ?? 0)) {
1909  $fVnew = self::datetime((int)$fV);
1910  } elseif ($fN === 'pid') {
1911  // Fetches the path with no regard to the users permissions to select pages.
1912  $fVnew = self::getRecordPath((int)$fV, '1=1', 20);
1913  } else {
1914  $fVnew = $fV;
1915  }
1916  }
1917  }
1918  return $fVnew;
1919  }
1920 
1935  public static function getCommonSelectFields($table, $prefix = '', ‪$fields = [])
1936  {
1937  ‪$fields[] = $prefix . 'uid';
1938  ‪$fields[] = $prefix . 'pid';
1939  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['label']) && ‪$GLOBALS['TCA'][$table]['ctrl']['label'] != '') {
1940  ‪$fields[] = $prefix . ‪$GLOBALS['TCA'][$table]['ctrl']['label'];
1941  }
1942  if (!empty(‪$GLOBALS['TCA'][$table]['ctrl']['label_alt'])) {
1943  $secondFields = ‪GeneralUtility::trimExplode(',', ‪$GLOBALS['TCA'][$table]['ctrl']['label_alt'], true);
1944  foreach ($secondFields as $fieldN) {
1945  ‪$fields[] = $prefix . $fieldN;
1946  }
1947  }
1948  if (static::isTableWorkspaceEnabled($table)) {
1949  ‪$fields[] = $prefix . 't3ver_state';
1950  ‪$fields[] = $prefix . 't3ver_wsid';
1951  }
1952  if (!empty(‪$GLOBALS['TCA'][$table]['ctrl']['selicon_field'])) {
1953  ‪$fields[] = $prefix . ‪$GLOBALS['TCA'][$table]['ctrl']['selicon_field'];
1954  }
1955  if (!empty(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_column'])) {
1956  ‪$fields[] = $prefix . ‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_column'];
1957  }
1958  if (!empty(‪$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'])) {
1959  ‪$fields[] = $prefix . ‪$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'];
1960  }
1961  if (!empty(‪$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['starttime'])) {
1962  ‪$fields[] = $prefix . ‪$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['starttime'];
1963  }
1964  if (!empty(‪$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['endtime'])) {
1965  ‪$fields[] = $prefix . ‪$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['endtime'];
1966  }
1967  if (!empty(‪$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['fe_group'])) {
1968  ‪$fields[] = $prefix . ‪$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['fe_group'];
1969  }
1970  return implode(',', array_unique(‪$fields));
1971  }
1972 
1991  public static function wrapClickMenuOnIcon($content, $table, ‪$uid = 0, $context = ''): string
1992  {
1993  $attributes = self::getContextMenuAttributes((string)$table, ‪$uid, (string)$context);
1994  return '<button type="button" class="btn btn-link p-0" ' . GeneralUtility::implodeAttributes($attributes, true) . '>' . $content . '</button>';
1995  }
1996 
2006  public static function getContextMenuAttributes(string $table, ‪$uid = 0, string $context = '', string $trigger = 'click'): array
2007  {
2008  return [
2009  'data-contextmenu-trigger' => $trigger,
2010  'data-contextmenu-table' => $table,
2011  'data-contextmenu-uid' => ‪$uid,
2012  'data-contextmenu-context' => $context,
2013  ];
2014  }
2015 
2026  public static function setUpdateSignal($set = '', $params = '')
2027  {
2028  // A CLI use does not need to update the pagetree or anything else
2029  // Otherwise DataHandler hook in EXT:redirects in SlugService will throw an error
2030  if (‪Environment::isCli()) {
2031  return;
2032  }
2033  $beUser = static::getBackendUserAuthentication();
2034  $modData = $beUser->getModuleData(
2035  BackendUtility::class . '::getUpdateSignal',
2036  'ses'
2037  );
2038  if ($set) {
2039  $modData[$set] = [
2040  'set' => $set,
2041  'parameter' => $params,
2042  ];
2043  } else {
2044  // clear the module data
2045  $modData = [];
2046  }
2047  $beUser->pushModuleData(BackendUtility::class . '::getUpdateSignal', $modData);
2048  }
2049 
2057  public static function getUpdateSignalDetails(): array
2058  {
2059  ‪$details = [
2060  'html' => [],
2061  ];
2062  $modData = static::getBackendUserAuthentication()->getModuleData(
2063  BackendUtility::class . '::getUpdateSignal',
2064  'ses'
2065  );
2066  if (empty($modData)) {
2067  return ‪$details;
2068  }
2069  // Hook: Allows to let TYPO3 execute your JS code
2070  $updateSignals = ‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['updateSignalHook'] ?? [];
2071  // Loop through all setUpdateSignals and get the JS code
2072  foreach ($modData as $set => $val) {
2073  if (isset($updateSignals[$set])) {
2074  $params = ['set' => $set, 'parameter' => $val['parameter'], 'html' => ''];
2075  $ref = null;
2076  GeneralUtility::callUserFunction($updateSignals[$set], $params, $ref);
2077  if (!empty($params['html'])) {
2078  ‪$details['html'][] = $params['html'];
2079  }
2080  } else {
2081  switch ($set) {
2082  case 'updatePageTree':
2084  'typo3:pagetree:refresh',
2085  null,
2086  true
2087  );
2088  break;
2089  case 'updateFolderTree':
2091  'typo3:filestoragetree:refresh',
2092  null,
2093  true
2094  );
2095  break;
2096  case 'updateModuleMenu':
2098  'TYPO3.ModuleMenu.App.refreshMenu',
2099  );
2100  break;
2101  case 'updateTopbar':
2103  'TYPO3.Backend.Topbar.refresh'
2104  );
2105  break;
2106  }
2107  }
2108  }
2109  // reset update signals
2110  self::setUpdateSignal();
2111  return ‪$details;
2112  }
2113 
2128  public static function getModuleData(
2129  $MOD_MENU,
2130  $CHANGED_SETTINGS,
2131  $modName,
2132  $type = '',
2133  $dontValidateList = '',
2134  $setDefaultList = ''
2135  ) {
2136  if ($modName && is_string($modName)) {
2137  // Getting stored user-data from this module:
2138  $beUser = static::getBackendUserAuthentication();
2139  $settings = $beUser->getModuleData($modName, $type);
2140  $changed = 0;
2141  if (!is_array($settings)) {
2142  $changed = 1;
2143  $settings = [
2144  'function' => null,
2145  'language' => null,
2146  'constant_editor_cat' => null,
2147  ];
2148  }
2149  if (is_array($MOD_MENU)) {
2150  foreach ($MOD_MENU as $key => $var) {
2151  // If a global var is set before entering here. eg if submitted, then it's substituting the current value the array.
2152  if (is_array($CHANGED_SETTINGS) && isset($CHANGED_SETTINGS[$key])) {
2153  if (is_array($CHANGED_SETTINGS[$key])) {
2154  $serializedSettings = serialize($CHANGED_SETTINGS[$key]);
2155  if ((string)$settings[$key] !== $serializedSettings) {
2156  $settings[$key] = $serializedSettings;
2157  $changed = 1;
2158  }
2159  } else {
2160  if ((string)($settings[$key] ?? '') !== (string)($CHANGED_SETTINGS[$key] ?? '')) {
2161  $settings[$key] = $CHANGED_SETTINGS[$key];
2162  $changed = 1;
2163  }
2164  }
2165  }
2166  // If the $var is an array, which denotes the existence of a menu, we check if the value is permitted
2167  if (is_array($var) && (!$dontValidateList || !‪GeneralUtility::inList($dontValidateList, $key))) {
2168  // If the setting is an array or not present in the menu-array, MOD_MENU, then the default value is inserted.
2169  if (is_array($settings[$key] ?? null) || !isset($MOD_MENU[$key][$settings[$key] ?? null])) {
2170  $settings[$key] = (string)key($var);
2171  $changed = 1;
2172  }
2173  }
2174  // Sets default values (only strings/checkboxes, not menus)
2175  if ($setDefaultList && !is_array($var)) {
2176  if (‪GeneralUtility::inList($setDefaultList, $key) && !isset($settings[$key])) {
2177  $settings[$key] = (string)$var;
2178  }
2179  }
2180  }
2181  } else {
2182  throw new \RuntimeException('No menu', 1568119229);
2183  }
2184  if ($changed) {
2185  $beUser->pushModuleData($modName, $settings);
2186  }
2187  return $settings;
2188  }
2189  throw new \RuntimeException('Wrong module name "' . $modName . '"', 1568119221);
2190  }
2191 
2192  /*******************************************
2193  *
2194  * Core
2195  *
2196  *******************************************/
2206  public static function lockRecords($table = '', ‪$uid = 0, $pid = 0)
2207  {
2208  $beUser = static::getBackendUserAuthentication();
2209  if (isset($beUser->user['uid'])) {
2210  $userId = (int)$beUser->user['uid'];
2211  ‪if ($table && ‪$uid) {
2212  $fieldsValues = [
2213  'userid' => $userId,
2214  'feuserid' => 0,
2215  'tstamp' => ‪$GLOBALS['EXEC_TIME'],
2216  'record_table' => $table,
2217  'record_uid' => ‪$uid,
2218  'username' => $beUser->user['username'],
2219  'record_pid' => $pid,
2220  ];
2221  GeneralUtility::makeInstance(ConnectionPool::class)
2222  ->getConnectionForTable('sys_lockedrecords')
2223  ->insert(
2224  'sys_lockedrecords',
2225  $fieldsValues
2226  );
2227  } else {
2228  GeneralUtility::makeInstance(ConnectionPool::class)
2229  ->getConnectionForTable('sys_lockedrecords')
2230  ->delete(
2231  'sys_lockedrecords',
2232  ['userid' => (int)$userId]
2233  );
2234  }
2235  }
2236  }
2237 
2250  public static function isRecordLocked($table, ‪$uid)
2251  {
2252  $runtimeCache = self::getRuntimeCache();
2253  $cacheId = 'backend-recordLocked';
2254  $recordLockedCache = $runtimeCache->get($cacheId);
2255  if ($recordLockedCache !== false) {
2256  $lockedRecords = $recordLockedCache;
2257  } else {
2258  $lockedRecords = [];
2259 
2260  $queryBuilder = static::getQueryBuilderForTable('sys_lockedrecords');
2261  $result = $queryBuilder
2262  ->select('*')
2263  ->from('sys_lockedrecords')
2264  ->where(
2265  $queryBuilder->expr()->neq(
2266  'sys_lockedrecords.userid',
2267  $queryBuilder->createNamedParameter(
2268  static::getBackendUserAuthentication()->user['uid'],
2270  )
2271  ),
2272  $queryBuilder->expr()->gt(
2273  'sys_lockedrecords.tstamp',
2274  $queryBuilder->createNamedParameter(
2275  ‪$GLOBALS['EXEC_TIME'] - 2 * 3600,
2277  )
2278  )
2279  )
2280  ->executeQuery();
2281 
2282  $lang = static::getLanguageService();
2283  while ($row = $result->fetchAssociative()) {
2284  $row += [
2285  'userid' => 0,
2286  'record_pid' => 0,
2287  'feuserid' => 0,
2288  'username' => '',
2289  'record_table' => '',
2290  'record_uid' => 0,
2291 
2292  ];
2293  // Get the type of the user that locked this record:
2294  if ($row['userid']) {
2295  $userTypeLabel = 'beUser';
2296  } elseif ($row['feuserid']) {
2297  $userTypeLabel = 'feUser';
2298  } else {
2299  $userTypeLabel = 'user';
2300  }
2301  $userType = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.' . $userTypeLabel);
2302  // Get the username (if available):
2303  $userName = ($row['username'] ?? '') ?: $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.unknownUser');
2304 
2305  $lockedRecords[$row['record_table'] . ':' . $row['record_uid']] = $row;
2306  $lockedRecords[$row['record_table'] . ':' . $row['record_uid']]['msg'] = sprintf(
2307  $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.lockedRecordUser'),
2308  $userType,
2309  $userName,
2310  self::calcAge(
2311  ‪$GLOBALS['EXEC_TIME'] - $row['tstamp'],
2312  $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears')
2313  )
2314  );
2315  if ($row['record_pid'] && !isset($lockedRecords[$row['record_table'] . ':' . $row['record_pid']])) {
2316  $lockedRecords['pages:' . ($row['record_pid'] ?? '')]['msg'] = sprintf(
2317  $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.lockedRecordUser_content'),
2318  $userType,
2319  $userName,
2320  self::calcAge(
2321  ‪$GLOBALS['EXEC_TIME'] - $row['tstamp'],
2322  $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears')
2323  )
2324  );
2325  }
2326  }
2327  $runtimeCache->set($cacheId, $lockedRecords);
2328  }
2329 
2330  return $lockedRecords[$table . ':' . ‪$uid] ?? false;
2331  }
2332 
2341  public static function getTCEFORM_TSconfig($table, $row)
2342  {
2343  $res = [];
2344  // Get main config for the table
2345  [$TScID, $cPid] = self::getTSCpid($table, $row['uid'] ?? 0, $row['pid'] ?? 0);
2346  if ($TScID >= 0) {
2347  $tsConfig = static::getPagesTSconfig($TScID)['TCEFORM.'][$table . '.'] ?? [];
2348  $typeVal = self::getTCAtypeValue($table, $row);
2349  foreach ($tsConfig as $key => $val) {
2350  if (is_array($val)) {
2351  $fieldN = substr($key, 0, -1);
2352  $res[$fieldN] = $val;
2353  unset($res[$fieldN]['types.']);
2354  if ((string)$typeVal !== '' && is_array($val['types.'][$typeVal . '.'] ?? false)) {
2355  ArrayUtility::mergeRecursiveWithOverrule($res[$fieldN], $val['types.'][$typeVal . '.']);
2356  }
2357  }
2358  }
2359  }
2360  $res['_CURRENT_PID'] = $cPid;
2361  $res['_THIS_UID'] = $row['uid'] ?? 0;
2362  // So the row will be passed to foreign_table_where_query()
2363  $res['_THIS_ROW'] = $row;
2364  return $res;
2365  }
2366 
2379  public static function getTSconfig_pidValue($table, ‪$uid, $pid)
2380  {
2381  // If pid is an integer this takes precedence in our lookup.
2383  $thePidValue = (int)$pid;
2384  // If ref to another record, look that record up.
2385  if ($thePidValue < 0) {
2386  $pidRec = self::getRecord($table, abs($thePidValue), 'pid');
2387  $thePidValue = is_array($pidRec) ? $pidRec['pid'] : -2;
2388  }
2389  } else {
2390  // Try to fetch the record pid from uid. If the uid is 'NEW...' then this will of course return nothing
2391  $rr = self::getRecord($table, ‪$uid);
2392  $thePidValue = null;
2393  if (is_array($rr)) {
2394  // First check if the t3ver_oid value is greater 0, which means
2395  // it is a workspace element. If so, get the "real" record:
2396  if ((int)($rr['t3ver_oid'] ?? 0) > 0) {
2397  $rr = self::getRecord($table, $rr['t3ver_oid'], 'pid');
2398  if (is_array($rr)) {
2399  $thePidValue = $rr['pid'];
2400  }
2401  } else {
2402  // Returning the "pid" of the record
2403  $thePidValue = $rr['pid'];
2404  }
2405  }
2406  if (!$thePidValue) {
2407  // Returns -1 if the record with this pid was not found.
2408  $thePidValue = -1;
2409  }
2410  }
2411  return $thePidValue;
2412  }
2413 
2426  public static function getTSCpidCached($table, ‪$uid, $pid)
2427  {
2428  $runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');
2429  $firstLevelCache = $runtimeCache->get('backendUtilityTscPidCached') ?: [];
2430  $key = $table . ':' . ‪$uid . ':' . $pid;
2431  if (!isset($firstLevelCache[$key])) {
2432  $firstLevelCache[$key] = static::getTSCpid($table, (int)‪$uid, (int)$pid);
2433  $runtimeCache->set('backendUtilityTscPidCached', $firstLevelCache);
2434  }
2435  return $firstLevelCache[$key];
2436  }
2437 
2450  public static function getTSCpid($table, ‪$uid, $pid)
2451  {
2452  // If pid is negative (referring to another record) the pid of the other record is fetched and returned.
2453  $cPid = self::getTSconfig_pidValue($table, ‪$uid, $pid);
2454  // $TScID is the id of $table = pages, else it's the pid of the record.
2455  $TScID = $table === 'pages' && ‪MathUtility::canBeInterpretedAsInteger(‪$uid) ? ‪$uid : $cPid;
2456  return [$TScID, $cPid];
2457  }
2458 
2464  protected static function getRuntimeCache()
2465  {
2466  return GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');
2467  }
2468 
2478  public static function referenceCount($table, $ref, $msg = '', $count = null)
2479  {
2480  if ($count === null) {
2481  // Build base query
2482  $queryBuilder = static::getQueryBuilderForTable('sys_refindex');
2483  $queryBuilder
2484  ->count('*')
2485  ->from('sys_refindex')
2486  ->where(
2487  $queryBuilder->expr()->eq('ref_table', $queryBuilder->createNamedParameter($table))
2488  );
2489 
2490  // Look up the path:
2491  if ($table === '_FILE') {
2492  if (!str_starts_with($ref, ‪Environment::getPublicPath())) {
2493  return '';
2494  }
2495 
2497  $queryBuilder->andWhere(
2498  $queryBuilder->expr()->eq('ref_string', $queryBuilder->createNamedParameter($ref))
2499  );
2500  } else {
2501  $queryBuilder->andWhere(
2502  $queryBuilder->expr()->eq('ref_uid', $queryBuilder->createNamedParameter($ref, ‪Connection::PARAM_INT))
2503  );
2504  if ($table === 'sys_file') {
2505  $queryBuilder->andWhere($queryBuilder->expr()->neq('tablename', $queryBuilder->quote('sys_file_metadata')));
2506  }
2507  }
2508 
2509  $count = $queryBuilder->executeQuery()->fetchOne();
2510  }
2511 
2512  if ($count) {
2513  return $msg ? sprintf($msg, $count) : $count;
2514  }
2515  return $msg ? '' : 0;
2516  }
2517 
2526  public static function translationCount($table, $ref, $msg = '')
2527  {
2528  $count = 0;
2529  if ((‪$GLOBALS['TCA'][$table]['ctrl']['languageField'] ?? null)
2530  && (‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] ?? null)
2531  ) {
2532  $queryBuilder = static::getQueryBuilderForTable($table);
2533  $queryBuilder->getRestrictions()
2534  ->removeAll()
2535  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
2536 
2537  $count = (int)$queryBuilder
2538  ->count('*')
2539  ->from($table)
2540  ->where(
2541  $queryBuilder->expr()->eq(
2542  ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
2543  $queryBuilder->createNamedParameter($ref, ‪Connection::PARAM_INT)
2544  ),
2545  $queryBuilder->expr()->neq(
2546  ‪$GLOBALS['TCA'][$table]['ctrl']['languageField'],
2547  $queryBuilder->createNamedParameter(0, ‪Connection::PARAM_INT)
2548  )
2549  )
2550  ->executeQuery()
2551  ->fetchOne();
2552  }
2553 
2554  if ($count > 0) {
2555  return $msg ? sprintf($msg, $count) : (string)$count;
2556  }
2557  return $msg ? '' : '0';
2558  }
2559 
2560  /*******************************************
2561  *
2562  * Workspaces / Versioning
2563  *
2564  *******************************************/
2577  public static function selectVersionsOfRecord(
2578  $table,
2579  ‪$uid,
2580  ‪$fields = '*',
2581  $workspace = 0,
2582  $includeDeletedRecords = false,
2583  $row = null
2584  ) {
2585  if (!static::isTableWorkspaceEnabled($table)) {
2586  return null;
2587  }
2588 
2589  $outputRows = [];
2590  if (is_array($row) && !$includeDeletedRecords) {
2591  $row['_CURRENT_VERSION'] = true;
2592  $outputRows[] = $row;
2593  } else {
2594  // Select UID version:
2595  $row = self::getRecord($table, ‪$uid, ‪$fields, '', !$includeDeletedRecords);
2596  // Add rows to output array:
2597  if ($row) {
2598  $row['_CURRENT_VERSION'] = true;
2599  $outputRows[] = $row;
2600  }
2601  }
2602 
2603  $queryBuilder = static::getQueryBuilderForTable($table);
2604  $queryBuilder->getRestrictions()->removeAll();
2605 
2606  // build fields to select
2607  $queryBuilder->select(...‪GeneralUtility::trimExplode(',', ‪$fields));
2608 
2609  $queryBuilder
2610  ->from($table)
2611  ->where(
2612  $queryBuilder->expr()->neq('uid', $queryBuilder->createNamedParameter(‪$uid, ‪Connection::PARAM_INT)),
2613  $queryBuilder->expr()->eq('t3ver_oid', $queryBuilder->createNamedParameter(‪$uid, ‪Connection::PARAM_INT))
2614  )
2615  ->orderBy('uid', 'DESC');
2616 
2617  if (!$includeDeletedRecords) {
2618  $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
2619  }
2620 
2621  if ($workspace === 0) {
2622  // Only in Live WS
2623  $queryBuilder->andWhere(
2624  $queryBuilder->expr()->eq(
2625  't3ver_wsid',
2626  $queryBuilder->createNamedParameter(0, ‪Connection::PARAM_INT)
2627  )
2628  );
2629  } elseif ($workspace !== null) {
2630  // In Live WS and Workspace with given ID
2631  $queryBuilder->andWhere(
2632  $queryBuilder->expr()->in(
2633  't3ver_wsid',
2634  $queryBuilder->createNamedParameter([0, (int)$workspace], ‪Connection::PARAM_INT_ARRAY)
2635  )
2636  );
2637  }
2638 
2639  $rows = $queryBuilder->executeQuery()->fetchAllAssociative();
2640 
2641  // Add rows to output array:
2642  if (is_array($rows)) {
2643  $outputRows = array_merge($outputRows, $rows);
2644  }
2645  return $outputRows;
2646  }
2647 
2665  public static function workspaceOL($table, &$row, $wsid = -99, $unsetMovePointers = false)
2666  {
2667  if (!‪ExtensionManagementUtility::isLoaded('workspaces') || !is_array($row) || !static::isTableWorkspaceEnabled($table)) {
2668  return;
2669  }
2670 
2671  // Initialize workspace ID
2672  $wsid = (int)$wsid;
2673  if ($wsid === -99 && static::getBackendUserAuthentication() instanceof BackendUserAuthentication) {
2674  $wsid = static::getBackendUserAuthentication()->workspace;
2675  }
2676  if ($wsid === 0) {
2677  // Return early if in live workspace
2678  return;
2679  }
2680 
2681  // Check if input record is a moved record
2682  $incomingRecordIsAMoveVersion = false;
2683  if (isset($row['t3ver_oid'])
2684  && $row['t3ver_oid'] > 0
2685  && VersionState::tryFrom($row['t3ver_state'] ?? 0) === VersionState::MOVE_POINTER
2686  ) {
2687  // @todo: This handling needs a review, together with the 4th param $unsetMovePointers
2688  $incomingRecordIsAMoveVersion = true;
2689  }
2690 
2691  $wsAlt = self::getWorkspaceVersionOfRecord(
2692  $wsid,
2693  $table,
2694  $row['uid'],
2695  implode(',', static::purgeComputedPropertyNames(array_keys($row)))
2696  );
2697 
2698  // If version was found, swap the default record with that one.
2699  if (is_array($wsAlt)) {
2700  // If t3ver_state is not found, then find it... (but we like best if it is here...)
2701  if (!isset($wsAlt['t3ver_state'])) {
2702  $stateRec = self::getRecord($table, $wsAlt['uid'], 't3ver_state');
2703  $versionState = VersionState::tryFrom($stateRec['t3ver_state'] ?? 0);
2704  } else {
2705  $versionState = VersionState::tryFrom($wsAlt['t3ver_state']);
2706  }
2707  // Check if this is in move-state
2708  if ($versionState === VersionState::MOVE_POINTER) {
2709  // @todo Same problem as frontend in versionOL(). See TODO point there and todo above.
2710  if (!$incomingRecordIsAMoveVersion && $unsetMovePointers) {
2711  $row = false;
2712  return;
2713  }
2714  // When a moved record is found the "PID" value contains the newly moved location
2715  // Whereas the _ORIG_pid field contains the PID of the live version
2716  $wsAlt['_ORIG_pid'] = $row['pid'];
2717  }
2718  // Swap UID
2719  if ($versionState !== VersionState::NEW_PLACEHOLDER) {
2720  $wsAlt['_ORIG_uid'] = $wsAlt['uid'];
2721  $wsAlt['uid'] = $row['uid'];
2722  }
2723  // Backend css class:
2724  $wsAlt['_CSSCLASS'] = 'ver-element';
2725  // Changing input record to the workspace version alternative:
2726  $row = $wsAlt;
2727  }
2728  }
2729 
2739  public static function getWorkspaceVersionOfRecord($workspace, $table, ‪$uid, ‪$fields = '*')
2740  {
2741  if (‪ExtensionManagementUtility::isLoaded('workspaces')) {
2742  if ($workspace !== 0 && self::isTableWorkspaceEnabled($table)) {
2743  // Select workspace version of record:
2744  $queryBuilder = static::getQueryBuilderForTable($table);
2745  $queryBuilder->getRestrictions()
2746  ->removeAll()
2747  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
2748 
2749  // build fields to select
2750  $queryBuilder->select(...‪GeneralUtility::trimExplode(',', ‪$fields));
2751 
2752  $row = $queryBuilder
2753  ->from($table)
2754  ->where(
2755  $queryBuilder->expr()->eq(
2756  't3ver_wsid',
2757  $queryBuilder->createNamedParameter($workspace, ‪Connection::PARAM_INT)
2758  ),
2759  $queryBuilder->expr()->or(
2760  // t3ver_state=1 does not contain a t3ver_oid, and returns itself
2761  $queryBuilder->expr()->and(
2762  $queryBuilder->expr()->eq(
2763  'uid',
2764  $queryBuilder->createNamedParameter(‪$uid, ‪Connection::PARAM_INT)
2765  ),
2766  $queryBuilder->expr()->eq(
2767  't3ver_state',
2768  $queryBuilder->createNamedParameter(VersionState::NEW_PLACEHOLDER->value, ‪Connection::PARAM_INT)
2769  )
2770  ),
2771  $queryBuilder->expr()->eq(
2772  't3ver_oid',
2773  $queryBuilder->createNamedParameter(‪$uid, ‪Connection::PARAM_INT)
2774  )
2775  )
2776  )
2777  ->executeQuery()
2778  ->fetchAssociative();
2779 
2780  return $row;
2781  }
2782  }
2783  return false;
2784  }
2785 
2797  public static function getPossibleWorkspaceVersionIdsOfLiveRecordIds(string $table, array $liveRecordIds, int $workspaceId): array
2798  {
2799  if ($liveRecordIds === [] || $workspaceId === 0 || !self::isTableWorkspaceEnabled($table)) {
2800  return [];
2801  }
2802  $doOverlaysForRecords = [];
2803  $connection = self::getConnectionForTable($table);
2804  $maxChunk = ‪PlatformInformation::getMaxBindParameters($connection->getDatabasePlatform());
2805  foreach (array_chunk($liveRecordIds, (int)floor($maxChunk / 2)) as $liveRecordIdChunk) {
2806  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
2807  $doOverlaysForRecordsStatement = $queryBuilder
2808  ->select('t3ver_oid', 'uid')
2809  ->from($table)
2810  ->where(
2811  $queryBuilder->expr()->eq('t3ver_wsid', $queryBuilder->createNamedParameter($workspaceId, ‪Connection::PARAM_INT)),
2812  $queryBuilder->expr()->in('t3ver_oid', $queryBuilder->quoteArrayBasedValueListToIntegerList($liveRecordIdChunk))
2813  )
2814  ->executeQuery();
2815  while ($recordWithVersionedRecord = $doOverlaysForRecordsStatement->fetchNumeric()) {
2816  $doOverlaysForRecords[(int)$recordWithVersionedRecord[0]] = (int)$recordWithVersionedRecord[1];
2817  }
2818  }
2819  return $doOverlaysForRecords;
2820  }
2821 
2830  public static function getLiveVersionOfRecord($table, ‪$uid, ‪$fields = '*')
2831  {
2832  $liveVersionId = self::getLiveVersionIdOfRecord($table, ‪$uid);
2833  if ($liveVersionId !== null) {
2834  return self::getRecord($table, $liveVersionId, ‪$fields);
2835  }
2836  return null;
2837  }
2838 
2847  public static function getLiveVersionIdOfRecord($table, ‪$uid)
2848  {
2849  if (!‪ExtensionManagementUtility::isLoaded('workspaces')) {
2850  return null;
2851  }
2852  $liveVersionId = null;
2853  if (self::isTableWorkspaceEnabled($table)) {
2854  $currentRecord = self::getRecord($table, ‪$uid, 'pid,t3ver_oid,t3ver_state');
2855  if (is_array($currentRecord)) {
2856  if ((int)$currentRecord['t3ver_oid'] > 0) {
2857  $liveVersionId = $currentRecord['t3ver_oid'];
2858  } elseif (VersionState::tryFrom($currentRecord['t3ver_state'] ?? 0) === VersionState::NEW_PLACEHOLDER) {
2859  // New versions do not have a live counterpart
2860  $liveVersionId = (int)‪$uid;
2861  }
2862  }
2863  }
2864  return $liveVersionId;
2865  }
2866 
2875  public static function wsMapId($table, ‪$uid)
2876  {
2877  $wsRec = null;
2878  if (static::getBackendUserAuthentication() instanceof BackendUserAuthentication) {
2879  $wsRec = self::getWorkspaceVersionOfRecord(
2880  static::getBackendUserAuthentication()->workspace,
2881  $table,
2882  ‪$uid,
2883  'uid'
2884  );
2885  }
2886  return is_array($wsRec) ? $wsRec['uid'] : ‪$uid;
2887  }
2888 
2889  /*******************************************
2890  *
2891  * Miscellaneous
2892  *
2893  *******************************************/
2894 
2901  public static function isTableWorkspaceEnabled($table)
2902  {
2903  return !empty(‪$GLOBALS['TCA'][$table]['ctrl']['versioningWS']);
2904  }
2905 
2913  public static function getTcaFieldConfiguration($table, $field)
2914  {
2915  $configuration = [];
2916  if (isset(‪$GLOBALS['TCA'][$table]['columns'][$field]['config'])) {
2917  $configuration = ‪$GLOBALS['TCA'][$table]['columns'][$field]['config'];
2918  }
2919  return $configuration;
2920  }
2921 
2930  public static function isWebMountRestrictionIgnored($table)
2931  {
2932  return !empty(‪$GLOBALS['TCA'][$table]['ctrl']['security']['ignoreWebMountRestriction']);
2933  }
2934 
2943  public static function isRootLevelRestrictionIgnored($table)
2944  {
2945  return !empty(‪$GLOBALS['TCA'][$table]['ctrl']['security']['ignoreRootLevelRestriction']);
2946  }
2947 
2956  public static function getAllowedFieldsForTable(string $table, bool $checkUserAccess = true): array
2957  {
2958  if (!is_array(‪$GLOBALS['TCA'][$table]['columns'] ?? null)) {
2959  self::getLogger()->error('TCA is broken for the table "' . $table . '": no required "columns" entry in TCA.');
2960  return [];
2961  }
2962 
2963  $fieldList = [];
2964  $backendUser = self::getBackendUserAuthentication();
2965 
2966  // Traverse configured columns and add them to field array, if available for user.
2967  foreach (‪$GLOBALS['TCA'][$table]['columns'] as $fieldName => $fieldValue) {
2968  if (($fieldValue['config']['type'] ?? '') === 'none') {
2969  // Never render or fetch type=none fields from db
2970  continue;
2971  }
2972  if (!$checkUserAccess
2973  || (
2974  (
2975  !($fieldValue['exclude'] ?? null)
2976  || $backendUser->check('non_exclude_fields', $table . ':' . $fieldName)
2977  )
2978  && ($fieldValue['config']['type'] ?? '') !== 'passthrough'
2979  )
2980  ) {
2981  $fieldList[] = $fieldName;
2982  }
2983  }
2984 
2985  $fieldList[] = 'uid';
2986  $fieldList[] = 'pid';
2987 
2988  // Add date fields - if defined for the table
2989  if (‪$GLOBALS['TCA'][$table]['ctrl']['tstamp'] ?? false) {
2990  $fieldList[] = ‪$GLOBALS['TCA'][$table]['ctrl']['tstamp'];
2991  }
2992  if (‪$GLOBALS['TCA'][$table]['ctrl']['crdate'] ?? false) {
2993  $fieldList[] = ‪$GLOBALS['TCA'][$table]['ctrl']['crdate'];
2994  }
2995 
2996  // Add more special fields in case user should not be checked or is admin
2997  if (!$checkUserAccess || $backendUser->isAdmin()) {
2998  if (‪$GLOBALS['TCA'][$table]['ctrl']['sortby'] ?? false) {
2999  $fieldList[] = ‪$GLOBALS['TCA'][$table]['ctrl']['sortby'];
3000  }
3001  if (self::isTableWorkspaceEnabled($table)) {
3002  $fieldList[] = 't3ver_state';
3003  $fieldList[] = 't3ver_wsid';
3004  $fieldList[] = 't3ver_oid';
3005  }
3006  }
3007 
3008  // Return unique field list
3009  return array_values(array_unique($fieldList));
3010  }
3011 
3018  public static function convertDatabaseRowValuesToPhp(string $table, array $row): array
3019  {
3020  $tableTca = ‪$GLOBALS['TCA'][$table] ?? [];
3021  if (!is_array($tableTca) || $tableTca === []) {
3022  return $row;
3023  }
3024  $platform = static::getConnectionForTable($table)->getDatabasePlatform();
3025  foreach ($row as $field => $value) {
3026  // @todo Only handle specific TCA type=json
3027  if (($tableTca['columns'][$field]['config']['type'] ?? '') === 'json') {
3028  $row[$field] = Type::getType('json')->convertToPHPValue($value, $platform);
3029  }
3030  }
3031  return $row;
3032  }
3033 
3038  protected static function getConnectionForTable($table)
3039  {
3040  return GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
3041  }
3042 
3047  protected static function getQueryBuilderForTable($table)
3048  {
3049  return GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
3050  }
3051 
3055  protected static function getLogger()
3056  {
3057  return GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
3058  }
3059 
3060  protected static function getLanguageService(): LanguageService
3061  {
3062  return ‪$GLOBALS['LANG'];
3063  }
3064 
3065  protected static function getBackendUserAuthentication(): ?BackendUserAuthentication
3066  {
3067  return ‪$GLOBALS['BE_USER'] ?? null;
3068  }
3069 }
‪TYPO3\CMS\Core\Database\Query\QueryHelper\getDateTimeFormats
‪static array getDateTimeFormats()
Definition: QueryHelper.php:183
‪TYPO3\CMS\Core\Utility\PathUtility\stripPathSitePrefix
‪static stripPathSitePrefix(string $path)
Definition: PathUtility.php:428
‪TYPO3\CMS\Core\Resource\ProcessedFile\CONTEXT_IMAGEPREVIEW
‪const CONTEXT_IMAGEPREVIEW
Definition: ProcessedFile.php:55
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:27
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:52
‪TYPO3\CMS\Core\Utility\GeneralUtility\fixed_lgd_cs
‪static string fixed_lgd_cs(string $string, int $chars, string $appendString='...')
Definition: GeneralUtility.php:92
‪TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection\create
‪static create(string $jsonString, array $tcaConfig=[])
Definition: CropVariantCollection.php:37
‪TYPO3\CMS\Core\Database\Platform\PlatformInformation\getMaxBindParameters
‪static getMaxBindParameters(DoctrineAbstractPlatform $platform)
Definition: PlatformInformation.php:106
‪TYPO3\CMS\Webhooks\Message\$details
‪identifier readonly UriInterface readonly array $details
Definition: MfaVerificationErrorOccurredMessage.php:37
‪TYPO3\CMS\Core\Database\RelationHandler
Definition: RelationHandler.php:36
‪TYPO3\CMS\Core\Versioning\VersionState
‪VersionState
Definition: VersionState.php:22
‪TYPO3\CMS\Core\Resource\ProcessedFile\CONTEXT_IMAGECROPSCALEMASK
‪const CONTEXT_IMAGECROPSCALEMASK
Definition: ProcessedFile.php:61
‪TYPO3\CMS\Core\Core\Environment\getPublicPath
‪static getPublicPath()
Definition: Environment.php:187
‪TYPO3\CMS\Core\Domain\Repository\PageRepository\DOKTYPE_SHORTCUT
‪const DOKTYPE_SHORTCUT
Definition: PageRepository.php:100
‪TYPO3\CMS\Core\Domain\Repository\PageRepository\DOKTYPE_LINK
‪const DOKTYPE_LINK
Definition: PageRepository.php:99
‪TYPO3\CMS\Core\Site\Entity\NullSite
Definition: NullSite.php:32
‪TYPO3\CMS\Core\Exception\SiteNotFoundException
Definition: SiteNotFoundException.php:25
‪TYPO3\CMS\Core\Site\SiteFinder
Definition: SiteFinder.php:31
‪TYPO3\CMS\Core\Utility\StringUtility\uniqueList
‪static string uniqueList(string $list)
Definition: StringUtility.php:122
‪TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException
Definition: FileDoesNotExistException.php:21
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:34
‪TYPO3\CMS\Backend\Domain\Model\Element\ImmediateActionElement\dispatchCustomEvent
‪static dispatchCustomEvent(string $name, array $details=null, bool $useTop=false)
Definition: ImmediateActionElement.php:53
‪$fields
‪$fields
Definition: pages.php:5
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\isLoaded
‪static isLoaded(string $key)
Definition: ExtensionManagementUtility.php:55
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility
Definition: ExtensionManagementUtility.php:32
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger(mixed $var)
Definition: MathUtility.php:69
‪TYPO3\CMS\Core\TypoScript\PageTsConfig
Definition: PageTsConfig.php:28
‪TYPO3\CMS\Core\Domain\Repository\PageRepository\DOKTYPE_MOUNTPOINT
‪const DOKTYPE_MOUNTPOINT
Definition: PageRepository.php:102
‪TYPO3\CMS\Backend\Utility
Definition: BackendUtility.php:16
‪TYPO3\CMS\Core\Database\Query\QueryHelper
Definition: QueryHelper.php:32
‪TYPO3\CMS\Core\Resource\ResourceFactory
Definition: ResourceFactory.php:42
‪TYPO3\CMS\Webhooks\Message\$record
‪identifier readonly int readonly array $record
Definition: PageModificationMessage.php:36
‪TYPO3\CMS\Core\Cache\CacheManager
Definition: CacheManager.php:36
‪TYPO3\CMS\Core\TypoScript\PageTsConfigFactory
Definition: PageTsConfigFactory.php:44
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:62
‪TYPO3\CMS\Core\Core\Environment\isCli
‪static isCli()
Definition: Environment.php:145
‪TYPO3\CMS\Core\Resource\ProcessedFile
Definition: ProcessedFile.php:47
‪$output
‪$output
Definition: annotationChecker.php:114
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:41
‪TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
Definition: FrontendInterface.php:22
‪TYPO3\CMS\Core\DataHandling\ItemProcessingService
Definition: ItemProcessingService.php:34
‪TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider
Definition: TranslationConfigurationProvider.php:39
‪TYPO3\CMS\Webhooks\Message\$uid
‪identifier readonly int $uid
Definition: PageModificationMessage.php:35
‪TYPO3\CMS\Core\Database\Query\QueryHelper\stripLogicalOperatorPrefix
‪static string stripLogicalOperatorPrefix(string $constraint)
Definition: QueryHelper.php:171
‪TYPO3\CMS\Core\Utility\ArrayUtility
Definition: ArrayUtility.php:26
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Domain\Repository\PageRepository\SHORTCUT_MODE_NONE
‪const SHORTCUT_MODE_NONE
Definition: PageRepository.php:109
‪TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction
Definition: DeletedRestriction.php:28
‪TYPO3\CMS\Core\Log\LogManager
Definition: LogManager.php:33
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:41
‪if
‪if(PHP_SAPI !=='cli')
Definition: checkNamespaceIntegrity.php:27
‪TYPO3\CMS\Core\Utility\ArrayUtility\sortArraysByKey
‪static array sortArraysByKey(array $arrays, string $key, bool $ascending=true)
Definition: ArrayUtility.php:358
‪TYPO3\CMS\Core\Database\Platform\PlatformInformation
Definition: PlatformInformation.php:33
‪TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection
Definition: CropVariantCollection.php:23
‪TYPO3\CMS\Backend\Domain\Model\Element\ImmediateActionElement\forAction
‪static forAction(string $action)
Definition: ImmediateActionElement.php:32
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:24
‪TYPO3\CMS\Core\Utility\GeneralUtility\inList
‪static bool inList($list, $item)
Definition: GeneralUtility.php:422
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:46
‪TYPO3\CMS\Core\Domain\Repository\PageRepository
Definition: PageRepository.php:69
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Utility\StringUtility
Definition: StringUtility.php:24
‪TYPO3\CMS\Backend\Domain\Model\Element\ImmediateActionElement
Definition: ImmediateActionElement.php:28
‪TYPO3\CMS\Core\Utility\GeneralUtility\intExplode
‪static list< int > intExplode(string $delimiter, string $string, bool $removeEmptyValues=false)
Definition: GeneralUtility.php:756
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT_ARRAY
‪const PARAM_INT_ARRAY
Definition: Connection.php:72
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode(string $delim, string $string, bool $removeEmptyValues=false, int $limit=0)
Definition: GeneralUtility.php:822
‪TYPO3\CMS\Core\Imaging\ImageDimension
Definition: ImageDimension.php:28
‪TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction
Definition: WorkspaceRestriction.php:39