TYPO3 CMS  TYPO3_8-7
BackendUtility.php
Go to the documentation of this file.
1 <?php
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
47 
57 {
64  protected static $tcaTableTypeConfigurationCache = [];
65 
66  /*******************************************
67  *
68  * SQL-related, selecting records, searching
69  *
70  *******************************************/
85  public static function deleteClause($table, $tableAlias = '')
86  {
87  if (empty($GLOBALS['TCA'][$table]['ctrl']['delete'])) {
88  return '';
89  }
90  $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
91  ->getQueryBuilderForTable($table)
92  ->expr();
93  return ' AND ' . $expressionBuilder->eq(
94  ($tableAlias ?: $table) . '.' . $GLOBALS['TCA'][$table]['ctrl']['delete'],
95  0
96  );
97  }
98 
113  public static function getRecord($table, $uid, $fields = '*', $where = '', $useDeleteClause = true)
114  {
115  // Ensure we have a valid uid (not 0 and not NEWxxxx) and a valid TCA
116  if ((int)$uid && !empty($GLOBALS['TCA'][$table])) {
117  $queryBuilder = static::getQueryBuilderForTable($table);
118 
119  // do not use enabled fields here
120  $queryBuilder->getRestrictions()->removeAll();
121 
122  // should the delete clause be used
123  if ($useDeleteClause) {
124  $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
125  }
126 
127  // set table and where clause
128  $queryBuilder
129  ->select(...GeneralUtility::trimExplode(',', $fields, true))
130  ->from($table)
131  ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter((int)$uid, \PDO::PARAM_INT)));
132 
133  // add custom where clause
134  if ($where) {
135  $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($where));
136  }
137 
138  $row = $queryBuilder->execute()->fetch();
139  if ($row) {
140  return $row;
141  }
142  }
143  return null;
144  }
145 
157  public static function getRecordWSOL(
158  $table,
159  $uid,
160  $fields = '*',
161  $where = '',
162  $useDeleteClause = true,
163  $unsetMovePointers = false
164  ) {
165  if ($fields !== '*') {
166  $internalFields = GeneralUtility::uniqueList($fields . ',uid,pid');
167  $row = self::getRecord($table, $uid, $internalFields, $where, $useDeleteClause);
168  self::workspaceOL($table, $row, -99, $unsetMovePointers);
169  if (is_array($row)) {
170  foreach ($row as $key => $_) {
171  if (!GeneralUtility::inList($fields, $key) && $key[0] !== '_') {
172  unset($row[$key]);
173  }
174  }
175  }
176  } else {
177  $row = self::getRecord($table, $uid, $fields, $where, $useDeleteClause);
178  self::workspaceOL($table, $row, -99, $unsetMovePointers);
179  }
180  return $row;
181  }
182 
196  public static function getRecordRaw($table, $where = '', $fields = '*')
197  {
199  $queryBuilder = static::getQueryBuilderForTable($table);
200  $queryBuilder->getRestrictions()->removeAll();
201 
202  $row = $queryBuilder
203  ->select(...GeneralUtility::trimExplode(',', $fields, true))
204  ->from($table)
206  ->execute()
207  ->fetch();
208 
209  return $row ?: false;
210  }
211 
229  public static function getRecordsByField(
230  $theTable,
231  $theField,
232  $theValue,
233  $whereClause = '',
234  $groupBy = '',
235  $orderBy = '',
236  $limit = '',
237  $useDeleteClause = true,
238  $queryBuilder = null
239  ) {
241  if (is_array($GLOBALS['TCA'][$theTable])) {
242  if (null === $queryBuilder) {
243  $queryBuilder = static::getQueryBuilderForTable($theTable);
244  }
245 
246  // Show all records except versioning placeholders
247  $queryBuilder->getRestrictions()
248  ->removeAll()
249  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
250 
251  // Remove deleted records from the query result
252  if ($useDeleteClause) {
253  $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
254  }
255 
256  // build fields to select
257  $queryBuilder
258  ->select('*')
259  ->from($theTable)
260  ->where($queryBuilder->expr()->eq($theField, $queryBuilder->createNamedParameter($theValue)));
261 
262  // additional where
263  if ($whereClause) {
264  $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($whereClause));
265  }
266 
267  // group by
268  if ($groupBy !== '') {
269  $queryBuilder->groupBy(QueryHelper::parseGroupBy($groupBy));
270  }
271 
272  // order by
273  if ($orderBy !== '') {
274  foreach (QueryHelper::parseOrderBy($orderBy) as $orderPair) {
275  list($fieldName, $order) = $orderPair;
276  $queryBuilder->addOrderBy($fieldName, $order);
277  }
278  }
279 
280  // limit
281  if ($limit !== '') {
282  if (strpos($limit, ',')) {
283  $limitOffsetAndMax = GeneralUtility::intExplode(',', $limit);
284  $queryBuilder->setFirstResult((int)$limitOffsetAndMax[0]);
285  $queryBuilder->setMaxResults((int)$limitOffsetAndMax[1]);
286  } else {
287  $queryBuilder->setMaxResults((int)$limit);
288  }
289  }
290 
291  $rows = $queryBuilder->execute()->fetchAll();
292  return $rows;
293  }
294  return null;
295  }
296 
303  public static function purgeComputedPropertiesFromRecord(array $record): array
304  {
305  return array_filter(
306  $record,
307  function (string $propertyName): bool {
308  return $propertyName[0] !== '_';
309  },
310  ARRAY_FILTER_USE_KEY
311  );
312  }
313 
320  public static function purgeComputedPropertyNames(array $propertyNames): array
321  {
322  return array_filter(
323  $propertyNames,
324  function (string $propertyName): bool {
325  return $propertyName[0] !== '_';
326  }
327  );
328  }
329 
337  public static function splitTable_Uid($str)
338  {
339  list($uid, $table) = explode('_', strrev($str), 2);
340  return [strrev($table), strrev($uid)];
341  }
342 
353  public static function getSQLselectableList($in_list, $tablename, $default_tablename)
354  {
356  $list = [];
357  if ((string)trim($in_list) != '') {
358  $tempItemArray = explode(',', trim($in_list));
359  foreach ($tempItemArray as $key => $val) {
360  $val = strrev($val);
361  $parts = explode('_', $val, 2);
362  if ((string)trim($parts[0]) != '') {
363  $theID = (int)strrev($parts[0]);
364  $theTable = trim($parts[1]) ? strrev(trim($parts[1])) : $default_tablename;
365  if ($theTable == $tablename) {
366  $list[] = $theID;
367  }
368  }
369  }
370  }
371  return implode(',', $list);
372  }
373 
384  public static function BEenableFields($table, $inv = false)
385  {
386  $ctrl = $GLOBALS['TCA'][$table]['ctrl'];
387  $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
388  ->getConnectionForTable($table)
389  ->getExpressionBuilder();
390  $query = $expressionBuilder->andX();
391  $invQuery = $expressionBuilder->orX();
392 
393  if (is_array($ctrl)) {
394  if (is_array($ctrl['enablecolumns'])) {
395  if ($ctrl['enablecolumns']['disabled']) {
396  $field = $table . '.' . $ctrl['enablecolumns']['disabled'];
397  $query->add($expressionBuilder->eq($field, 0));
398  $invQuery->add($expressionBuilder->neq($field, 0));
399  }
400  if ($ctrl['enablecolumns']['starttime']) {
401  $field = $table . '.' . $ctrl['enablecolumns']['starttime'];
402  $query->add($expressionBuilder->lte($field, (int)$GLOBALS['SIM_ACCESS_TIME']));
403  $invQuery->add(
404  $expressionBuilder->andX(
405  $expressionBuilder->neq($field, 0),
406  $expressionBuilder->gt($field, (int)$GLOBALS['SIM_ACCESS_TIME'])
407  )
408  );
409  }
410  if ($ctrl['enablecolumns']['endtime']) {
411  $field = $table . '.' . $ctrl['enablecolumns']['endtime'];
412  $query->add(
413  $expressionBuilder->orX(
414  $expressionBuilder->eq($field, 0),
415  $expressionBuilder->gt($field, (int)$GLOBALS['SIM_ACCESS_TIME'])
416  )
417  );
418  $invQuery->add(
419  $expressionBuilder->andX(
420  $expressionBuilder->neq($field, 0),
421  $expressionBuilder->lte($field, (int)$GLOBALS['SIM_ACCESS_TIME'])
422  )
423  );
424  }
425  }
426  }
427 
428  if ($query->count() === 0) {
429  return '';
430  }
431 
432  return ' AND ' . ($inv ? $invQuery : $query);
433  }
434 
444  public static function getRecordLocalization($table, $uid, $language, $andWhereClause = '')
445  {
446  $recordLocalization = false;
447 
448  // Pages still stores translations in the pages_language_overlay table, all other tables store in themself
449  if ($table === 'pages') {
450  $table = 'pages_language_overlay';
451  }
452 
453  if (self::isTableLocalizable($table)) {
454  $tcaCtrl = $GLOBALS['TCA'][$table]['ctrl'];
455 
456  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
457  ->getQueryBuilderForTable($table);
458  $queryBuilder->getRestrictions()
459  ->removeAll()
460  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
461  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
462 
463  $queryBuilder->select('*')
464  ->from($table)
465  ->where(
466  $queryBuilder->expr()->eq(
467  $tcaCtrl['translationSource'] ?? $tcaCtrl['transOrigPointerField'],
468  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
469  ),
470  $queryBuilder->expr()->eq(
471  $tcaCtrl['languageField'],
472  $queryBuilder->createNamedParameter((int)$language, \PDO::PARAM_INT)
473  )
474  )
475  ->setMaxResults(1);
476 
477  if ($andWhereClause) {
478  $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($andWhereClause));
479  }
480 
481  $recordLocalization = $queryBuilder->execute()->fetchAll();
482  }
483 
484  return $recordLocalization;
485  }
486 
487  /*******************************************
488  *
489  * Page tree, TCA related
490  *
491  *******************************************/
506  public static function BEgetRootLine($uid, $clause = '', $workspaceOL = false)
507  {
508  static $BEgetRootLine_cache = [];
509  $output = [];
510  $pid = $uid;
511  $ident = $pid . '-' . $clause . '-' . $workspaceOL;
512  if (is_array($BEgetRootLine_cache[$ident] ?? false)) {
513  $output = $BEgetRootLine_cache[$ident];
514  } else {
515  $loopCheck = 100;
516  $theRowArray = [];
517  while ($uid != 0 && $loopCheck) {
518  $loopCheck--;
519  $row = self::getPageForRootline($uid, $clause, $workspaceOL);
520  if (is_array($row)) {
521  $uid = $row['pid'];
522  $theRowArray[] = $row;
523  } else {
524  break;
525  }
526  }
527  if ($uid == 0) {
528  $theRowArray[] = ['uid' => 0, 'title' => ''];
529  }
530  $c = count($theRowArray);
531  foreach ($theRowArray as $val) {
532  $c--;
533  $output[$c] = [
534  'uid' => $val['uid'],
535  'pid' => $val['pid'],
536  'title' => $val['title'],
537  'doktype' => $val['doktype'],
538  'tsconfig_includes' => $val['tsconfig_includes'],
539  'TSconfig' => $val['TSconfig'],
540  'is_siteroot' => $val['is_siteroot'],
541  't3ver_oid' => $val['t3ver_oid'],
542  't3ver_wsid' => $val['t3ver_wsid'],
543  't3ver_state' => $val['t3ver_state'],
544  't3ver_stage' => $val['t3ver_stage'],
545  'backend_layout_next_level' => $val['backend_layout_next_level']
546  ];
547  if (isset($val['_ORIG_pid'])) {
548  $output[$c]['_ORIG_pid'] = $val['_ORIG_pid'];
549  }
550  }
551  $BEgetRootLine_cache[$ident] = $output;
552  }
553  return $output;
554  }
555 
565  protected static function getPageForRootline($uid, $clause, $workspaceOL)
566  {
567  static $getPageForRootline_cache = [];
568  $ident = $uid . '-' . $clause . '-' . $workspaceOL;
569  if (is_array($getPageForRootline_cache[$ident] ?? false)) {
570  $row = $getPageForRootline_cache[$ident];
571  } else {
572  $queryBuilder = static::getQueryBuilderForTable('pages');
573  $queryBuilder->getRestrictions()
574  ->removeAll()
575  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
576 
577  $row = $queryBuilder
578  ->select(
579  'pid',
580  'uid',
581  'title',
582  'doktype',
583  'tsconfig_includes',
584  'TSconfig',
585  'is_siteroot',
586  't3ver_oid',
587  't3ver_wsid',
588  't3ver_state',
589  't3ver_stage',
590  'backend_layout_next_level'
591  )
592  ->from('pages')
593  ->where(
594  $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)),
596  )
597  ->execute()
598  ->fetch();
599 
600  if ($row) {
601  $newLocation = false;
602  if ($workspaceOL) {
603  self::workspaceOL('pages', $row);
604  $newLocation = self::getMovePlaceholder('pages', $row['uid'], 'pid');
605  }
606  if (is_array($row)) {
607  if ($newLocation !== false) {
608  $row['pid'] = $newLocation['pid'];
609  } else {
610  self::fixVersioningPid('pages', $row);
611  }
612  $getPageForRootline_cache[$ident] = $row;
613  }
614  }
615  }
616  return $row;
617  }
618 
625  public static function openPageTree($pid, $clearExpansion)
626  {
627  $beUser = static::getBackendUserAuthentication();
628  // Get current expansion data:
629  if ($clearExpansion) {
630  $expandedPages = [];
631  } else {
632  $expandedPages = unserialize($beUser->uc['browseTrees']['browsePages']);
633  }
634  // Get rootline:
635  $rL = self::BEgetRootLine($pid);
636  // First, find out what mount index to use (if more than one DB mount exists):
637  $mountIndex = 0;
638  $mountKeys = array_flip($beUser->returnWebmounts());
639  foreach ($rL as $rLDat) {
640  if (isset($mountKeys[$rLDat['uid']])) {
641  $mountIndex = $mountKeys[$rLDat['uid']];
642  break;
643  }
644  }
645  // Traverse rootline and open paths:
646  foreach ($rL as $rLDat) {
647  $expandedPages[$mountIndex][$rLDat['uid']] = 1;
648  }
649  // Write back:
650  $beUser->uc['browseTrees']['browsePages'] = serialize($expandedPages);
651  $beUser->writeUC();
652  }
653 
665  public static function getRecordPath($uid, $clause, $titleLimit, $fullTitleLimit = 0)
666  {
667  if (!$titleLimit) {
668  $titleLimit = 1000;
669  }
670  $output = $fullOutput = '/';
671  $clause = trim($clause);
672  if ($clause !== '' && substr($clause, 0, 3) !== 'AND') {
673  $clause = 'AND ' . $clause;
674  }
675  $data = self::BEgetRootLine($uid, $clause);
676  foreach ($data as $record) {
677  if ($record['uid'] === 0) {
678  continue;
679  }
680  $output = '/' . GeneralUtility::fixed_lgd_cs(strip_tags($record['title']), $titleLimit) . $output;
681  if ($fullTitleLimit) {
682  $fullOutput = '/' . GeneralUtility::fixed_lgd_cs(strip_tags($record['title']), $fullTitleLimit) . $fullOutput;
683  }
684  }
685  if ($fullTitleLimit) {
686  return [$output, $fullOutput];
687  }
688  return $output;
689  }
690 
699  public static function getOriginalTranslationTable($table)
700  {
701  return $table === 'pages_language_overlay' ? 'pages' : $table;
702  }
703 
710  public static function isTableLocalizable($table)
711  {
712  $isLocalizable = false;
713  if (isset($GLOBALS['TCA'][$table]['ctrl']) && is_array($GLOBALS['TCA'][$table]['ctrl'])) {
714  $tcaCtrl = $GLOBALS['TCA'][$table]['ctrl'];
715  $isLocalizable = isset($tcaCtrl['languageField']) && $tcaCtrl['languageField'] && isset($tcaCtrl['transOrigPointerField']) && $tcaCtrl['transOrigPointerField'];
716  }
717  return $isLocalizable;
718  }
719 
730  public static function getInlineLocalizationMode($table, $fieldOrConfig)
731  {
732  $localizationMode = false;
733  $config = null;
734  if (is_array($fieldOrConfig) && !empty($fieldOrConfig)) {
735  $config = $fieldOrConfig;
736  } elseif (is_string($fieldOrConfig) && isset($GLOBALS['TCA'][$table]['columns'][$fieldOrConfig]['config'])) {
737  $config = $GLOBALS['TCA'][$table]['columns'][$fieldOrConfig]['config'];
738  }
739  if (is_array($config) && isset($config['type']) && $config['type'] === 'inline' && self::isTableLocalizable($table)) {
740  $localizationMode = isset($config['behaviour']['localizationMode']) && $config['behaviour']['localizationMode']
741  ? $config['behaviour']['localizationMode']
742  : 'select';
743  // The mode 'select' is not possible when child table is not localizable at all:
744  if ($localizationMode === 'select' && !self::isTableLocalizable($config['foreign_table'])) {
745  $localizationMode = false;
746  }
747  }
748  return $localizationMode;
749  }
750 
760  public static function readPageAccess($id, $perms_clause)
761  {
762  if ((string)$id !== '') {
763  $id = (int)$id;
764  if (!$id) {
765  if (static::getBackendUserAuthentication()->isAdmin()) {
766  $path = '/';
767  $pageinfo['_thePath'] = $path;
768  return $pageinfo;
769  }
770  } else {
771  $pageinfo = self::getRecord('pages', $id, '*', $perms_clause);
772  if ($pageinfo['uid'] && static::getBackendUserAuthentication()->isInWebMount($id, $perms_clause)) {
773  self::workspaceOL('pages', $pageinfo);
774  if (is_array($pageinfo)) {
775  self::fixVersioningPid('pages', $pageinfo);
776  list($pageinfo['_thePath'], $pageinfo['_thePathFull']) = self::getRecordPath((int)$pageinfo['uid'], $perms_clause, 15, 1000);
777  return $pageinfo;
778  }
779  }
780  }
781  }
782  return false;
783  }
784 
793  public static function getTCAtypes($table, $rec, $useFieldNameAsKey = false)
794  {
795  if ($GLOBALS['TCA'][$table]) {
796  // Get type value:
797  $fieldValue = self::getTCAtypeValue($table, $rec);
798  $cacheIdentifier = $table . '-type-' . $fieldValue . '-fnk-' . $useFieldNameAsKey;
799 
800  // Fetch from first-level-cache if available
801  if (isset(self::$tcaTableTypeConfigurationCache[$cacheIdentifier])) {
802  return self::$tcaTableTypeConfigurationCache[$cacheIdentifier];
803  }
804 
805  // Get typesConf
806  $typesConf = $GLOBALS['TCA'][$table]['types'][$fieldValue];
807  // Get fields list and traverse it
808  $fieldList = explode(',', $typesConf['showitem']);
809 
810  // Add subtype fields e.g. for a valid RTE transformation
811  // The RTE runs the DB -> RTE transformation only, if the RTE field is part of the getTCAtypes array
812  if (isset($typesConf['subtype_value_field'])) {
813  $subType = $rec[$typesConf['subtype_value_field']];
814  if (isset($typesConf['subtypes_addlist'][$subType])) {
815  $subFields = GeneralUtility::trimExplode(',', $typesConf['subtypes_addlist'][$subType], true);
816  $fieldList = array_merge($fieldList, $subFields);
817  }
818  }
819 
820  // Add palette fields e.g. for a valid RTE transformation
821  $paletteFieldList = [];
822  foreach ($fieldList as $fieldData) {
823  list($pFieldName, $pAltTitle, $pPalette) = GeneralUtility::trimExplode(';', $fieldData);
824  if ($pPalette
825  && isset($GLOBALS['TCA'][$table]['palettes'][$pPalette])
826  && is_array($GLOBALS['TCA'][$table]['palettes'][$pPalette])
827  && isset($GLOBALS['TCA'][$table]['palettes'][$pPalette]['showitem'])
828  ) {
829  $paletteFields = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$table]['palettes'][$pPalette]['showitem'], true);
830  foreach ($paletteFields as $paletteField) {
831  if ($paletteField !== '--linebreak--') {
832  $paletteFieldList[] = $paletteField;
833  }
834  }
835  }
836  }
837  $fieldList = array_merge($fieldList, $paletteFieldList);
838 
839  $altFieldList = [];
840  // Traverse fields in types config and parse the configuration into a nice array:
841  foreach ($fieldList as $k => $v) {
842  list($pFieldName, $pAltTitle, $pPalette) = GeneralUtility::trimExplode(';', $v);
843  $fieldList[$k] = [
844  'field' => $pFieldName,
845  'title' => $pAltTitle,
846  'palette' => $pPalette,
847  'spec' => [],
848  'origString' => $v
849  ];
850  if ($useFieldNameAsKey) {
851  $altFieldList[$fieldList[$k]['field']] = $fieldList[$k];
852  }
853  }
854  if ($useFieldNameAsKey) {
855  $fieldList = $altFieldList;
856  }
857 
858  // Add to first-level-cache
859  self::$tcaTableTypeConfigurationCache[$cacheIdentifier] = $fieldList;
860 
861  // Return array:
862  return $fieldList;
863  }
864  return null;
865  }
866 
884  public static function getTCAtypeValue($table, $row)
885  {
886  $typeNum = 0;
887  if ($GLOBALS['TCA'][$table]) {
888  $field = $GLOBALS['TCA'][$table]['ctrl']['type'];
889  if (strpos($field, ':') !== false) {
890  list($pointerField, $foreignTableTypeField) = explode(':', $field);
891  // Get field value from database if field is not in the $row array
892  if (!isset($row[$pointerField])) {
893  $localRow = self::getRecord($table, $row['uid'], $pointerField);
894  $foreignUid = $localRow[$pointerField];
895  } else {
896  $foreignUid = $row[$pointerField];
897  }
898  if ($foreignUid) {
899  $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$pointerField]['config'];
900  $relationType = $fieldConfig['type'];
901  if ($relationType === 'select') {
902  $foreignTable = $fieldConfig['foreign_table'];
903  } elseif ($relationType === 'group') {
904  $allowedTables = explode(',', $fieldConfig['allowed']);
905  $foreignTable = $allowedTables[0];
906  } else {
907  throw new \RuntimeException(
908  'TCA foreign field pointer fields are only allowed to be used with group or select field types.',
909  1325862240
910  );
911  }
912  $foreignRow = self::getRecord($foreignTable, $foreignUid, $foreignTableTypeField);
913  if ($foreignRow[$foreignTableTypeField]) {
914  $typeNum = $foreignRow[$foreignTableTypeField];
915  }
916  }
917  } else {
918  $typeNum = $row[$field];
919  }
920  // If that value is an empty string, set it to "0" (zero)
921  if (empty($typeNum)) {
922  $typeNum = 0;
923  }
924  }
925  // If current typeNum doesn't exist, set it to 0 (or to 1 for historical reasons, if 0 doesn't exist)
926  if (!$GLOBALS['TCA'][$table]['types'][$typeNum]) {
927  $typeNum = $GLOBALS['TCA'][$table]['types']['0'] ? 0 : 1;
928  }
929  // Force to string. Necessary for eg '-1' to be recognized as a type value.
930  $typeNum = (string)$typeNum;
931  return $typeNum;
932  }
933 
944  public static function getSpecConfParts($defaultExtrasString)
945  {
947  $specConfParts = GeneralUtility::trimExplode(':', $defaultExtrasString, true);
948  $reg = [];
949  if (!empty($specConfParts)) {
950  foreach ($specConfParts as $k2 => $v2) {
951  unset($specConfParts[$k2]);
952  if (preg_match('/(.*)\\[(.*)\\]/', $v2, $reg)) {
953  $specConfParts[trim($reg[1])] = [
954  'parameters' => GeneralUtility::trimExplode('|', $reg[2], true)
955  ];
956  } else {
957  $specConfParts[trim($v2)] = 1;
958  }
959  }
960  } else {
961  $specConfParts = [];
962  }
963  return $specConfParts;
964  }
965 
974  public static function getSpecConfParametersFromArray($pArr)
975  {
977  $out = [];
978  if (is_array($pArr)) {
979  foreach ($pArr as $k => $v) {
980  $parts = explode('=', $v, 2);
981  if (count($parts) === 2) {
982  $out[trim($parts[0])] = trim($parts[1]);
983  } else {
984  $out[$k] = $v;
985  }
986  }
987  }
988  return $out;
989  }
990 
1019  public static function getFlexFormDS($conf, $row, $table, $fieldName = '', $WSOL = true, $newRecordPidValue = 0)
1020  {
1022  // Get pointer field etc from TCA-config:
1023  $ds_pointerField = $conf['ds_pointerField'];
1024  $ds_array = $conf['ds'];
1025  $ds_tableField = $conf['ds_tableField'];
1026  $ds_searchParentField = $conf['ds_pointerField_searchParent'];
1027  // If there is a data source array, that takes precedence
1028  if (is_array($ds_array)) {
1029  // If a pointer field is set, take the value from that field in the $row array and use as key.
1030  if ($ds_pointerField) {
1031  // Up to two pointer fields can be specified in a comma separated list.
1032  $pointerFields = GeneralUtility::trimExplode(',', $ds_pointerField);
1033  // If we have two pointer fields, the array keys should contain both field values separated by comma.
1034  // The asterisk "*" catches all values. For backwards compatibility, it's also possible to specify only
1035  // the value of the first defined ds_pointerField.
1036  if (count($pointerFields) === 2) {
1037  if ($ds_array[$row[$pointerFields[0]] . ',' . $row[$pointerFields[1]]]) {
1038  // Check if we have a DS for the combination of both pointer fields values
1039  $srcPointer = $row[$pointerFields[0]] . ',' . $row[$pointerFields[1]];
1040  } elseif ($ds_array[$row[$pointerFields[1]] . ',*']) {
1041  // Check if we have a DS for the value of the first pointer field suffixed with ",*"
1042  $srcPointer = $row[$pointerFields[1]] . ',*';
1043  } elseif ($ds_array['*,' . $row[$pointerFields[1]]]) {
1044  // Check if we have a DS for the value of the second pointer field prefixed with "*,"
1045  $srcPointer = '*,' . $row[$pointerFields[1]];
1046  } elseif ($ds_array[$row[$pointerFields[0]]]) {
1047  // Check if we have a DS for just the value of the first pointer field (mainly for backwards compatibility)
1048  $srcPointer = $row[$pointerFields[0]];
1049  } else {
1050  $srcPointer = null;
1051  }
1052  } else {
1053  $srcPointer = $row[$pointerFields[0]];
1054  }
1055  $srcPointer = $srcPointer !== null && isset($ds_array[$srcPointer]) ? $srcPointer : 'default';
1056  } else {
1057  $srcPointer = 'default';
1058  }
1059  // Get Data Source: Detect if it's a file reference and in that case read the file and parse as XML. Otherwise the value is expected to be XML.
1060  if (substr($ds_array[$srcPointer], 0, 5) === 'FILE:') {
1061  $file = GeneralUtility::getFileAbsFileName(substr($ds_array[$srcPointer], 5));
1062  if ($file && @is_file($file)) {
1063  $dataStructArray = GeneralUtility::xml2array(file_get_contents($file));
1064  } else {
1065  $dataStructArray = 'The file "' . substr($ds_array[$srcPointer], 5) . '" in ds-array key "' . $srcPointer . '" was not found ("' . $file . '")';
1066  }
1067  } else {
1068  $dataStructArray = GeneralUtility::xml2array($ds_array[$srcPointer]);
1069  }
1070  } elseif ($ds_pointerField) {
1071  // If pointer field AND possibly a table/field is set:
1072  // Value of field pointed to:
1073  $srcPointer = $row[$ds_pointerField];
1074  // Searching recursively back if 'ds_pointerField_searchParent' is defined (typ. a page rootline, or maybe a tree-table):
1075  if ($ds_searchParentField && !$srcPointer) {
1076  $rr = self::getRecord($table, $row['uid'], 'uid,' . $ds_searchParentField);
1077  // Get the "pid" field - we cannot know that it is in the input record! ###NOTE_A###
1078  if ($WSOL) {
1079  self::workspaceOL($table, $rr);
1080  self::fixVersioningPid($table, $rr, true);
1081  }
1082 
1083  $queryBuilder = static::getQueryBuilderForTable($table);
1084  $queryBuilder->getRestrictions()
1085  ->removeAll()
1086  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1087 
1088  $uidAcc = [];
1089  // Used to avoid looping, if any should happen.
1090  $subFieldPointer = $conf['ds_pointerField_searchParent_subField'];
1091  while (!$srcPointer) {
1092  // select fields
1093  $queryBuilder
1094  ->select('uid', $ds_pointerField, $ds_searchParentField)
1095  ->from($table)
1096  ->where(
1097  $queryBuilder->expr()->eq(
1098  'uid',
1099  $queryBuilder->createNamedParameter(
1100  ($newRecordPidValue ?: $rr[$ds_searchParentField]),
1101  \PDO::PARAM_INT
1102  )
1103  )
1104  );
1105  if ($subFieldPointer) {
1106  $queryBuilder->addSelect($subFieldPointer);
1107  }
1108 
1109  $rr = $queryBuilder->execute()->fetch();
1110 
1111  $newRecordPidValue = 0;
1112  // Break if no result from SQL db or if looping...
1113  if (!is_array($rr) || isset($uidAcc[$rr['uid']])) {
1114  break;
1115  }
1116  $uidAcc[$rr['uid']] = 1;
1117  if ($WSOL) {
1118  self::workspaceOL($table, $rr);
1119  self::fixVersioningPid($table, $rr, true);
1120  }
1121  $srcPointer = $subFieldPointer && $rr[$subFieldPointer] ? $rr[$subFieldPointer] : $rr[$ds_pointerField];
1122  }
1123  }
1124  // If there is a srcPointer value:
1125  if ($srcPointer) {
1126  if (MathUtility::canBeInterpretedAsInteger($srcPointer)) {
1127  // If integer, then its a record we will look up:
1128  list($tName, $fName) = explode(':', $ds_tableField, 2);
1129  if ($tName && $fName && is_array($GLOBALS['TCA'][$tName])) {
1130  $dataStructRec = self::getRecord($tName, $srcPointer);
1131  if ($WSOL) {
1132  self::workspaceOL($tName, $dataStructRec);
1133  }
1134  if (strpos($dataStructRec[$fName], '<') === false) {
1135  if (is_file(PATH_site . $dataStructRec[$fName])) {
1136  // The value is a pointer to a file
1137  $dataStructArray = GeneralUtility::xml2array(file_get_contents(PATH_site . $dataStructRec[$fName]));
1138  } else {
1139  $dataStructArray = sprintf('File \'%s\' was not found', $dataStructRec[$fName]);
1140  }
1141  } else {
1142  // No file pointer, handle as being XML (default behaviour)
1143  $dataStructArray = GeneralUtility::xml2array($dataStructRec[$fName]);
1144  }
1145  } else {
1146  $dataStructArray = 'No tablename (' . $tName . ') or fieldname (' . $fName . ') was found an valid!';
1147  }
1148  } else {
1149  // Otherwise expect it to be a file:
1150  $file = GeneralUtility::getFileAbsFileName($srcPointer);
1151  if ($file && @is_file($file)) {
1152  $dataStructArray = GeneralUtility::xml2array(file_get_contents($file));
1153  } else {
1154  // Error message.
1155  $dataStructArray = 'The file "' . $srcPointer . '" was not found ("' . $file . '")';
1156  }
1157  }
1158  } else {
1159  // Error message.
1160  $dataStructArray = 'No source value in fieldname "' . $ds_pointerField . '"';
1161  }
1162  } else {
1163  $dataStructArray = 'No proper configuration!';
1164  }
1165  // Hook for post-processing the Flexform DS. Introduces the possibility to configure Flexforms via TSConfig
1166  // This hook isn't called anymore from within the core, the whole method is deprecated.
1167  // There are alternative hooks, see FlexFormTools->getDataStructureIdentifier() and ->parseDataStructureByIdentifier()
1168  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['getFlexFormDSClass'])) {
1169  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['getFlexFormDSClass'] as $classRef) {
1170  $hookObj = GeneralUtility::getUserObj($classRef);
1171  if (method_exists($hookObj, 'getFlexFormDS_postProcessDS')) {
1172  $hookObj->getFlexFormDS_postProcessDS($dataStructArray, $conf, $row, $table, $fieldName);
1173  }
1174  }
1175  }
1176  return $dataStructArray;
1177  }
1178 
1179  /*******************************************
1180  *
1181  * Caching related
1182  *
1183  *******************************************/
1194  public static function storeHash($hash, $data, $ident)
1195  {
1197  $cacheManager = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Cache\CacheManager::class);
1198  $cacheManager->getCache('cache_hash')->set($hash, $data, ['ident_' . $ident], 0);
1199  }
1200 
1210  public static function getHash($hash)
1211  {
1213  $cacheManager = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Cache\CacheManager::class);
1214  $cacheEntry = $cacheManager->getCache('cache_hash')->get($hash);
1215  $hashContent = null;
1216  if ($cacheEntry) {
1217  $hashContent = $cacheEntry;
1218  }
1219  return $hashContent;
1220  }
1221 
1222  /*******************************************
1223  *
1224  * TypoScript related
1225  *
1226  *******************************************/
1236  public static function getPagesTSconfig($id, $rootLine = null, $returnPartArray = false)
1237  {
1238  $id = (int)$id;
1239 
1240  $cache = self::getRuntimeCache();
1241  if ($returnPartArray === false
1242  && $rootLine === null
1243  && $cache->has('pagesTsConfigIdToHash' . $id)
1244  ) {
1245  return $cache->get('pagesTsConfigHashToContent' . $cache->get('pagesTsConfigIdToHash' . $id));
1246  }
1247  $TSconfig = [];
1248  if (!is_array($rootLine)) {
1249  $useCacheForCurrentPageId = true;
1250  $rootLine = self::BEgetRootLine($id, '', true);
1251  } else {
1252  $useCacheForCurrentPageId = false;
1253  }
1254 
1255  // Order correctly
1256  ksort($rootLine);
1257  $TSdataArray = [];
1258  // Setting default configuration
1259  $TSdataArray['defaultPageTSconfig'] = $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'];
1260  foreach ($rootLine as $k => $v) {
1261  if (trim($v['tsconfig_includes'])) {
1262  $includeTsConfigFileList = GeneralUtility::trimExplode(',', $v['tsconfig_includes'], true);
1263  // Traversing list
1264  foreach ($includeTsConfigFileList as $key => $includeTsConfigFile) {
1265  if (strpos($includeTsConfigFile, 'EXT:') === 0) {
1266  list($includeTsConfigFileExtensionKey, $includeTsConfigFilename) = explode(
1267  '/',
1268  substr($includeTsConfigFile, 4),
1269  2
1270  );
1271  if ((string)$includeTsConfigFileExtensionKey !== ''
1272  && ExtensionManagementUtility::isLoaded($includeTsConfigFileExtensionKey)
1273  && (string)$includeTsConfigFilename !== ''
1274  ) {
1275  $extensionPath = ExtensionManagementUtility::extPath($includeTsConfigFileExtensionKey);
1276  $includeTsConfigFileAndPath = PathUtility::getCanonicalPath($extensionPath . $includeTsConfigFilename);
1277  if (strpos($includeTsConfigFileAndPath, $extensionPath) === 0 && file_exists($includeTsConfigFileAndPath)) {
1278  $TSdataArray['uid_' . $v['uid'] . '_static_' . $key] = file_get_contents($includeTsConfigFileAndPath);
1279  }
1280  }
1281  }
1282  }
1283  }
1284  $TSdataArray['uid_' . $v['uid']] = $v['TSconfig'];
1285  }
1286  $TSdataArray = static::emitGetPagesTSconfigPreIncludeSignal($TSdataArray, $id, $rootLine, $returnPartArray);
1287  $TSdataArray = TypoScriptParser::checkIncludeLines_array($TSdataArray);
1288  if ($returnPartArray) {
1289  return $TSdataArray;
1290  }
1291  // Parsing the page TS-Config
1292  $pageTS = implode(LF . '[GLOBAL]' . LF, $TSdataArray);
1293  /* @var $parseObj \TYPO3\CMS\Backend\Configuration\TsConfigParser */
1294  $parseObj = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Configuration\TsConfigParser::class);
1295  $res = $parseObj->parseTSconfig($pageTS, 'PAGES', $id, $rootLine);
1296  if ($res) {
1297  $TSconfig = $res['TSconfig'];
1298  }
1299  $cacheHash = $res['hash'];
1300  // Get User TSconfig overlay
1301  $userTSconfig = static::getBackendUserAuthentication()->userTS['page.'] ?? null;
1302  if (is_array($userTSconfig)) {
1303  ArrayUtility::mergeRecursiveWithOverrule($TSconfig, $userTSconfig);
1304  $cacheHash .= '_user' . $GLOBALS['BE_USER']->user['uid'];
1305  }
1306 
1307  if ($useCacheForCurrentPageId) {
1308  // Many pages end up with the same ts config. To reduce memory usage, the cache
1309  // entries are a linked list: One or more pids point to content hashes which then
1310  // contain the cached content.
1311  $cache->set('pagesTsConfigHashToContent' . $cacheHash, $TSconfig, ['pagesTsConfig']);
1312  $cache->set('pagesTsConfigIdToHash' . $id, $cacheHash, ['pagesTsConfig']);
1313  }
1314 
1315  return $TSconfig;
1316  }
1317 
1318  /*******************************************
1319  *
1320  * Users / Groups related
1321  *
1322  *******************************************/
1331  public static function getUserNames($fields = 'username,usergroup,usergroup_cached_list,uid', $where = '')
1332  {
1333  return self::getRecordsSortedByTitle(
1335  'be_users',
1336  'username',
1337  'AND pid=0 ' . $where
1338  );
1339  }
1340 
1348  public static function getGroupNames($fields = 'title,uid', $where = '')
1349  {
1350  return self::getRecordsSortedByTitle(
1352  'be_groups',
1353  'title',
1354  'AND pid=0 ' . $where
1355  );
1356  }
1357 
1369  protected static function getRecordsSortedByTitle(array $fields, $table, $titleField, $where = '')
1370  {
1371  $fieldsIndex = array_flip($fields);
1372  // Make sure the titleField is amongst the fields when getting sorted
1373  $fieldsIndex[$titleField] = 1;
1374 
1375  $result = [];
1376 
1377  $queryBuilder = static::getQueryBuilderForTable($table);
1378  $queryBuilder->getRestrictions()
1379  ->removeAll()
1380  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1381 
1382  $res = $queryBuilder
1383  ->select('*')
1384  ->from($table)
1386  ->execute();
1387 
1388  while ($record = $res->fetch()) {
1389  // store the uid, because it might be unset if it's not among the requested $fields
1390  $recordId = $record['uid'];
1391  $record[$titleField] = self::getRecordTitle($table, $record);
1392 
1393  // include only the requested fields in the result
1394  $result[$recordId] = array_intersect_key($record, $fieldsIndex);
1395  }
1396 
1397  // sort records by $sortField. This is not done in the query because the title might have been overwritten by
1398  // self::getRecordTitle();
1399  return ArrayUtility::sortArraysByKey($result, $titleField);
1400  }
1401 
1409  public static function getListGroupNames($fields = 'title, uid')
1410  {
1411  $beUser = static::getBackendUserAuthentication();
1412  $exQ = ' AND hide_in_lists=0';
1413  if (!$beUser->isAdmin()) {
1414  $exQ .= ' AND uid IN (' . ($beUser->user['usergroup_cached_list'] ?: 0) . ')';
1415  }
1416  return self::getGroupNames($fields, $exQ);
1417  }
1418 
1429  public static function blindUserNames($usernames, $groupArray, $excludeBlindedFlag = false)
1430  {
1431  if (is_array($usernames) && is_array($groupArray)) {
1432  foreach ($usernames as $uid => $row) {
1433  $userN = $uid;
1434  $set = 0;
1435  if ($row['uid'] != static::getBackendUserAuthentication()->user['uid']) {
1436  foreach ($groupArray as $v) {
1437  if ($v && GeneralUtility::inList($row['usergroup_cached_list'], $v)) {
1438  $userN = $row['username'];
1439  $set = 1;
1440  }
1441  }
1442  } else {
1443  $userN = $row['username'];
1444  $set = 1;
1445  }
1446  $usernames[$uid]['username'] = $userN;
1447  if ($excludeBlindedFlag && !$set) {
1448  unset($usernames[$uid]);
1449  }
1450  }
1451  }
1452  return $usernames;
1453  }
1454 
1463  public static function blindGroupNames($groups, $groupArray, $excludeBlindedFlag = false)
1464  {
1465  if (is_array($groups) && is_array($groupArray)) {
1466  foreach ($groups as $uid => $row) {
1467  $groupN = $uid;
1468  $set = 0;
1469  if (in_array($uid, $groupArray, false)) {
1470  $groupN = $row['title'];
1471  $set = 1;
1472  }
1473  $groups[$uid]['title'] = $groupN;
1474  if ($excludeBlindedFlag && !$set) {
1475  unset($groups[$uid]);
1476  }
1477  }
1478  }
1479  return $groups;
1480  }
1481 
1482  /*******************************************
1483  *
1484  * Output related
1485  *
1486  *******************************************/
1493  public static function daysUntil($tstamp)
1494  {
1495  $delta_t = $tstamp - $GLOBALS['EXEC_TIME'];
1496  return ceil($delta_t / (3600 * 24));
1497  }
1498 
1505  public static function date($tstamp)
1506  {
1507  return date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], (int)$tstamp);
1508  }
1509 
1516  public static function datetime($value)
1517  {
1518  return date(
1519  $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'],
1520  $value
1521  );
1522  }
1523 
1532  public static function time($value, $withSeconds = true)
1533  {
1534  return gmdate('H:i' . ($withSeconds ? ':s' : ''), (int)$value);
1535  }
1536 
1544  public static function calcAge($seconds, $labels = 'min|hrs|days|yrs|min|hour|day|year')
1545  {
1546  $labelArr = GeneralUtility::trimExplode('|', $labels, true);
1547  $absSeconds = abs($seconds);
1548  $sign = $seconds < 0 ? -1 : 1;
1549  if ($absSeconds < 3600) {
1550  $val = round($absSeconds / 60);
1551  $seconds = $sign * $val . ' ' . ($val == 1 ? $labelArr[4] : $labelArr[0]);
1552  } elseif ($absSeconds < 24 * 3600) {
1553  $val = round($absSeconds / 3600);
1554  $seconds = $sign * $val . ' ' . ($val == 1 ? $labelArr[5] : $labelArr[1]);
1555  } elseif ($absSeconds < 365 * 24 * 3600) {
1556  $val = round($absSeconds / (24 * 3600));
1557  $seconds = $sign * $val . ' ' . ($val == 1 ? $labelArr[6] : $labelArr[2]);
1558  } else {
1559  $val = round($absSeconds / (365 * 24 * 3600));
1560  $seconds = $sign * $val . ' ' . ($val == 1 ? $labelArr[7] : $labelArr[3]);
1561  }
1562  return $seconds;
1563  }
1564 
1574  public static function dateTimeAge($tstamp, $prefix = 1, $date = '')
1575  {
1576  if (!$tstamp) {
1577  return '';
1578  }
1579  $label = static::getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears');
1580  $age = ' (' . self::calcAge($prefix * ($GLOBALS['EXEC_TIME'] - $tstamp), $label) . ')';
1581  return ($date === 'date' ? self::date($tstamp) : self::datetime($tstamp)) . $age;
1582  }
1583 
1591  public static function titleAltAttrib($content)
1592  {
1594  $out = '';
1595  $out .= ' alt="' . htmlspecialchars($content) . '"';
1596  $out .= ' title="' . htmlspecialchars($content) . '"';
1597  return $out;
1598  }
1599 
1609  public static function resolveFileReferences($tableName, $fieldName, $element, $workspaceId = null)
1610  {
1611  if (empty($GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'])) {
1612  return null;
1613  }
1614  $configuration = $GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
1615  if (empty($configuration['type']) || $configuration['type'] !== 'inline'
1616  || empty($configuration['foreign_table']) || $configuration['foreign_table'] !== 'sys_file_reference'
1617  ) {
1618  return null;
1619  }
1620 
1621  $fileReferences = [];
1623  $relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
1624  if ($workspaceId !== null) {
1625  $relationHandler->setWorkspaceId($workspaceId);
1626  }
1627  $relationHandler->start(
1628  $element[$fieldName],
1629  $configuration['foreign_table'],
1630  $configuration['MM'],
1631  $element['uid'],
1632  $tableName,
1633  $configuration
1634  );
1635  $relationHandler->processDeletePlaceholder();
1636  $referenceUids = $relationHandler->tableArray[$configuration['foreign_table']];
1637 
1638  foreach ($referenceUids as $referenceUid) {
1639  try {
1640  $fileReference = ResourceFactory::getInstance()->getFileReferenceObject(
1641  $referenceUid,
1642  [],
1643  ($workspaceId === 0)
1644  );
1645  $fileReferences[$fileReference->getUid()] = $fileReference;
1646  } catch (\TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException $e) {
1651  } catch (\InvalidArgumentException $e) {
1656  $logMessage = $e->getMessage() . ' (table: "' . $tableName . '", fieldName: "' . $fieldName . '", referenceUid: ' . $referenceUid . ')';
1657  GeneralUtility::sysLog($logMessage, 'core', GeneralUtility::SYSLOG_SEVERITY_ERROR);
1658  }
1659  }
1660 
1661  return $fileReferences;
1662  }
1663 
1681  public static function thumbCode(
1682  $row,
1683  $table,
1684  $field,
1685  $backPath = '',
1686  $thumbScript = '',
1687  $uploaddir = null,
1688  $abs = 0,
1689  $tparams = '',
1690  $size = '',
1691  $linkInfoPopup = true
1692  ) {
1693  // Check and parse the size parameter
1694  $size = trim($size);
1695  $sizeParts = [64, 64];
1696  if ($size) {
1697  $sizeParts = explode('x', $size . 'x' . $size);
1698  }
1699  $thumbData = '';
1700  $fileReferences = static::resolveFileReferences($table, $field, $row);
1701  // FAL references
1702  $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
1703  if ($fileReferences !== null) {
1704  foreach ($fileReferences as $fileReferenceObject) {
1705  // Do not show previews of hidden references
1706  if ($fileReferenceObject->getProperty('hidden')) {
1707  continue;
1708  }
1709  $fileObject = $fileReferenceObject->getOriginalFile();
1710 
1711  if ($fileObject->isMissing()) {
1712  $thumbData .= '<span class="label label-danger">'
1713  . htmlspecialchars(
1714  static::getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:warning.file_missing')
1715  )
1716  . '</span>&nbsp;' . htmlspecialchars($fileObject->getName()) . '<br />';
1717  continue;
1718  }
1719 
1720  // Preview web image or media elements
1721  if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['thumbnails']
1723  $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'],
1724  $fileReferenceObject->getExtension()
1725  )
1726  ) {
1727  $cropVariantCollection = CropVariantCollection::create((string)$fileReferenceObject->getProperty('crop'));
1728  $cropArea = $cropVariantCollection->getCropArea();
1729  $parameters = json_encode([
1730  'fileId' => $fileObject->getUid(),
1731  'configuration' => [
1732  'width' => $sizeParts[0],
1733  'height' => $sizeParts[1] . 'c',
1734  'crop' => $cropArea->isEmpty() ? null : $cropArea->makeAbsoluteBasedOnFile($fileReferenceObject),
1735  ]
1736  ]);
1737  $uriParameters = [
1738  'parameters' => $parameters,
1739  'hmac' => GeneralUtility::hmac(
1740  $parameters,
1741  \TYPO3\CMS\Backend\Controller\File\ThumbnailController::class
1742  ),
1743  ];
1744  $imageUrl = (string)GeneralUtility::makeInstance(UriBuilder::class)
1745  ->buildUriFromRoute('thumbnails', $uriParameters);
1746  $attributes = [
1747  'src' => $imageUrl,
1748  'width' => (int)$sizeParts[0],
1749  'height' => (int)$sizeParts[1],
1750  'alt' => $fileReferenceObject->getName(),
1751  ];
1752  $imgTag = '<img ' . GeneralUtility::implodeAttributes($attributes, true) . '/>';
1753  } else {
1754  // Icon
1755  $imgTag = '<span title="' . htmlspecialchars($fileObject->getName()) . '">'
1756  . $iconFactory->getIconForResource($fileObject, Icon::SIZE_SMALL)->render()
1757  . '</span>';
1758  }
1759  if ($linkInfoPopup) {
1760  $onClick = 'top.launchView(\'_FILE\',\'' . (int)$fileObject->getUid() . '\'); return false;';
1761  $thumbData .= '<a href="#" onclick="' . htmlspecialchars($onClick) . '">' . $imgTag . '</a> ';
1762  } else {
1763  $thumbData .= $imgTag;
1764  }
1765  }
1766  } else {
1767  // Find uploaddir automatically
1768  if (is_null($uploaddir)) {
1769  $uploaddir = $GLOBALS['TCA'][$table]['columns'][$field]['config']['uploadfolder'];
1770  }
1771  $uploaddir = rtrim($uploaddir, '/');
1772  // Traverse files:
1773  $thumbs = GeneralUtility::trimExplode(',', $row[$field], true);
1774  $thumbData = '';
1775  foreach ($thumbs as $theFile) {
1776  if ($theFile) {
1777  $fileName = trim($uploaddir . '/' . $theFile, '/');
1778  try {
1780  $fileObject = ResourceFactory::getInstance()->retrieveFileOrFolderObject($fileName);
1781  // Skip the resource if it's not of type AbstractFile. One case where this can happen if the
1782  // storage has been externally modified and the field value now points to a folder
1783  // instead of a file.
1784  if (!$fileObject instanceof AbstractFile) {
1785  continue;
1786  }
1787  if ($fileObject->isMissing()) {
1788  $thumbData .= '<span class="label label-danger">'
1789  . htmlspecialchars(static::getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:warning.file_missing'))
1790  . '</span>&nbsp;' . htmlspecialchars($fileObject->getName()) . '<br />';
1791  continue;
1792  }
1793  } catch (ResourceDoesNotExistException $exception) {
1794  $thumbData .= '<span class="label label-danger">'
1795  . htmlspecialchars(static::getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:warning.file_missing'))
1796  . '</span>&nbsp;' . htmlspecialchars($fileName) . '<br />';
1797  continue;
1798  }
1799 
1800  $fileExtension = $fileObject->getExtension();
1801  if ($fileExtension === 'ttf'
1802  || GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], $fileExtension)
1803  ) {
1804  $imageUrl = $fileObject->process(
1806  [
1807  'width' => $sizeParts[0],
1808  'height' => $sizeParts[1]
1809  ]
1810  )->getPublicUrl(true);
1811 
1812  $image = '<img src="' . htmlspecialchars($imageUrl) . '" hspace="2" border="0" title="' . htmlspecialchars($fileObject->getName()) . '"' . $tparams . ' alt="" />';
1813  if ($linkInfoPopup) {
1814  $onClick = 'top.launchView(\'_FILE\', ' . GeneralUtility::quoteJSvalue($fileName) . ',\'\');return false;';
1815  $thumbData .= '<a href="#" onclick="' . htmlspecialchars($onClick) . '">' . $image . '</a> ';
1816  } else {
1817  $thumbData .= $image;
1818  }
1819  } else {
1820  // Gets the icon
1821  $fileIcon = '<span title="' . htmlspecialchars($fileObject->getName()) . '">'
1822  . $iconFactory->getIconForResource($fileObject, Icon::SIZE_SMALL)->render()
1823  . '</span>';
1824  if ($linkInfoPopup) {
1825  $onClick = 'top.launchView(\'_FILE\', ' . GeneralUtility::quoteJSvalue($fileName) . ',\'\'); return false;';
1826  $thumbData .= '<a href="#" onclick="' . htmlspecialchars($onClick) . '">' . $fileIcon . '</a> ';
1827  } else {
1828  $thumbData .= $fileIcon;
1829  }
1830  }
1831  }
1832  }
1833  }
1834  return $thumbData;
1835  }
1836 
1845  public static function titleAttribForPages($row, $perms_clause = '', $includeAttrib = true)
1846  {
1847  $lang = static::getLanguageService();
1848  $parts = [];
1849  $parts[] = 'id=' . $row['uid'];
1850  if ($row['alias']) {
1851  $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['alias']['label']) . ' ' . $row['alias'];
1852  }
1853  if ($row['pid'] < 0) {
1854  $parts[] = 'v#1.' . $row['t3ver_id'];
1855  }
1856  switch (VersionState::cast($row['t3ver_state'])) {
1858  $parts[] = 'PLH WSID#' . $row['t3ver_wsid'];
1859  break;
1861  $parts[] = 'Deleted element!';
1862  break;
1864  $parts[] = 'NEW LOCATION (PLH) WSID#' . $row['t3ver_wsid'];
1865  break;
1867  $parts[] = 'OLD LOCATION (PNT) WSID#' . $row['t3ver_wsid'];
1868  break;
1870  $parts[] = 'New element!';
1871  break;
1872  }
1873  if ($row['doktype'] == PageRepository::DOKTYPE_LINK) {
1874  $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['url']['label']) . ' ' . $row['url'];
1875  } elseif ($row['doktype'] == PageRepository::DOKTYPE_SHORTCUT) {
1876  if ($perms_clause) {
1877  $label = self::getRecordPath((int)$row['shortcut'], $perms_clause, 20);
1878  } else {
1879  $row['shortcut'] = (int)$row['shortcut'];
1880  $lRec = self::getRecordWSOL('pages', $row['shortcut'], 'title');
1881  $label = $lRec['title'] . ' (id=' . $row['shortcut'] . ')';
1882  }
1883  if ($row['shortcut_mode'] != PageRepository::SHORTCUT_MODE_NONE) {
1884  $label .= ', ' . $lang->sL($GLOBALS['TCA']['pages']['columns']['shortcut_mode']['label']) . ' '
1885  . $lang->sL(self::getLabelFromItemlist('pages', 'shortcut_mode', $row['shortcut_mode']));
1886  }
1887  $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['shortcut']['label']) . ' ' . $label;
1888  } elseif ($row['doktype'] == PageRepository::DOKTYPE_MOUNTPOINT) {
1889  if ($perms_clause) {
1890  $label = self::getRecordPath((int)$row['mount_pid'], $perms_clause, 20);
1891  } else {
1892  $lRec = self::getRecordWSOL('pages', (int)$row['mount_pid'], 'title');
1893  $label = $lRec['title'] . ' (id=' . $row['mount_pid'] . ')';
1894  }
1895  $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['mount_pid']['label']) . ' ' . $label;
1896  if ($row['mount_pid_ol']) {
1897  $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['mount_pid_ol']['label']);
1898  }
1899  }
1900  if ($row['nav_hide']) {
1901  $parts[] = rtrim($lang->sL($GLOBALS['TCA']['pages']['columns']['nav_hide']['label']), ':');
1902  }
1903  if ($row['hidden']) {
1904  $parts[] = $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.hidden');
1905  }
1906  if ($row['starttime']) {
1907  $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['starttime']['label'])
1908  . ' ' . self::dateTimeAge($row['starttime'], -1, 'date');
1909  }
1910  if ($row['endtime']) {
1911  $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['endtime']['label']) . ' '
1912  . self::dateTimeAge($row['endtime'], -1, 'date');
1913  }
1914  if ($row['fe_group']) {
1915  $fe_groups = [];
1916  foreach (GeneralUtility::intExplode(',', $row['fe_group']) as $fe_group) {
1917  if ($fe_group < 0) {
1918  $fe_groups[] = $lang->sL(self::getLabelFromItemlist('pages', 'fe_group', $fe_group));
1919  } else {
1920  $lRec = self::getRecordWSOL('fe_groups', $fe_group, 'title');
1921  $fe_groups[] = $lRec['title'];
1922  }
1923  }
1924  $label = implode(', ', $fe_groups);
1925  $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['fe_group']['label']) . ' ' . $label;
1926  }
1927  $out = htmlspecialchars(implode(' - ', $parts));
1928  return $includeAttrib ? 'title="' . $out . '"' : $out;
1929  }
1930 
1938  public static function getRecordToolTip(array $row, $table = 'pages')
1939  {
1940  $toolTipText = self::getRecordIconAltText($row, $table);
1941  $toolTipCode = 'data-toggle="tooltip" data-title=" '
1942  . str_replace(' - ', '<br>', $toolTipText)
1943  . '" data-html="true" data-placement="right"';
1944  return $toolTipCode;
1945  }
1946 
1956  public static function getRecordIconAltText($row, $table = 'pages')
1957  {
1958  if ($table === 'pages') {
1959  $out = self::titleAttribForPages($row, '', 0);
1960  } else {
1961  $out = !empty(trim($GLOBALS['TCA'][$table]['ctrl']['descriptionColumn'])) ? $row[$GLOBALS['TCA'][$table]['ctrl']['descriptionColumn']] . ' ' : '';
1962  $ctrl = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns'];
1963  // Uid is added
1964  $out .= 'id=' . $row['uid'];
1965  if ($table === 'pages' && $row['alias']) {
1966  $out .= ' / ' . $row['alias'];
1967  }
1968  if (static::isTableWorkspaceEnabled($table) && $row['pid'] < 0) {
1969  $out .= ' - v#1.' . $row['t3ver_id'];
1970  }
1971  if (static::isTableWorkspaceEnabled($table)) {
1972  switch (VersionState::cast($row['t3ver_state'])) {
1974  $out .= ' - PLH WSID#' . $row['t3ver_wsid'];
1975  break;
1977  $out .= ' - Deleted element!';
1978  break;
1980  $out .= ' - NEW LOCATION (PLH) WSID#' . $row['t3ver_wsid'];
1981  break;
1983  $out .= ' - OLD LOCATION (PNT) WSID#' . $row['t3ver_wsid'];
1984  break;
1986  $out .= ' - New element!';
1987  break;
1988  }
1989  }
1990  // Hidden
1991  $lang = static::getLanguageService();
1992  if ($ctrl['disabled']) {
1993  $out .= $row[$ctrl['disabled']] ? ' - ' . $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.hidden') : '';
1994  }
1995  if ($ctrl['starttime']) {
1996  if ($row[$ctrl['starttime']] > $GLOBALS['EXEC_TIME']) {
1997  $out .= ' - ' . $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.starttime') . ':' . self::date($row[$ctrl['starttime']]) . ' (' . self::daysUntil($row[$ctrl['starttime']]) . ' ' . $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.days') . ')';
1998  }
1999  }
2000  if ($row[$ctrl['endtime']]) {
2001  $out .= ' - ' . $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.endtime') . ': ' . self::date($row[$ctrl['endtime']]) . ' (' . self::daysUntil($row[$ctrl['endtime']]) . ' ' . $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.days') . ')';
2002  }
2003  }
2004  return htmlspecialchars($out);
2005  }
2006 
2015  public static function getLabelFromItemlist($table, $col, $key)
2016  {
2017  // Check, if there is an "items" array:
2018  if (is_array($GLOBALS['TCA'][$table]['columns'][$col]['config']['items'] ?? false)) {
2019  // Traverse the items-array...
2020  foreach ($GLOBALS['TCA'][$table]['columns'][$col]['config']['items'] as $v) {
2021  // ... and return the first found label where the value was equal to $key
2022  if ((string)$v[1] === (string)$key) {
2023  return $v[0];
2024  }
2025  }
2026  }
2027  return '';
2028  }
2029 
2039  public static function getLabelFromItemListMerged($pageId, $table, $column, $key)
2040  {
2041  $pageTsConfig = static::getPagesTSconfig($pageId);
2042  $label = '';
2043  if (is_array($pageTsConfig['TCEFORM.'])
2044  && is_array($pageTsConfig['TCEFORM.'][$table . '.'])
2045  && is_array($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.'])
2046  ) {
2047  if (is_array($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['addItems.'])
2048  && isset($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['addItems.'][$key])
2049  ) {
2050  $label = $pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['addItems.'][$key];
2051  } elseif (is_array($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['altLabels.'])
2052  && isset($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['altLabels.'][$key])
2053  ) {
2054  $label = $pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['altLabels.'][$key];
2055  }
2056  }
2057  if (empty($label)) {
2058  $tcaValue = self::getLabelFromItemlist($table, $column, $key);
2059  if (!empty($tcaValue)) {
2060  $label = $tcaValue;
2061  }
2062  }
2063  return $label;
2064  }
2065 
2076  public static function getLabelsFromItemsList($table, $column, $keyList, array $columnTsConfig = [])
2077  {
2078  // Check if there is an "items" array
2079  if (
2080  !isset($GLOBALS['TCA'][$table]['columns'][$column]['config']['items'])
2081  || !is_array($GLOBALS['TCA'][$table]['columns'][$column]['config']['items'])
2082  || $keyList === ''
2083  ) {
2084  return '';
2085  }
2086 
2087  $keys = GeneralUtility::trimExplode(',', $keyList, true);
2088  $labels = [];
2089  // Loop on all selected values
2090  foreach ($keys as $key) {
2091  $label = null;
2092  if ($columnTsConfig) {
2093  // Check if label has been defined or redefined via pageTsConfig
2094  if (isset($columnTsConfig['addItems.'][$key])) {
2095  $label = $columnTsConfig['addItems.'][$key];
2096  } elseif (isset($columnTsConfig['altLabels.'][$key])) {
2097  $label = $columnTsConfig['altLabels.'][$key];
2098  }
2099  }
2100  if ($label === null) {
2101  // Otherwise lookup the label in TCA items list
2102  foreach ($GLOBALS['TCA'][$table]['columns'][$column]['config']['items'] as $itemConfiguration) {
2103  list($currentLabel, $currentKey) = $itemConfiguration;
2104  if ((string)$key === (string)$currentKey) {
2105  $label = $currentLabel;
2106  break;
2107  }
2108  }
2109  }
2110  if ($label !== null) {
2111  $labels[] = static::getLanguageService()->sL($label);
2112  }
2113  }
2114  return implode(', ', $labels);
2115  }
2116 
2125  public static function getItemLabel($table, $col)
2126  {
2127  // Check if column exists
2128  if (is_array($GLOBALS['TCA'][$table]) && is_array($GLOBALS['TCA'][$table]['columns'][$col])) {
2129  return $GLOBALS['TCA'][$table]['columns'][$col]['label'];
2130  }
2131 
2132  return null;
2133  }
2134 
2144  protected static function replaceL10nModeFields($table, array $row)
2145  {
2147  $originalUidField = isset($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])
2148  ? $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
2149  : '';
2150  if (empty($row[$originalUidField])) {
2151  return $row;
2152  }
2153 
2154  $originalTable = self::getOriginalTranslationTable($table);
2155  $originalRow = self::getRecord($originalTable, $row[$originalUidField]);
2156  foreach ($row as $field => $_) {
2157  $l10n_mode = isset($GLOBALS['TCA'][$originalTable]['columns'][$field]['l10n_mode'])
2158  ? $GLOBALS['TCA'][$originalTable]['columns'][$field]['l10n_mode']
2159  : '';
2160  if ($l10n_mode === 'exclude') {
2161  $row[$field] = $originalRow[$field];
2162  }
2163  }
2164  return $row;
2165  }
2166 
2177  public static function getRecordTitle($table, $row, $prep = false, $forceResult = true)
2178  {
2179  $recordTitle = '';
2180  if (is_array($GLOBALS['TCA'][$table])) {
2181  // If configured, call userFunc
2182  if (!empty($GLOBALS['TCA'][$table]['ctrl']['label_userFunc'])) {
2183  $params['table'] = $table;
2184  $params['row'] = $row;
2185  $params['title'] = '';
2186  $params['options'] = $GLOBALS['TCA'][$table]['ctrl']['label_userFunc_options'] ?? [];
2187 
2188  // Create NULL-reference
2189  $null = null;
2190  GeneralUtility::callUserFunction($GLOBALS['TCA'][$table]['ctrl']['label_userFunc'], $params, $null);
2191  $recordTitle = $params['title'];
2192  } else {
2193  // No userFunc: Build label
2194  $recordTitle = self::getProcessedValue(
2195  $table,
2196  $GLOBALS['TCA'][$table]['ctrl']['label'],
2197  $row[$GLOBALS['TCA'][$table]['ctrl']['label']],
2198  0,
2199  0,
2200  false,
2201  $row['uid'],
2202  $forceResult
2203  );
2204  if (!empty($GLOBALS['TCA'][$table]['ctrl']['label_alt'])
2205  && (!empty($GLOBALS['TCA'][$table]['ctrl']['label_alt_force']) || (string)$recordTitle === '')
2206  ) {
2207  $altFields = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$table]['ctrl']['label_alt'], true);
2208  $tA = [];
2209  if (!empty($recordTitle)) {
2210  $tA[] = $recordTitle;
2211  }
2212  foreach ($altFields as $fN) {
2213  $recordTitle = trim(strip_tags($row[$fN]));
2214  if ((string)$recordTitle !== '') {
2215  $recordTitle = self::getProcessedValue($table, $fN, $recordTitle, 0, 0, false, $row['uid']);
2216  if (!$GLOBALS['TCA'][$table]['ctrl']['label_alt_force']) {
2217  break;
2218  }
2219  $tA[] = $recordTitle;
2220  }
2221  }
2222  if ($GLOBALS['TCA'][$table]['ctrl']['label_alt_force']) {
2223  $recordTitle = implode(', ', $tA);
2224  }
2225  }
2226  }
2227  // If the current result is empty, set it to '[No title]' (localized) and prepare for output if requested
2228  if ($prep || $forceResult) {
2229  if ($prep) {
2230  $recordTitle = self::getRecordTitlePrep($recordTitle);
2231  }
2232  if (trim($recordTitle) === '') {
2233  $recordTitle = self::getNoRecordTitle($prep);
2234  }
2235  }
2236  }
2237 
2238  return $recordTitle;
2239  }
2240 
2249  public static function getRecordTitlePrep($title, $titleLength = 0)
2250  {
2251  // If $titleLength is not a valid positive integer, use BE_USER->uc['titleLen']:
2252  if (!$titleLength || !MathUtility::canBeInterpretedAsInteger($titleLength) || $titleLength < 0) {
2253  $titleLength = static::getBackendUserAuthentication()->uc['titleLen'];
2254  }
2255  $titleOrig = htmlspecialchars($title);
2256  $title = htmlspecialchars(GeneralUtility::fixed_lgd_cs($title, $titleLength));
2257  // If title was cropped, offer a tooltip:
2258  if ($titleOrig != $title) {
2259  $title = '<span title="' . $titleOrig . '">' . $title . '</span>';
2260  }
2261  return $title;
2262  }
2263 
2270  public static function getNoRecordTitle($prep = false)
2271  {
2272  $noTitle = '[' .
2273  htmlspecialchars(static::getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.no_title'))
2274  . ']';
2275  if ($prep) {
2276  $noTitle = '<em>' . $noTitle . '</em>';
2277  }
2278  return $noTitle;
2279  }
2280 
2299  public static function getProcessedValue(
2300  $table,
2301  $col,
2302  $value,
2303  $fixed_lgd_chars = 0,
2304  $defaultPassthrough = false,
2305  $noRecordLookup = false,
2306  $uid = 0,
2307  $forceResult = true,
2308  $pid = 0
2309  ) {
2310  if ($col === 'uid') {
2311  // uid is not in TCA-array
2312  return $value;
2313  }
2314  // Check if table and field is configured
2315  if (!is_array($GLOBALS['TCA'][$table]) || !is_array($GLOBALS['TCA'][$table]['columns'][$col])) {
2316  return null;
2317  }
2318  // Depending on the fields configuration, make a meaningful output value.
2319  $theColConf = $GLOBALS['TCA'][$table]['columns'][$col]['config'];
2320  /*****************
2321  *HOOK: pre-processing the human readable output from a record
2322  ****************/
2323  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['preProcessValue'])) {
2324  // Create NULL-reference
2325  $null = null;
2326  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['preProcessValue'] as $_funcRef) {
2327  GeneralUtility::callUserFunction($_funcRef, $theColConf, $null);
2328  }
2329  }
2330  $l = '';
2331  $lang = static::getLanguageService();
2332  switch ((string)$theColConf['type']) {
2333  case 'radio':
2334  $l = self::getLabelFromItemlist($table, $col, $value);
2335  $l = $lang->sL($l);
2336  break;
2337  case 'inline':
2338  case 'select':
2339  if (!empty($theColConf['MM'])) {
2340  if ($uid) {
2341  // Display the title of MM related records in lists
2342  if ($noRecordLookup) {
2343  $MMfields = [];
2344  $MMfields[] = $theColConf['foreign_table'] . '.uid';
2345  } else {
2346  $MMfields = [$theColConf['foreign_table'] . '.' . $GLOBALS['TCA'][$theColConf['foreign_table']]['ctrl']['label']];
2347  foreach (GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$theColConf['foreign_table']]['ctrl']['label_alt'], true) as $f) {
2348  $MMfields[] = $theColConf['foreign_table'] . '.' . $f;
2349  }
2350  }
2352  $dbGroup = GeneralUtility::makeInstance(RelationHandler::class);
2353  $dbGroup->start(
2354  $value,
2355  $theColConf['foreign_table'],
2356  $theColConf['MM'],
2357  $uid,
2358  $table,
2359  $theColConf
2360  );
2361  $selectUids = $dbGroup->tableArray[$theColConf['foreign_table']];
2362  if (is_array($selectUids) && !empty($selectUids)) {
2363  $queryBuilder = static::getQueryBuilderForTable($theColConf['foreign_table']);
2364  $queryBuilder->getRestrictions()
2365  ->removeAll()
2366  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
2367 
2368  $result = $queryBuilder
2369  ->select('uid', ...$MMfields)
2370  ->from($theColConf['foreign_table'])
2371  ->where(
2372  $queryBuilder->expr()->in(
2373  'uid',
2374  $queryBuilder->createNamedParameter($selectUids, Connection::PARAM_INT_ARRAY)
2375  )
2376  )
2377  ->execute();
2378 
2379  $mmlA = [];
2380  while ($MMrow = $result->fetch()) {
2381  // Keep sorting of $selectUids
2382  $selectedUid = array_search($MMrow['uid'], $selectUids);
2383  $mmlA[$selectedUid] = $MMrow['uid'];
2384  if (!$noRecordLookup) {
2385  $mmlA[$selectedUid] = static::getRecordTitle(
2386  $theColConf['foreign_table'],
2387  $MMrow,
2388  false,
2389  $forceResult
2390  );
2391  }
2392  }
2393 
2394  if (!empty($mmlA)) {
2395  ksort($mmlA);
2396  $l = implode('; ', $mmlA);
2397  } else {
2398  $l = 'N/A';
2399  }
2400  } else {
2401  $l = 'N/A';
2402  }
2403  } else {
2404  $l = 'N/A';
2405  }
2406  } else {
2407  $columnTsConfig = [];
2408  if ($pid) {
2409  $pageTsConfig = self::getPagesTSconfig($pid);
2410  if (isset($pageTsConfig['TCEFORM.'][$table . '.'][$col . '.']) && is_array($pageTsConfig['TCEFORM.'][$table . '.'][$col . '.'])) {
2411  $columnTsConfig = $pageTsConfig['TCEFORM.'][$table . '.'][$col . '.'];
2412  }
2413  }
2414  $l = self::getLabelsFromItemsList($table, $col, $value, $columnTsConfig);
2415  if (!empty($theColConf['foreign_table']) && !$l && !empty($GLOBALS['TCA'][$theColConf['foreign_table']])) {
2416  if ($noRecordLookup) {
2417  $l = $value;
2418  } else {
2419  $rParts = [];
2420  if ($uid && isset($theColConf['foreign_field']) && $theColConf['foreign_field'] !== '') {
2421  $queryBuilder = static::getQueryBuilderForTable($theColConf['foreign_table']);
2422  $queryBuilder->getRestrictions()
2423  ->removeAll()
2424  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
2425  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
2426  $constraints = [
2427  $queryBuilder->expr()->eq(
2428  $theColConf['foreign_field'],
2429  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
2430  )
2431  ];
2432 
2433  if (!empty($theColConf['foreign_table_field'])) {
2434  $constraints[] = $queryBuilder->expr()->eq(
2435  $theColConf['foreign_table_field'],
2436  $queryBuilder->createNamedParameter($table, \PDO::PARAM_STR)
2437  );
2438  }
2439 
2440  // Add additional where clause if foreign_match_fields are defined
2441  $foreignMatchFields = [];
2442  if (is_array($theColConf['foreign_match_fields'])) {
2443  $foreignMatchFields = $theColConf['foreign_match_fields'];
2444  }
2445 
2446  foreach ($foreignMatchFields as $matchField => $matchValue) {
2447  $constraints[] = $queryBuilder->expr()->eq(
2448  $matchField,
2449  $queryBuilder->createNamedParameter($matchValue)
2450  );
2451  }
2452 
2453  $result = $queryBuilder
2454  ->select('*')
2455  ->from($theColConf['foreign_table'])
2456  ->where(...$constraints)
2457  ->execute();
2458 
2459  while ($record = $result->fetch()) {
2460  $rParts[] = $record['uid'];
2461  }
2462  }
2463  if (empty($rParts)) {
2464  $rParts = GeneralUtility::trimExplode(',', $value, true);
2465  }
2466  $lA = [];
2467  foreach ($rParts as $rVal) {
2468  $rVal = (int)$rVal;
2469  $r = self::getRecordWSOL($theColConf['foreign_table'], $rVal);
2470  if (is_array($r)) {
2471  $lA[] = $lang->sL($theColConf['foreign_table_prefix'])
2472  . self::getRecordTitle($theColConf['foreign_table'], $r, false, $forceResult);
2473  } else {
2474  $lA[] = $rVal ? '[' . $rVal . '!]' : '';
2475  }
2476  }
2477  $l = implode(', ', $lA);
2478  }
2479  }
2480  if (empty($l) && !empty($value)) {
2481  // Use plain database value when label is empty
2482  $l = $value;
2483  }
2484  }
2485  break;
2486  case 'group':
2487  // resolve the titles for DB records
2488  if ($theColConf['internal_type'] === 'db') {
2489  if ($theColConf['MM']) {
2490  if ($uid) {
2491  // Display the title of MM related records in lists
2492  if ($noRecordLookup) {
2493  $MMfields = [];
2494  $MMfields[] = $theColConf['foreign_table'] . '.uid';
2495  } else {
2496  $MMfields = [$theColConf['foreign_table'] . '.' . $GLOBALS['TCA'][$theColConf['foreign_table']]['ctrl']['label']];
2497  $altLabelFields = explode(
2498  ',',
2499  $GLOBALS['TCA'][$theColConf['foreign_table']]['ctrl']['label_alt']
2500  );
2501  foreach ($altLabelFields as $f) {
2502  $f = trim($f);
2503  if ($f !== '') {
2504  $MMfields[] = $theColConf['foreign_table'] . '.' . $f;
2505  }
2506  }
2507  }
2509  $dbGroup = GeneralUtility::makeInstance(RelationHandler::class);
2510  $dbGroup->start(
2511  $value,
2512  $theColConf['foreign_table'],
2513  $theColConf['MM'],
2514  $uid,
2515  $table,
2516  $theColConf
2517  );
2518  $selectUids = $dbGroup->tableArray[$theColConf['foreign_table']];
2519  if (!empty($selectUids) && is_array($selectUids)) {
2520  $queryBuilder = static::getQueryBuilderForTable($theColConf['foreign_table']);
2521  $queryBuilder->getRestrictions()
2522  ->removeAll()
2523  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
2524 
2525  $result = $queryBuilder
2526  ->select('uid', ...$MMfields)
2527  ->from($theColConf['foreign_table'])
2528  ->where(
2529  $queryBuilder->expr()->in(
2530  'uid',
2531  $queryBuilder->createNamedParameter(
2532  $selectUids,
2533  Connection::PARAM_INT_ARRAY
2534  )
2535  )
2536  )
2537  ->execute();
2538 
2539  $mmlA = [];
2540  while ($MMrow = $result->fetch()) {
2541  // Keep sorting of $selectUids
2542  $selectedUid = array_search($MMrow['uid'], $selectUids);
2543  $mmlA[$selectedUid] = $MMrow['uid'];
2544  if (!$noRecordLookup) {
2545  $mmlA[$selectedUid] = static::getRecordTitle(
2546  $theColConf['foreign_table'],
2547  $MMrow,
2548  false,
2549  $forceResult
2550  );
2551  }
2552  }
2553 
2554  if (!empty($mmlA)) {
2555  ksort($mmlA);
2556  $l = implode('; ', $mmlA);
2557  } else {
2558  $l = 'N/A';
2559  }
2560  } else {
2561  $l = 'N/A';
2562  }
2563  } else {
2564  $l = 'N/A';
2565  }
2566  } else {
2567  $finalValues = [];
2568  $relationTableName = $theColConf['allowed'];
2569  $explodedValues = GeneralUtility::trimExplode(',', $value, true);
2570 
2571  foreach ($explodedValues as $explodedValue) {
2572  if (MathUtility::canBeInterpretedAsInteger($explodedValue)) {
2573  $relationTableNameForField = $relationTableName;
2574  } else {
2575  list($relationTableNameForField, $explodedValue) = self::splitTable_Uid($explodedValue);
2576  }
2577 
2578  $relationRecord = static::getRecordWSOL($relationTableNameForField, $explodedValue);
2579  $finalValues[] = static::getRecordTitle($relationTableNameForField, $relationRecord);
2580  }
2581  $l = implode(', ', $finalValues);
2582  }
2583  } else {
2584  $l = implode(', ', GeneralUtility::trimExplode(',', $value, true));
2585  }
2586  break;
2587  case 'check':
2588  if (!is_array($theColConf['items']) || count($theColConf['items']) === 1) {
2589  $l = $value ? $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:yes') : $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:no');
2590  } else {
2591  $lA = [];
2592  foreach ($theColConf['items'] as $key => $val) {
2593  if ($value & pow(2, $key)) {
2594  $lA[] = $lang->sL($val[0]);
2595  }
2596  }
2597  $l = implode(', ', $lA);
2598  }
2599  break;
2600  case 'input':
2601  // Hide value 0 for dates, but show it for everything else
2602  if (isset($value)) {
2603  if (GeneralUtility::inList($theColConf['eval'], 'date')) {
2604  // Handle native date field
2605  if (isset($theColConf['dbType']) && $theColConf['dbType'] === 'date') {
2606  $dateTimeFormats = QueryHelper::getDateTimeFormats();
2607  $emptyValue = $dateTimeFormats['date']['empty'];
2608  $value = $value !== $emptyValue ? strtotime($value) : 0;
2609  }
2610  if (!empty($value)) {
2611  $ageSuffix = '';
2612  $dateColumnConfiguration = $GLOBALS['TCA'][$table]['columns'][$col]['config'];
2613  $ageDisplayKey = 'disableAgeDisplay';
2614 
2615  // generate age suffix as long as not explicitly suppressed
2616  if (!isset($dateColumnConfiguration[$ageDisplayKey])
2617  // non typesafe comparison on intention
2618  || $dateColumnConfiguration[$ageDisplayKey] == false
2619  ) {
2620  $ageSuffix = ' (' . ($GLOBALS['EXEC_TIME'] - $value > 0 ? '-' : '')
2621  . self::calcAge(
2622  abs(($GLOBALS['EXEC_TIME'] - $value)),
2623  $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears')
2624  )
2625  . ')';
2626  }
2627 
2628  $l = self::date($value) . $ageSuffix;
2629  }
2630  } elseif (GeneralUtility::inList($theColConf['eval'], 'time')) {
2631  if (!empty($value)) {
2632  $l = gmdate('H:i', (int)$value);
2633  }
2634  } elseif (GeneralUtility::inList($theColConf['eval'], 'timesec')) {
2635  if (!empty($value)) {
2636  $l = gmdate('H:i:s', (int)$value);
2637  }
2638  } elseif (GeneralUtility::inList($theColConf['eval'], 'datetime')) {
2639  // Handle native date/time field
2640  if (isset($theColConf['dbType']) && $theColConf['dbType'] === 'datetime') {
2641  $dateTimeFormats = QueryHelper::getDateTimeFormats();
2642  $emptyValue = $dateTimeFormats['datetime']['empty'];
2643  $value = $value !== $emptyValue ? strtotime($value) : 0;
2644  }
2645  if (!empty($value)) {
2646  $l = self::datetime($value);
2647  }
2648  } else {
2649  $l = $value;
2650  }
2651  }
2652  break;
2653  case 'flex':
2654  $l = strip_tags($value);
2655  break;
2656  default:
2657  if ($defaultPassthrough) {
2658  $l = $value;
2659  } elseif ($theColConf['MM']) {
2660  $l = 'N/A';
2661  } elseif ($value) {
2662  $l = GeneralUtility::fixed_lgd_cs(strip_tags($value), 200);
2663  }
2664  }
2665  // If this field is a password field, then hide the password by changing it to a random number of asterisk (*)
2666  if (!empty($theColConf['eval']) && stristr($theColConf['eval'], 'password')) {
2667  $l = '';
2668  $randomNumber = rand(5, 12);
2669  for ($i = 0; $i < $randomNumber; $i++) {
2670  $l .= '*';
2671  }
2672  }
2673  /*****************
2674  *HOOK: post-processing the human readable output from a record
2675  ****************/
2676  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['postProcessValue'])) {
2677  // Create NULL-reference
2678  $null = null;
2679  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['postProcessValue'] as $_funcRef) {
2680  $params = [
2681  'value' => $l,
2682  'colConf' => $theColConf
2683  ];
2684  $l = GeneralUtility::callUserFunction($_funcRef, $params, $null);
2685  }
2686  }
2687  if ($fixed_lgd_chars) {
2688  return GeneralUtility::fixed_lgd_cs($l, $fixed_lgd_chars);
2689  }
2690  return $l;
2691  }
2692 
2706  public static function getProcessedValueExtra(
2707  $table,
2708  $fN,
2709  $fV,
2710  $fixed_lgd_chars = 0,
2711  $uid = 0,
2712  $forceResult = true,
2713  $pid = 0
2714  ) {
2715  $fVnew = self::getProcessedValue($table, $fN, $fV, $fixed_lgd_chars, 1, 0, $uid, $forceResult, $pid);
2716  if (!isset($fVnew)) {
2717  if (is_array($GLOBALS['TCA'][$table])) {
2718  if ($fN == $GLOBALS['TCA'][$table]['ctrl']['tstamp'] || $fN == $GLOBALS['TCA'][$table]['ctrl']['crdate']) {
2719  $fVnew = self::datetime($fV);
2720  } elseif ($fN === 'pid') {
2721  // Fetches the path with no regard to the users permissions to select pages.
2722  $fVnew = self::getRecordPath($fV, '1=1', 20);
2723  } else {
2724  $fVnew = $fV;
2725  }
2726  }
2727  }
2728  return $fVnew;
2729  }
2730 
2741  public static function getCommonSelectFields($table, $prefix = '', $fields = [])
2742  {
2743  $fields[] = $prefix . 'uid';
2744  if (isset($GLOBALS['TCA'][$table]['ctrl']['label']) && $GLOBALS['TCA'][$table]['ctrl']['label'] != '') {
2745  $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['label'];
2746  }
2747  if ($GLOBALS['TCA'][$table]['ctrl']['label_alt']) {
2748  $secondFields = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$table]['ctrl']['label_alt'], true);
2749  foreach ($secondFields as $fieldN) {
2750  $fields[] = $prefix . $fieldN;
2751  }
2752  }
2753  if (static::isTableWorkspaceEnabled($table)) {
2754  $fields[] = $prefix . 't3ver_id';
2755  $fields[] = $prefix . 't3ver_state';
2756  $fields[] = $prefix . 't3ver_wsid';
2757  $fields[] = $prefix . 't3ver_count';
2758  }
2759  if ($GLOBALS['TCA'][$table]['ctrl']['selicon_field']) {
2760  $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['selicon_field'];
2761  }
2762  if ($GLOBALS['TCA'][$table]['ctrl']['typeicon_column']) {
2763  $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['typeicon_column'];
2764  }
2765  if (is_array($GLOBALS['TCA'][$table]['ctrl']['enablecolumns'])) {
2766  if ($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled']) {
2767  $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'];
2768  }
2769  if ($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['starttime']) {
2770  $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['starttime'];
2771  }
2772  if ($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['endtime']) {
2773  $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['endtime'];
2774  }
2775  if ($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['fe_group']) {
2776  $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['fe_group'];
2777  }
2778  }
2779  return implode(',', array_unique($fields));
2780  }
2781 
2795  public static function makeConfigForm($configArray, $defaults, $dataPrefix)
2796  {
2798  $params = $defaults;
2799  $lines = [];
2800  if (is_array($configArray)) {
2801  foreach ($configArray as $fname => $config) {
2802  if (is_array($config)) {
2803  $lines[$fname] = '<strong>' . htmlspecialchars($config[1]) . '</strong><br />';
2804  $lines[$fname] .= $config[2] . '<br />';
2805  switch ($config[0]) {
2806  case 'string':
2807 
2808  case 'short':
2809  $formEl = '<input type="text" name="' . $dataPrefix . '[' . $fname . ']" value="' . $params[$fname] . '"' . static::getDocumentTemplate()->formWidth(($config[0] === 'short' ? 24 : 48)) . ' />';
2810  break;
2811  case 'check':
2812  $formEl = '<input type="hidden" name="' . $dataPrefix . '[' . $fname . ']" value="0" /><input type="checkbox" name="' . $dataPrefix . '[' . $fname . ']" value="1"' . ($params[$fname] ? ' checked="checked"' : '') . ' />';
2813  break;
2814  case 'comment':
2815  $formEl = '';
2816  break;
2817  case 'select':
2818  $opt = [];
2819  foreach ($config[3] as $k => $v) {
2820  $opt[] = '<option value="' . htmlspecialchars($k) . '"' . ($params[$fname] == $k ? ' selected="selected"' : '') . '>' . htmlspecialchars($v) . '</option>';
2821  }
2822  $formEl = '<select name="' . $dataPrefix . '[' . $fname . ']">'
2823  . implode('', $opt) . '</select>';
2824  break;
2825  default:
2826  $formEl = '<strong>Should not happen. Bug in config.</strong>';
2827  }
2828  $lines[$fname] .= $formEl;
2829  $lines[$fname] .= '<br /><br />';
2830  } else {
2831  $lines[$fname] = '<hr />';
2832  if ($config) {
2833  $lines[$fname] .= '<strong>' . strtoupper(htmlspecialchars($config)) . '</strong><br />';
2834  }
2835  if ($config) {
2836  $lines[$fname] .= '<br />';
2837  }
2838  }
2839  }
2840  }
2841  $out = implode('', $lines);
2842  $out .= '<input class="btn btn-default" type="submit" name="submit" value="Update configuration" />';
2843  return $out;
2844  }
2845 
2846  /*******************************************
2847  *
2848  * Backend Modules API functions
2849  *
2850  *******************************************/
2851 
2859  public static function helpTextArray($table, $field)
2860  {
2861  if (!isset($GLOBALS['TCA_DESCR'][$table]['columns'])) {
2862  static::getLanguageService()->loadSingleTableDescription($table);
2863  }
2864  $output = [
2865  'description' => null,
2866  'title' => null,
2867  'moreInfo' => false
2868  ];
2869  if (is_array($GLOBALS['TCA_DESCR'][$table]) && is_array($GLOBALS['TCA_DESCR'][$table]['columns'][$field])) {
2870  $data = $GLOBALS['TCA_DESCR'][$table]['columns'][$field];
2871  // Add alternative title, if defined
2872  if ($data['alttitle']) {
2873  $output['title'] = $data['alttitle'];
2874  }
2875  // If we have more information to show and access to the cshmanual
2876  if (($data['image_descr'] || $data['seeAlso'] || $data['details'] || $data['syntax'])
2877  && static::getBackendUserAuthentication()->check('modules', 'help_CshmanualCshmanual')
2878  ) {
2879  $output['moreInfo'] = true;
2880  }
2881  // Add description
2882  if ($data['description']) {
2883  $output['description'] = $data['description'];
2884  }
2885  }
2886  return $output;
2887  }
2888 
2897  public static function helpText($table, $field)
2898  {
2899  $helpTextArray = self::helpTextArray($table, $field);
2900  $output = '';
2901  $arrow = '';
2902  // Put header before the rest of the text
2903  if ($helpTextArray['title'] !== null) {
2904  $output .= '<h2>' . $helpTextArray['title'] . '</h2>';
2905  }
2906  // Add see also arrow if we have more info
2907  if ($helpTextArray['moreInfo']) {
2909  $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
2910  $arrow = $iconFactory->getIcon('actions-view-go-forward', Icon::SIZE_SMALL)->render();
2911  }
2912  // Wrap description and arrow in p tag
2913  if ($helpTextArray['description'] !== null || $arrow) {
2914  $output .= '<p class="t3-help-short">' . nl2br(htmlspecialchars($helpTextArray['description'])) . $arrow . '</p>';
2915  }
2916  return $output;
2917  }
2918 
2931  public static function wrapInHelp($table, $field, $text = '', array $overloadHelpText = [])
2932  {
2933  if (!ExtensionManagementUtility::isLoaded('context_help')) {
2934  return $text;
2935  }
2936 
2937  // Initialize some variables
2938  $helpText = '';
2939  $abbrClassAdd = '';
2940  $hasHelpTextOverload = !empty($overloadHelpText);
2941  // Get the help text that should be shown on hover
2942  if (!$hasHelpTextOverload) {
2943  $helpText = self::helpText($table, $field);
2944  }
2945  // If there's a help text or some overload information, proceed with preparing an output
2946  // @todo: right now this is a hard dependency on csh manual, as the whole help system should be moved to
2947  // the extension. The core provides an API for adding help and rendering help, but the rendering
2948  // should be up to the extension itself
2949  if ((!empty($helpText) || $hasHelpTextOverload) && ExtensionManagementUtility::isLoaded('cshmanual')) {
2950  // If no text was given, just use the regular help icon
2951  if ($text == '') {
2953  $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
2954  $text = $iconFactory->getIcon('actions-system-help-open', Icon::SIZE_SMALL)->render();
2955  $abbrClassAdd = '-icon';
2956  }
2957  $text = '<abbr class="t3-help-teaser' . $abbrClassAdd . '">' . $text . '</abbr>';
2958  $wrappedText = '<span class="t3-help-link" href="#" data-table="' . $table . '" data-field="' . $field . '"';
2959  // The overload array may provide a title and a description
2960  // If either one is defined, add them to the "data" attributes
2961  if ($hasHelpTextOverload) {
2962  if (isset($overloadHelpText['title'])) {
2963  $wrappedText .= ' data-title="' . htmlspecialchars($overloadHelpText['title']) . '"';
2964  }
2965  if (isset($overloadHelpText['description'])) {
2966  $wrappedText .= ' data-description="' . htmlspecialchars($overloadHelpText['description']) . '"';
2967  }
2968  }
2969  $wrappedText .= '>' . $text . '</span>';
2970  return $wrappedText;
2971  }
2972  return $text;
2973  }
2974 
2985  public static function cshItem($table, $field, $_ = '', $wrap = '')
2986  {
2987  static::getLanguageService()->loadSingleTableDescription($table);
2988  if (is_array($GLOBALS['TCA_DESCR'][$table])
2989  && is_array($GLOBALS['TCA_DESCR'][$table]['columns'][$field])
2990  ) {
2991  // Creating short description
2992  $output = self::wrapInHelp($table, $field);
2993  if ($output && $wrap) {
2994  $wrParts = explode('|', $wrap);
2995  $output = $wrParts[0] . $output . $wrParts[1];
2996  }
2997  return $output;
2998  }
2999  return '';
3000  }
3001 
3012  public static function editOnClick($params, $_ = '', $requestUri = '')
3013  {
3014  if ($requestUri == -1) {
3015  $returnUrl = 'T3_THIS_LOCATION';
3016  } else {
3017  $returnUrl = GeneralUtility::quoteJSvalue(rawurlencode($requestUri ?: GeneralUtility::getIndpEnv('REQUEST_URI')));
3018  }
3019  return 'window.location.href=' . GeneralUtility::quoteJSvalue(self::getModuleUrl('record_edit') . $params . '&returnUrl=') . '+' . $returnUrl . '; return false;';
3020  }
3021 
3036  public static function viewOnClick(
3037  $pageUid,
3038  $backPath = '',
3039  $rootLine = null,
3040  $anchorSection = '',
3041  $alternativeUrl = '',
3042  $additionalGetVars = '',
3043  $switchFocus = true
3044  ) {
3045  $viewScript = '/index.php?id=';
3046  if ($alternativeUrl) {
3047  $viewScript = $alternativeUrl;
3048  }
3049 
3050  if (
3051  isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['viewOnClickClass'])
3052  && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['viewOnClickClass'])
3053  ) {
3054  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['viewOnClickClass'] as $funcRef) {
3055  $hookObj = GeneralUtility::getUserObj($funcRef);
3056  if (method_exists($hookObj, 'preProcess')) {
3057  $hookObj->preProcess(
3058  $pageUid,
3059  $backPath,
3060  $rootLine,
3061  $anchorSection,
3062  $viewScript,
3063  $additionalGetVars,
3064  $switchFocus
3065  );
3066  }
3067  }
3068  }
3069 
3070  if ($alternativeUrl) {
3071  $previewUrl = $viewScript;
3072  } else {
3073  $permissionClause = $GLOBALS['BE_USER']->getPagePermsClause(Permission::PAGE_SHOW);
3074  $pageInfo = self::readPageAccess($pageUid, $permissionClause);
3075  $additionalGetVars .= self::ADMCMD_previewCmds($pageInfo);
3076  $previewUrl = self::createPreviewUrl($pageUid, $rootLine, $anchorSection, $additionalGetVars, $viewScript);
3077  }
3078 
3079  if (
3080  isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['viewOnClickClass'])
3081  && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['viewOnClickClass'])
3082  ) {
3083  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['viewOnClickClass'] as $className) {
3084  $hookObj = GeneralUtility::makeInstance($className);
3085  if (method_exists($hookObj, 'postProcess')) {
3086  $previewUrl = $hookObj->postProcess(
3087  $previewUrl,
3088  $pageUid,
3089  $rootLine,
3090  $anchorSection,
3091  $viewScript,
3092  $additionalGetVars,
3093  $switchFocus
3094  );
3095  }
3096  }
3097  }
3098 
3099  $onclickCode = 'var previewWin = window.open(' . GeneralUtility::quoteJSvalue($previewUrl) . ',\'newTYPO3frontendWindow\');' . ($switchFocus ? 'previewWin.focus();' : '');
3100  return $onclickCode;
3101  }
3102 
3125  public static function wrapClickMenuOnIcon(
3126  $content,
3127  $table,
3128  $uid = 0,
3129  $context = '',
3130  $_addParams = '',
3131  $_enDisItems = '',
3132  $returnTagParameters = false
3133  ) {
3134  $tagParameters = [
3135  'class' => 't3js-contextmenutrigger',
3136  'data-table' => $table,
3137  'data-uid' => $uid,
3138  'data-context' => $context
3139  ];
3140 
3141  if ($returnTagParameters) {
3142  return $tagParameters;
3143  }
3144  return '<a href="#" ' . GeneralUtility::implodeAttributes($tagParameters, true) . '>' . $content . '</a>';
3145  }
3146 
3154  public static function getLinkToDataHandlerAction($parameters, $redirectUrl = '')
3155  {
3156  $urlParameters = [
3157  'prErr' => 1,
3158  'uPT' => 1,
3159  ];
3160  $url = self::getModuleUrl('tce_db', $urlParameters) . $parameters . '&redirect=';
3161  if ((int)$redirectUrl === -1) {
3162  $url = GeneralUtility::quoteJSvalue($url) . '+T3_THIS_LOCATION';
3163  } else {
3164  $url .= rawurlencode($redirectUrl ?: GeneralUtility::getIndpEnv('REQUEST_URI'));
3165  }
3166  return $url;
3167  }
3168 
3180  protected static function createPreviewUrl($pageUid, $rootLine, $anchorSection, $additionalGetVars, $viewScript)
3181  {
3182  // Look if a fixed preview language should be added:
3183  $beUser = static::getBackendUserAuthentication();
3184  $viewLanguageOrder = $beUser->getTSConfigVal('options.view.languageOrder');
3185 
3186  if ((string)$viewLanguageOrder !== '') {
3187  $suffix = '';
3188  // Find allowed languages (if none, all are allowed!)
3189  $allowedLanguages = null;
3190  if (!$beUser->user['admin'] && $beUser->groupData['allowed_languages'] !== '') {
3191  $allowedLanguages = array_flip(explode(',', $beUser->groupData['allowed_languages']));
3192  }
3193  // Traverse the view order, match first occurrence:
3194  $languageOrder = GeneralUtility::intExplode(',', $viewLanguageOrder);
3195  foreach ($languageOrder as $langUid) {
3196  if (is_array($allowedLanguages) && !empty($allowedLanguages)) {
3197  // Choose if set.
3198  if (isset($allowedLanguages[$langUid])) {
3199  $suffix = '&L=' . $langUid;
3200  break;
3201  }
3202  } else {
3203  // All allowed since no lang. are listed.
3204  $suffix = '&L=' . $langUid;
3205  break;
3206  }
3207  }
3208  // Add it
3209  $additionalGetVars .= $suffix;
3210  }
3211 
3212  // Check a mount point needs to be previewed
3213  $sys_page = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\Page\PageRepository::class);
3214  $sys_page->init(false);
3215  $mountPointInfo = $sys_page->getMountPointInfo($pageUid);
3216 
3217  if ($mountPointInfo && $mountPointInfo['overlay']) {
3218  $pageUid = $mountPointInfo['mount_pid'];
3219  $additionalGetVars .= '&MP=' . $mountPointInfo['MPvar'];
3220  }
3221  $viewDomain = self::getViewDomain($pageUid, $rootLine);
3222 
3223  return $viewDomain . $viewScript . $pageUid . $additionalGetVars . $anchorSection;
3224  }
3225 
3234  public static function getViewDomain($pageId, $rootLine = null)
3235  {
3236  $domain = rtrim(GeneralUtility::getIndpEnv('TYPO3_SITE_URL'), '/');
3237  if (!is_array($rootLine)) {
3238  $rootLine = self::BEgetRootLine($pageId);
3239  }
3240  // Checks alternate domains
3241  if (!empty($rootLine)) {
3242  $urlParts = parse_url($domain);
3243  $protocol = GeneralUtility::getIndpEnv('TYPO3_SSL') ? 'https' : 'http';
3244  $previewDomainConfig = static::getBackendUserAuthentication()->getTSConfig(
3245  'TCEMAIN.previewDomain',
3246  self::getPagesTSconfig($pageId)
3247  );
3248  if ($previewDomainConfig['value']) {
3249  if (strpos($previewDomainConfig['value'], '://') !== false) {
3250  list($protocol, $domainName) = explode('://', $previewDomainConfig['value']);
3251  } else {
3252  $domainName = $previewDomainConfig['value'];
3253  }
3254  } else {
3255  $domainName = self::firstDomainRecord($rootLine);
3256  }
3257  if ($domainName) {
3258  $domain = $domainName;
3259  } else {
3260  $domainRecord = self::getDomainStartPage($urlParts['host'], $urlParts['path']);
3261  $domain = $domainRecord['domainName'];
3262  }
3263  if ($domain) {
3264  $domain = $protocol . '://' . $domain;
3265  } else {
3266  $domain = rtrim(GeneralUtility::getIndpEnv('TYPO3_SITE_URL'), '/');
3267  }
3268  // Append port number if lockSSLPort is not the standard port 443
3269  $portNumber = (int)$GLOBALS['TYPO3_CONF_VARS']['BE']['lockSSLPort'];
3270  if ($portNumber > 0 && $portNumber !== 443 && $portNumber < 65536 && $protocol === 'https') {
3271  $domain .= ':' . strval($portNumber);
3272  }
3273  }
3274  return $domain;
3275  }
3276 
3285  public static function getModTSconfig($id, $TSref)
3286  {
3287  $beUser = static::getBackendUserAuthentication();
3288  $pageTS_modOptions = $beUser->getTSConfig($TSref, static::getPagesTSconfig($id));
3289  $BE_USER_modOptions = $beUser->getTSConfig($TSref);
3290  if (is_null($BE_USER_modOptions['value'])) {
3291  unset($BE_USER_modOptions['value']);
3292  }
3293  ArrayUtility::mergeRecursiveWithOverrule($pageTS_modOptions, $BE_USER_modOptions);
3294  return $pageTS_modOptions;
3295  }
3296 
3310  public static function getFuncMenu(
3311  $mainParams,
3312  $elementName,
3313  $currentValue,
3314  $menuItems,
3315  $script = '',
3316  $addParams = ''
3317  ) {
3318  if (!is_array($menuItems) || count($menuItems) <= 1) {
3319  return '';
3320  }
3321  $scriptUrl = self::buildScriptUrl($mainParams, $addParams, $script);
3322  $options = [];
3323  foreach ($menuItems as $value => $label) {
3324  $options[] = '<option value="'
3325  . htmlspecialchars($value) . '"'
3326  . ((string)$currentValue === (string)$value ? ' selected="selected"' : '') . '>'
3327  . htmlspecialchars($label, ENT_COMPAT, 'UTF-8', false) . '</option>';
3328  }
3329  $dataMenuIdentifier = str_replace(['SET[', ']'], '', $elementName);
3330  $dataMenuIdentifier = GeneralUtility::camelCaseToLowerCaseUnderscored($dataMenuIdentifier);
3331  $dataMenuIdentifier = str_replace('_', '-', $dataMenuIdentifier);
3332  if (!empty($options)) {
3333  $onChange = 'jumpToUrl(' . GeneralUtility::quoteJSvalue($scriptUrl . '&' . $elementName . '=') . '+this.options[this.selectedIndex].value,this);';
3334  return '
3335 
3336  <!-- Function Menu of module -->
3337  <select class="form-control" name="' . $elementName . '" onchange="' . htmlspecialchars($onChange) . '" data-menu-identifier="' . htmlspecialchars($dataMenuIdentifier) . '">
3338  ' . implode('
3339  ', $options) . '
3340  </select>
3341  ';
3342  }
3343  return '';
3344  }
3345 
3360  public static function getDropdownMenu(
3361  $mainParams,
3362  $elementName,
3363  $currentValue,
3364  $menuItems,
3365  $script = '',
3366  $addParams = ''
3367  ) {
3368  if (!is_array($menuItems) || count($menuItems) <= 1) {
3369  return '';
3370  }
3371  $scriptUrl = self::buildScriptUrl($mainParams, $addParams, $script);
3372  $options = [];
3373  foreach ($menuItems as $value => $label) {
3374  $options[] = '<option value="'
3375  . htmlspecialchars($value) . '"'
3376  . ((string)$currentValue === (string)$value ? ' selected="selected"' : '') . '>'
3377  . htmlspecialchars($label, ENT_COMPAT, 'UTF-8', false) . '</option>';
3378  }
3379  $dataMenuIdentifier = str_replace(['SET[', ']'], '', $elementName);
3380  $dataMenuIdentifier = GeneralUtility::camelCaseToLowerCaseUnderscored($dataMenuIdentifier);
3381  $dataMenuIdentifier = str_replace('_', '-', $dataMenuIdentifier);
3382  if (!empty($options)) {
3383  $onChange = 'jumpToUrl(' . GeneralUtility::quoteJSvalue($scriptUrl . '&' . $elementName . '=') . '+this.options[this.selectedIndex].value,this);';
3384  return '
3385  <div class="form-group">
3386  <!-- Function Menu of module -->
3387  <select class="form-control input-sm" name="' . htmlspecialchars($elementName) . '" onchange="' . htmlspecialchars($onChange) . '" data-menu-identifier="' . htmlspecialchars($dataMenuIdentifier) . '">
3388  ' . implode(LF, $options) . '
3389  </select>
3390  </div>
3391  ';
3392  }
3393  return '';
3394  }
3395 
3409  public static function getFuncCheck(
3410  $mainParams,
3411  $elementName,
3412  $currentValue,
3413  $script = '',
3414  $addParams = '',
3415  $tagParams = ''
3416  ) {
3417  $scriptUrl = self::buildScriptUrl($mainParams, $addParams, $script);
3418  $onClick = 'jumpToUrl(' . GeneralUtility::quoteJSvalue($scriptUrl . '&' . $elementName . '=') . '+(this.checked?1:0),this);';
3419 
3420  return
3421  '<input' .
3422  ' type="checkbox"' .
3423  ' class="checkbox"' .
3424  ' name="' . $elementName . '"' .
3425  ($currentValue ? ' checked="checked"' : '') .
3426  ' onclick="' . htmlspecialchars($onClick) . '"' .
3427  ($tagParams ? ' ' . $tagParams : '') .
3428  ' value="1"' .
3429  ' />';
3430  }
3431 
3445  public static function getFuncInput(
3446  $mainParams,
3447  $elementName,
3448  $currentValue,
3449  $size = 10,
3450  $script = '',
3451  $addParams = ''
3452  ) {
3453  $scriptUrl = self::buildScriptUrl($mainParams, $addParams, $script);
3454  $onChange = 'jumpToUrl(' . GeneralUtility::quoteJSvalue($scriptUrl . '&' . $elementName . '=') . '+escape(this.value),this);';
3455  return '<input type="text"' . static::getDocumentTemplate()->formWidth($size) . ' name="' . $elementName . '" value="' . htmlspecialchars($currentValue) . '" onchange="' . htmlspecialchars($onChange) . '" />';
3456  }
3457 
3466  protected static function buildScriptUrl($mainParams, $addParams, $script = '')
3467  {
3468  if (!is_array($mainParams)) {
3469  $mainParams = ['id' => $mainParams];
3470  }
3471  if (!$script) {
3472  $script = basename(PATH_thisScript);
3473  }
3474 
3475  if (GeneralUtility::_GP('route')) {
3476  $router = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\Router::class);
3477  $route = $router->match(GeneralUtility::_GP('route'));
3478  $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
3479  $scriptUrl = (string)$uriBuilder->buildUriFromRoute($route->getOption('_identifier'));
3480  $scriptUrl .= $addParams;
3481  } elseif ($script === 'index.php' && GeneralUtility::_GET('M')) {
3482  $scriptUrl = self::getModuleUrl(GeneralUtility::_GET('M'), $mainParams) . $addParams;
3483  } else {
3484  $scriptUrl = $script . '?' . GeneralUtility::implodeArrayForUrl('', $mainParams) . $addParams;
3485  }
3486 
3487  return $scriptUrl;
3488  }
3489 
3499  public static function unsetMenuItems($modTSconfig, $itemArray, $TSref)
3500  {
3501  // Getting TS-config options for this module for the Backend User:
3502  $conf = static::getBackendUserAuthentication()->getTSConfig($TSref, $modTSconfig);
3503  if (is_array($conf['properties'])) {
3504  foreach ($conf['properties'] as $key => $val) {
3505  if (!$val) {
3506  unset($itemArray[$key]);
3507  }
3508  }
3509  }
3510  return $itemArray;
3511  }
3512 
3521  public static function setUpdateSignal($set = '', $params = '')
3522  {
3523  $beUser = static::getBackendUserAuthentication();
3524  $modData = $beUser->getModuleData(
3525  \TYPO3\CMS\Backend\Utility\BackendUtility::class . '::getUpdateSignal',
3526  'ses'
3527  );
3528  if ($set) {
3529  $modData[$set] = [
3530  'set' => $set,
3531  'parameter' => $params
3532  ];
3533  } else {
3534  // clear the module data
3535  $modData = [];
3536  }
3537  $beUser->pushModuleData(\TYPO3\CMS\Backend\Utility\BackendUtility::class . '::getUpdateSignal', $modData);
3538  }
3539 
3548  public static function getUpdateSignalCode()
3549  {
3550  $signals = [];
3551  $modData = static::getBackendUserAuthentication()->getModuleData(
3552  \TYPO3\CMS\Backend\Utility\BackendUtility::class . '::getUpdateSignal',
3553  'ses'
3554  );
3555  if (empty($modData)) {
3556  return '';
3557  }
3558  // Hook: Allows to let TYPO3 execute your JS code
3559  if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['updateSignalHook'])) {
3560  $updateSignals = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['updateSignalHook'];
3561  } else {
3562  $updateSignals = [];
3563  }
3564  // Loop through all setUpdateSignals and get the JS code
3565  foreach ($modData as $set => $val) {
3566  if (isset($updateSignals[$set])) {
3567  $params = ['set' => $set, 'parameter' => $val['parameter'], 'JScode' => ''];
3568  $ref = null;
3569  GeneralUtility::callUserFunction($updateSignals[$set], $params, $ref);
3570  $signals[] = $params['JScode'];
3571  } else {
3572  switch ($set) {
3573  case 'updatePageTree':
3574  $signals[] = '
3575  if (top && top.TYPO3.Backend && top.TYPO3.Backend.NavigationContainer.PageTree) {
3576  top.TYPO3.Backend.NavigationContainer.PageTree.refreshTree();
3577  }
3578  ';
3579  break;
3580  case 'updateFolderTree':
3581  $signals[] = '
3582  if (top && top.nav_frame && top.nav_frame.location) {
3583  top.nav_frame.location.reload(true);
3584  }';
3585  break;
3586  case 'updateModuleMenu':
3587  $signals[] = '
3588  if (top && top.TYPO3.ModuleMenu && top.TYPO3.ModuleMenu.App) {
3589  top.TYPO3.ModuleMenu.App.refreshMenu();
3590  }';
3591  break;
3592  case 'updateTopbar':
3593  $signals[] = '
3594  if (top && top.TYPO3.Backend && top.TYPO3.Backend.Topbar) {
3595  top.TYPO3.Backend.Topbar.refresh();
3596  }';
3597  break;
3598  }
3599  }
3600  }
3601  $content = implode(LF, $signals);
3602  // For backwards compatibility, should be replaced
3603  self::setUpdateSignal();
3604  return $content;
3605  }
3606 
3621  public static function getModuleData(
3622  $MOD_MENU,
3623  $CHANGED_SETTINGS,
3624  $modName,
3625  $type = '',
3626  $dontValidateList = '',
3627  $setDefaultList = ''
3628  ) {
3629  if ($modName && is_string($modName)) {
3630  // Getting stored user-data from this module:
3631  $beUser = static::getBackendUserAuthentication();
3632  $settings = $beUser->getModuleData($modName, $type);
3633  $changed = 0;
3634  if (!is_array($settings)) {
3635  $changed = 1;
3636  $settings = [];
3637  }
3638  if (is_array($MOD_MENU)) {
3639  foreach ($MOD_MENU as $key => $var) {
3640  // If a global var is set before entering here. eg if submitted, then it's substituting the current value the array.
3641  if (is_array($CHANGED_SETTINGS) && isset($CHANGED_SETTINGS[$key])) {
3642  if (is_array($CHANGED_SETTINGS[$key])) {
3643  $serializedSettings = serialize($CHANGED_SETTINGS[$key]);
3644  if ((string)$settings[$key] !== $serializedSettings) {
3645  $settings[$key] = $serializedSettings;
3646  $changed = 1;
3647  }
3648  } else {
3649  if ((string)$settings[$key] !== (string)$CHANGED_SETTINGS[$key]) {
3650  $settings[$key] = $CHANGED_SETTINGS[$key];
3651  $changed = 1;
3652  }
3653  }
3654  }
3655  // If the $var is an array, which denotes the existence of a menu, we check if the value is permitted
3656  if (is_array($var) && (!$dontValidateList || !GeneralUtility::inList($dontValidateList, $key))) {
3657  // If the setting is an array or not present in the menu-array, MOD_MENU, then the default value is inserted.
3658  if (is_array($settings[$key]) || !isset($MOD_MENU[$key][$settings[$key]])) {
3659  $settings[$key] = (string)key($var);
3660  $changed = 1;
3661  }
3662  }
3663  // Sets default values (only strings/checkboxes, not menus)
3664  if ($setDefaultList && !is_array($var)) {
3665  if (GeneralUtility::inList($setDefaultList, $key) && !isset($settings[$key])) {
3666  $settings[$key] = (string)$var;
3667  }
3668  }
3669  }
3670  } else {
3671  die('No menu!');
3672  }
3673  if ($changed) {
3674  $beUser->pushModuleData($modName, $settings);
3675  }
3676  return $settings;
3677  }
3678  die('Wrong module name: "' . $modName . '"');
3679  }
3680 
3688  public static function getModuleUrl($moduleName, $urlParameters = [])
3689  {
3691  $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
3692  try {
3693  $uri = $uriBuilder->buildUriFromRoute($moduleName, $urlParameters);
3694  } catch (\TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException $e) {
3695  // no route registered, use the fallback logic to check for a module
3696  $uri = $uriBuilder->buildUriFromModule($moduleName, $urlParameters);
3697  }
3698  return (string)$uri;
3699  }
3700 
3713  public static function getAjaxUrl($ajaxIdentifier, array $urlParameters = [])
3714  {
3717  $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
3718  try {
3719  $routeIdentifier = 'ajax_' . $ajaxIdentifier;
3720  $uri = $uriBuilder->buildUriFromRoute($routeIdentifier, $urlParameters);
3721  } catch (\TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException $e) {
3722  // no route registered, use the fallback logic to check for a module
3723  $uri = $uriBuilder->buildUriFromAjaxId($ajaxIdentifier, $urlParameters);
3724  }
3725  return (string)$uri;
3726  }
3727 
3737  public static function getListViewLink($urlParameters = [], $linkTitle = '', $linkText = '')
3738  {
3741  $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
3742  return '<a href="'
3743  . htmlspecialchars(self::getModuleUrl('web_list', $urlParameters)) . '" title="'
3744  . htmlspecialchars($linkTitle) . '">'
3745  . $iconFactory->getIcon('actions-system-list-open', Icon::SIZE_SMALL)->render()
3746  . htmlspecialchars($linkText) . '</a>';
3747  }
3748 
3749  /*******************************************
3750  *
3751  * Core
3752  *
3753  *******************************************/
3763  public static function lockRecords($table = '', $uid = 0, $pid = 0)
3764  {
3765  $beUser = static::getBackendUserAuthentication();
3766  if (isset($beUser->user['uid'])) {
3767  $userId = (int)$beUser->user['uid'];
3768  if ($table && $uid) {
3769  $fieldsValues = [
3770  'userid' => $userId,
3771  'feuserid' => 0,
3772  'tstamp' => $GLOBALS['EXEC_TIME'],
3773  'record_table' => $table,
3774  'record_uid' => $uid,
3775  'username' => $beUser->user['username'],
3776  'record_pid' => $pid
3777  ];
3778  GeneralUtility::makeInstance(ConnectionPool::class)
3779  ->getConnectionForTable('sys_lockedrecords')
3780  ->insert(
3781  'sys_lockedrecords',
3782  $fieldsValues
3783  );
3784  } else {
3785  GeneralUtility::makeInstance(ConnectionPool::class)
3786  ->getConnectionForTable('sys_lockedrecords')
3787  ->delete(
3788  'sys_lockedrecords',
3789  ['userid' => (int)$userId]
3790  );
3791  }
3792  }
3793  }
3794 
3807  public static function isRecordLocked($table, $uid)
3808  {
3809  $runtimeCache = self::getRuntimeCache();
3810  $cacheId = 'backend-recordLocked';
3811  $recordLockedCache = $runtimeCache->get($cacheId);
3812  if ($recordLockedCache !== false) {
3813  $lockedRecords = $recordLockedCache;
3814  } else {
3815  $lockedRecords = [];
3816 
3817  $queryBuilder = static::getQueryBuilderForTable('sys_lockedrecords');
3818  $result = $queryBuilder
3819  ->select('*')
3820  ->from('sys_lockedrecords')
3821  ->where(
3822  $queryBuilder->expr()->neq(
3823  'sys_lockedrecords.userid',
3824  $queryBuilder->createNamedParameter(
3825  static::getBackendUserAuthentication()->user['uid'],
3826  \PDO::PARAM_INT
3827  )
3828  ),
3829  $queryBuilder->expr()->gt(
3830  'sys_lockedrecords.tstamp',
3831  $queryBuilder->createNamedParameter(
3832  ($GLOBALS['EXEC_TIME'] - 2 * 3600),
3833  \PDO::PARAM_INT
3834  )
3835  )
3836  )
3837  ->execute();
3838 
3839  $lang = static::getLanguageService();
3840  while ($row = $result->fetch()) {
3841  // Get the type of the user that locked this record:
3842  if ($row['userid']) {
3843  $userTypeLabel = 'beUser';
3844  } elseif ($row['feuserid']) {
3845  $userTypeLabel = 'feUser';
3846  } else {
3847  $userTypeLabel = 'user';
3848  }
3849  $userType = $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.' . $userTypeLabel);
3850  // Get the username (if available):
3851  if ($row['username']) {
3852  $userName = $row['username'];
3853  } else {
3854  $userName = $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.unknownUser');
3855  }
3856  $lockedRecords[$row['record_table'] . ':' . $row['record_uid']] = $row;
3857  $lockedRecords[$row['record_table'] . ':' . $row['record_uid']]['msg'] = sprintf(
3858  $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.lockedRecordUser'),
3859  $userType,
3860  $userName,
3861  self::calcAge(
3862  $GLOBALS['EXEC_TIME'] - $row['tstamp'],
3863  $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears')
3864  )
3865  );
3866  if ($row['record_pid'] && !isset($lockedRecords[$row['record_table'] . ':' . $row['record_pid']])) {
3867  $lockedRecords['pages:' . $row['record_pid']]['msg'] = sprintf(
3868  $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.lockedRecordUser_content'),
3869  $userType,
3870  $userName,
3871  self::calcAge(
3872  $GLOBALS['EXEC_TIME'] - $row['tstamp'],
3873  $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears')
3874  )
3875  );
3876  }
3877  }
3878  $runtimeCache->set($cacheId, $lockedRecords);
3879  }
3880 
3881  return $lockedRecords[$table . ':' . $uid] ?? false;
3882  }
3883 
3892  public static function getTCEFORM_TSconfig($table, $row)
3893  {
3894  self::fixVersioningPid($table, $row);
3895  $res = [];
3896  // Get main config for the table
3897  list($TScID, $cPid) = self::getTSCpid($table, $row['uid'], $row['pid']);
3898  if ($TScID >= 0) {
3899  $tempConf = static::getBackendUserAuthentication()->getTSConfig(
3900  'TCEFORM.' . $table,
3901  self::getPagesTSconfig($TScID)
3902  );
3903  if (is_array($tempConf['properties'])) {
3904  $typeVal = self::getTCAtypeValue($table, $row);
3905  foreach ($tempConf['properties'] as $key => $val) {
3906  if (is_array($val)) {
3907  $fieldN = substr($key, 0, -1);
3908  $res[$fieldN] = $val;
3909  unset($res[$fieldN]['types.']);
3910  if ((string)$typeVal !== '' && is_array($val['types.'][$typeVal . '.'])) {
3911  ArrayUtility::mergeRecursiveWithOverrule($res[$fieldN], $val['types.'][$typeVal . '.']);
3912  }
3913  }
3914  }
3915  }
3916  }
3917  $res['_CURRENT_PID'] = $cPid;
3918  $res['_THIS_UID'] = $row['uid'];
3919  // So the row will be passed to foreign_table_where_query()
3920  $res['_THIS_ROW'] = $row;
3921  return $res;
3922  }
3923 
3937  public static function getTSconfig_pidValue($table, $uid, $pid)
3938  {
3939  // If pid is an integer this takes precedence in our lookup.
3941  $thePidValue = (int)$pid;
3942  // If ref to another record, look that record up.
3943  if ($thePidValue < 0) {
3944  $pidRec = self::getRecord($table, abs($thePidValue), 'pid');
3945  $thePidValue = is_array($pidRec) ? $pidRec['pid'] : -2;
3946  }
3947  } else {
3948  // Try to fetch the record pid from uid. If the uid is 'NEW...' then this will of course return nothing
3949  $rr = self::getRecord($table, $uid);
3950  $thePidValue = null;
3951  if (is_array($rr)) {
3952  // First check if the pid is -1 which means it is a workspaced element. Get the "real" record:
3953  if ($rr['pid'] == '-1') {
3954  $rr = self::getRecord($table, $rr['t3ver_oid'], 'pid');
3955  if (is_array($rr)) {
3956  $thePidValue = $rr['pid'];
3957  }
3958  } else {
3959  // Returning the "pid" of the record
3960  $thePidValue = $rr['pid'];
3961  }
3962  }
3963  if (!$thePidValue) {
3964  // Returns -1 if the record with this pid was not found.
3965  $thePidValue = -1;
3966  }
3967  }
3968  return $thePidValue;
3969  }
3970 
3980  public static function getPidForModTSconfig($table, $uid, $pid)
3981  {
3982  return $table === 'pages' && MathUtility::canBeInterpretedAsInteger($uid) ? $uid : $pid;
3983  }
3984 
3997  public static function getTSCpidCached($table, $uid, $pid)
3998  {
3999  // A local first level cache
4000  static $firstLevelCache;
4001 
4002  if (!is_array($firstLevelCache)) {
4003  $firstLevelCache = [];
4004  }
4005 
4006  $key = $table . ':' . $uid . ':' . $pid;
4007  if (!isset($firstLevelCache[$key])) {
4008  $firstLevelCache[$key] = static::getTSCpid($table, $uid, $pid);
4009  }
4010  return $firstLevelCache[$key];
4011  }
4012 
4024  public static function getTSCpid($table, $uid, $pid)
4025  {
4026  // If pid is negative (referring to another record) the pid of the other record is fetched and returned.
4027  $cPid = self::getTSconfig_pidValue($table, $uid, $pid);
4028  // $TScID is the id of $table = pages, else it's the pid of the record.
4029  $TScID = self::getPidForModTSconfig($table, $uid, $cPid);
4030  return [$TScID, $cPid];
4031  }
4032 
4039  public static function firstDomainRecord($rootLine)
4040  {
4041  $queryBuilder = static::getQueryBuilderForTable('sys_domain');
4042  $queryBuilder->getRestrictions()
4043  ->removeAll()
4044  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
4045  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
4046 
4047  $queryBuilder->select('domainName')
4048  ->from('sys_domain')
4049  ->where(
4050  $queryBuilder->expr()->eq(
4051  'pid',
4052  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT, ':pid')
4053  ),
4054  $queryBuilder->expr()->eq(
4055  'redirectTo',
4056  $queryBuilder->createNamedParameter('', \PDO::PARAM_STR)
4057  ),
4058  $queryBuilder->expr()->eq(
4059  'hidden',
4060  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
4061  )
4062  )
4063  ->setMaxResults(1)
4064  ->orderBy('sorting');
4065 
4066  foreach ($rootLine as $row) {
4067  $domainName = $queryBuilder->setParameter('pid', $row['uid'], \PDO::PARAM_INT)
4068  ->execute()
4069  ->fetchColumn(0);
4070 
4071  if ($domainName) {
4072  return rtrim($domainName, '/');
4073  }
4074  }
4075  return null;
4076  }
4077 
4085  public static function getDomainStartPage($domain, $path = '')
4086  {
4087  $domain = explode(':', $domain);
4088  $domain = strtolower(preg_replace('/\\.$/', '', $domain[0]));
4089  // Path is calculated.
4090  $path = trim(preg_replace('/\\/[^\\/]*$/', '', $path));
4091  // Stuff
4092  $domain .= $path;
4093 
4094  $queryBuilder = static::getQueryBuilderForTable('sys_domain');
4095  $queryBuilder->getRestrictions()
4096  ->removeAll()
4097  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
4098 
4099  $result = $queryBuilder
4100  ->select('sys_domain.*')
4101  ->from('sys_domain')
4102  ->from('pages')
4103  ->where(
4104  $queryBuilder->expr()->eq(
4105  'sys_domain.pid',
4106  $queryBuilder->quoteIdentifier('pages.uid')
4107  ),
4108  $queryBuilder->expr()->orX(
4109  $queryBuilder->expr()->eq(
4110  'sys_domain.domainName',
4111  $queryBuilder->createNamedParameter($domain, \PDO::PARAM_STR)
4112  ),
4113  $queryBuilder->expr()->eq(
4114  'sys_domain.domainName',
4115  $queryBuilder->createNamedParameter($domain . '/', \PDO::PARAM_STR)
4116  )
4117  )
4118  )
4119  ->execute()
4120  ->fetch();
4121 
4122  return $result;
4123  }
4124 
4137  public static function RTEsetup($RTEprop, $table, $field, $type = '')
4138  {
4140  $thisConfig = is_array($RTEprop['default.']) ? $RTEprop['default.'] : [];
4141  $thisFieldConf = $RTEprop['config.'][$table . '.'][$field . '.'];
4142  if (is_array($thisFieldConf)) {
4143  unset($thisFieldConf['types.']);
4144  ArrayUtility::mergeRecursiveWithOverrule($thisConfig, $thisFieldConf);
4145  }
4146  if ($type && is_array($RTEprop['config.'][$table . '.'][$field . '.']['types.'][$type . '.'])) {
4148  $thisConfig,
4149  $RTEprop['config.'][$table . '.'][$field . '.']['types.'][$type . '.']
4150  );
4151  }
4152  return $thisConfig;
4153  }
4154 
4162  public static function &softRefParserObj($spKey)
4163  {
4164  // If no softRef parser object has been set previously, try to create it:
4165  if (!isset($GLOBALS['T3_VAR']['softRefParser'][$spKey])) {
4166  // Set the object string to blank by default:
4167  $GLOBALS['T3_VAR']['softRefParser'][$spKey] = '';
4168  // Now, try to create parser object:
4169  $objRef = null;
4170  if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['softRefParser'][$spKey])) {
4171  $objRef = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['softRefParser'][$spKey];
4172  if ($objRef) {
4173  $softRefParserObj = GeneralUtility::getUserObj($objRef);
4174  if (is_object($softRefParserObj)) {
4175  $GLOBALS['T3_VAR']['softRefParser'][$spKey] = $softRefParserObj;
4176  }
4177  }
4178  }
4179  }
4180  // Return RTE object (if any!)
4181  return $GLOBALS['T3_VAR']['softRefParser'][$spKey];
4182  }
4183 
4189  protected static function getRuntimeCache()
4190  {
4191  return GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_runtime');
4192  }
4193 
4201  public static function explodeSoftRefParserList($parserList)
4202  {
4203  // Return immediately if list is blank:
4204  if ((string)$parserList === '') {
4205  return false;
4206  }
4207 
4208  $runtimeCache = self::getRuntimeCache();
4209  $cacheId = 'backend-softRefList-' . md5($parserList);
4210  $parserListCache = $runtimeCache->get($cacheId);
4211  if ($parserListCache !== false) {
4212  return $parserListCache;
4213  }
4214 
4215  // Otherwise parse the list:
4216  $keyList = GeneralUtility::trimExplode(',', $parserList, true);
4217  $output = [];
4218  foreach ($keyList as $val) {
4219  $reg = [];
4220  if (preg_match('/^([[:alnum:]_-]+)\\[(.*)\\]$/', $val, $reg)) {
4221  $output[$reg[1]] = GeneralUtility::trimExplode(';', $reg[2], true);
4222  } else {
4223  $output[$val] = '';
4224  }
4225  }
4226  $runtimeCache->set($cacheId, $output);
4227  return $output;
4228  }
4229 
4236  public static function isModuleSetInTBE_MODULES($modName)
4237  {
4238  $loaded = [];
4239  foreach ($GLOBALS['TBE_MODULES'] as $mkey => $list) {
4240  $loaded[$mkey] = 1;
4241  if (!is_array($list) && trim($list)) {
4242  $subList = GeneralUtility::trimExplode(',', $list, true);
4243  foreach ($subList as $skey) {
4244  $loaded[$mkey . '_' . $skey] = 1;
4245  }
4246  }
4247  }
4248  return $modName && isset($loaded[$modName]);
4249  }
4250 
4260  public static function referenceCount($table, $ref, $msg = '', $count = null)
4261  {
4262  if ($count === null) {
4263 
4264  // Build base query
4265  $queryBuilder = static::getQueryBuilderForTable('sys_refindex');
4266  $queryBuilder
4267  ->count('*')
4268  ->from('sys_refindex')
4269  ->where(
4270  $queryBuilder->expr()->eq('ref_table', $queryBuilder->createNamedParameter($table, \PDO::PARAM_STR)),
4271  $queryBuilder->expr()->eq('deleted', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
4272  );
4273 
4274  // Look up the path:
4275  if ($table === '_FILE') {
4276  if (!GeneralUtility::isFirstPartOfStr($ref, PATH_site)) {
4277  return '';
4278  }
4279 
4280  $ref = PathUtility::stripPathSitePrefix($ref);
4281  $queryBuilder->andWhere(
4282  $queryBuilder->expr()->eq('ref_string', $queryBuilder->createNamedParameter($ref, \PDO::PARAM_STR))
4283  );
4284  } else {
4285  $queryBuilder->andWhere(
4286  $queryBuilder->expr()->eq('ref_uid', $queryBuilder->createNamedParameter($ref, \PDO::PARAM_INT))
4287  );
4288  if ($table === 'sys_file') {
4289  $queryBuilder->andWhere($queryBuilder->expr()->neq('tablename', $queryBuilder->quote('sys_file_metadata')));
4290  }
4291  }
4292 
4293  $count = $queryBuilder->execute()->fetchColumn(0);
4294  }
4295 
4296  if ($count) {
4297  return $msg ? sprintf($msg, $count) : $count;
4298  }
4299  return $msg ? '' : 0;
4300  }
4301 
4310  public static function translationCount($table, $ref, $msg = '')
4311  {
4312  $count = null;
4313  if ($table !== 'pages'
4314  && $GLOBALS['TCA'][$table]['ctrl']['languageField']
4315  && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
4316  && $table !== 'pages_language_overlay'
4317  ) {
4318  $queryBuilder = static::getQueryBuilderForTable($table);
4319  $queryBuilder->getRestrictions()
4320  ->removeAll()
4321  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
4322 
4323  $count = (int)$queryBuilder
4324  ->count('*')
4325  ->from($table)
4326  ->where(
4327  $queryBuilder->expr()->eq(
4328  $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
4329  $queryBuilder->createNamedParameter($ref, \PDO::PARAM_INT)
4330  ),
4331  $queryBuilder->expr()->neq(
4332  $GLOBALS['TCA'][$table]['ctrl']['languageField'],
4333  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
4334  )
4335  )
4336  ->execute()
4337  ->fetchColumn(0);
4338  }
4339 
4340  if ($count && $msg) {
4341  return sprintf($msg, $count);
4342  }
4343 
4344  if ($count) {
4345  return $msg ? sprintf($msg, $count) : $count;
4346  }
4347  return $msg ? '' : 0;
4348  }
4349 
4350  /*******************************************
4351  *
4352  * Workspaces / Versioning
4353  *
4354  *******************************************/
4366  public static function selectVersionsOfRecord(
4367  $table,
4368  $uid,
4369  $fields = '*',
4370  $workspace = 0,
4371  $includeDeletedRecords = false,
4372  $row = null
4373  ) {
4374  $realPid = 0;
4375  $outputRows = [];
4376  if ($GLOBALS['TCA'][$table] && static::isTableWorkspaceEnabled($table)) {
4377  if (is_array($row) && !$includeDeletedRecords) {
4378  $row['_CURRENT_VERSION'] = true;
4379  $realPid = $row['pid'];
4380  $outputRows[] = $row;
4381  } else {
4382  // Select UID version:
4383  $row = self::getRecord($table, $uid, $fields, '', !$includeDeletedRecords);
4384  // Add rows to output array:
4385  if ($row) {
4386  $row['_CURRENT_VERSION'] = true;
4387  $realPid = $row['pid'];
4388  $outputRows[] = $row;
4389  }
4390  }
4391 
4392  $queryBuilder = static::getQueryBuilderForTable($table);
4393  $queryBuilder->getRestrictions()->removeAll();
4394 
4395  // build fields to select
4396  $queryBuilder->select(...GeneralUtility::trimExplode(',', $fields));
4397 
4398  $queryBuilder
4399  ->from($table)
4400  ->where(
4401  $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)),
4402  $queryBuilder->expr()->neq('uid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)),
4403  $queryBuilder->expr()->eq('t3ver_oid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT))
4404  )
4405  ->orderBy('t3ver_id', 'DESC');
4406 
4407  if (!$includeDeletedRecords) {
4408  $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
4409  }
4410 
4411  if ($workspace === 0) {
4412  // Only in Live WS
4413  $queryBuilder->andWhere(
4414  $queryBuilder->expr()->eq(
4415  't3ver_wsid',
4416  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
4417  )
4418  );
4419  } elseif ($workspace !== null) {
4420  // In Live WS and Workspace with given ID
4421  $queryBuilder->andWhere(
4422  $queryBuilder->expr()->in(
4423  't3ver_wsid',
4424  $queryBuilder->createNamedParameter([0, (int)$workspace], Connection::PARAM_INT_ARRAY)
4425  )
4426  );
4427  }
4428 
4429  $rows = $queryBuilder->execute()->fetchAll();
4430 
4431  // Add rows to output array:
4432  if (is_array($rows)) {
4433  $outputRows = array_merge($outputRows, $rows);
4434  }
4435  // Set real-pid:
4436  foreach ($outputRows as $idx => $oRow) {
4437  $outputRows[$idx]['_REAL_PID'] = $realPid;
4438  }
4439  return $outputRows;
4440  }
4441  return null;
4442  }
4443 
4461  public static function fixVersioningPid($table, &$rr, $ignoreWorkspaceMatch = false)
4462  {
4463  if (!ExtensionManagementUtility::isLoaded('version')) {
4464  return;
4465  }
4466  // Check that the input record is an offline version from a table that supports versioning:
4467  if (is_array($rr) && $rr['pid'] == -1 && static::isTableWorkspaceEnabled($table)) {
4468  // Check values for t3ver_oid and t3ver_wsid:
4469  if (isset($rr['t3ver_oid']) && isset($rr['t3ver_wsid'])) {
4470  // If "t3ver_oid" is already a field, just set this:
4471  $oid = $rr['t3ver_oid'];
4472  $wsid = $rr['t3ver_wsid'];
4473  } else {
4474  $oid = 0;
4475  $wsid = 0;
4476  // Otherwise we have to expect "uid" to be in the record and look up based on this:
4477  $newPidRec = self::getRecord($table, $rr['uid'], 't3ver_oid,t3ver_wsid');
4478  if (is_array($newPidRec)) {
4479  $oid = $newPidRec['t3ver_oid'];
4480  $wsid = $newPidRec['t3ver_wsid'];
4481  }
4482  }
4483  // If ID of current online version is found, look up the PID value of that:
4484  if ($oid
4485  && ($ignoreWorkspaceMatch || (int)$wsid === (int)static::getBackendUserAuthentication()->workspace)
4486  ) {
4487  $oidRec = self::getRecord($table, $oid, 'pid');
4488  if (is_array($oidRec)) {
4489  $rr['_ORIG_pid'] = $rr['pid'];
4490  $rr['pid'] = $oidRec['pid'];
4491  }
4492  // Use target PID in case of move pointer
4493  if (
4494  !isset($rr['t3ver_state'])
4495  || VersionState::cast($rr['t3ver_state'])->equals(VersionState::MOVE_POINTER)
4496  ) {
4497  $movePlaceholder = self::getMovePlaceholder($table, $oid, 'pid');
4498  if ($movePlaceholder) {
4499  $rr['_ORIG_pid'] = $rr['pid'];
4500  $rr['pid'] = $movePlaceholder['pid'];
4501  }
4502  }
4503  }
4504  }
4505  }
4506 
4523  public static function workspaceOL($table, &$row, $wsid = -99, $unsetMovePointers = false)
4524  {
4525  if (!ExtensionManagementUtility::isLoaded('version')) {
4526  return;
4527  }
4528  // If this is FALSE the placeholder is shown raw in the backend.
4529  // I don't know if this move can be useful for users to toggle. Technically it can help debugging.
4530  $previewMovePlaceholders = true;
4531  // Initialize workspace ID
4532  if ($wsid == -99) {
4533  $wsid = static::getBackendUserAuthentication()->workspace;
4534  }
4535  // Check if workspace is different from zero and record is set:
4536  if ($wsid !== 0 && is_array($row)) {
4537  // Check if input record is a move-placeholder and if so, find the pointed-to live record:
4538  $movePldSwap = null;
4539  $orig_uid = 0;
4540  $orig_pid = 0;
4541  if ($previewMovePlaceholders) {
4542  $orig_uid = $row['uid'];
4543  $orig_pid = $row['pid'];
4544  $movePldSwap = self::movePlhOL($table, $row);
4545  }
4546  $wsAlt = self::getWorkspaceVersionOfRecord(
4547  $wsid,
4548  $table,
4549  $row['uid'],
4550  implode(',', static::purgeComputedPropertyNames(array_keys($row)))
4551  );
4552  // If version was found, swap the default record with that one.
4553  if (is_array($wsAlt)) {
4554  // Check if this is in move-state:
4555  if ($previewMovePlaceholders && !$movePldSwap && static::isTableWorkspaceEnabled($table) && $unsetMovePointers) {
4556  // Only for WS ver 2... (moving)
4557  // If t3ver_state is not found, then find it... (but we like best if it is here...)
4558  if (!isset($wsAlt['t3ver_state'])) {
4559  $stateRec = self::getRecord($table, $wsAlt['uid'], 't3ver_state');
4560  $versionState = VersionState::cast($stateRec['t3ver_state']);
4561  } else {
4562  $versionState = VersionState::cast($wsAlt['t3ver_state']);
4563  }
4564  if ($versionState->equals(VersionState::MOVE_POINTER)) {
4565  // @todo Same problem as frontend in versionOL(). See TODO point there.
4566  $row = false;
4567  return;
4568  }
4569  }
4570  // Always correct PID from -1 to what it should be
4571  if (isset($wsAlt['pid'])) {
4572  // Keep the old (-1) - indicates it was a version.
4573  $wsAlt['_ORIG_pid'] = $wsAlt['pid'];
4574  // Set in the online versions PID.
4575  $wsAlt['pid'] = $row['pid'];
4576  }
4577  // For versions of single elements or page+content, swap UID and PID
4578  $wsAlt['_ORIG_uid'] = $wsAlt['uid'];
4579  $wsAlt['uid'] = $row['uid'];
4580  // Backend css class:
4581  $wsAlt['_CSSCLASS'] = 'ver-element';
4582  // Changing input record to the workspace version alternative:
4583  $row = $wsAlt;
4584  }
4585  // If the original record was a move placeholder, the uid and pid of that is preserved here:
4586  if ($movePldSwap) {
4587  $row['_MOVE_PLH'] = true;
4588  $row['_MOVE_PLH_uid'] = $orig_uid;
4589  $row['_MOVE_PLH_pid'] = $orig_pid;
4590  // For display; To make the icon right for the placeholder vs. the original
4591  $row['t3ver_state'] = (string)new VersionState(VersionState::MOVE_PLACEHOLDER);
4592  }
4593  }
4594  }
4595 
4605  public static function movePlhOL($table, &$row)
4606  {
4607  if (static::isTableWorkspaceEnabled($table)) {
4608  // If t3ver_move_id or t3ver_state is not found, then find it... (but we like best if it is here...)
4609  if (!isset($row['t3ver_move_id']) || !isset($row['t3ver_state'])) {
4610  $moveIDRec = self::getRecord($table, $row['uid'], 't3ver_move_id, t3ver_state');
4611  $moveID = $moveIDRec['t3ver_move_id'];
4612  $versionState = VersionState::cast($moveIDRec['t3ver_state']);
4613  } else {
4614  $moveID = $row['t3ver_move_id'];
4615  $versionState = VersionState::cast($row['t3ver_state']);
4616  }
4617  // Find pointed-to record.
4618  if ($versionState->equals(VersionState::MOVE_PLACEHOLDER) && $moveID) {
4619  if ($origRow = self::getRecord(
4620  $table,
4621  $moveID,
4622  implode(',', static::purgeComputedPropertyNames(array_keys($row)))
4623  )) {
4624  $row = $origRow;
4625  return true;
4626  }
4627  }
4628  }
4629  return false;
4630  }
4631 
4641  public static function getWorkspaceVersionOfRecord($workspace, $table, $uid, $fields = '*')
4642  {
4643  if (ExtensionManagementUtility::isLoaded('version')) {
4644  if ($workspace !== 0 && $GLOBALS['TCA'][$table] && self::isTableWorkspaceEnabled($table)) {
4645 
4646  // Select workspace version of record:
4647  $queryBuilder = static::getQueryBuilderForTable($table);
4648  $queryBuilder->getRestrictions()
4649  ->removeAll()
4650  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
4651 
4652  // build fields to select
4653  $queryBuilder->select(...GeneralUtility::trimExplode(',', $fields));
4654 
4655  $row = $queryBuilder
4656  ->from($table)
4657  ->where(
4658  $queryBuilder->expr()->eq(
4659  'pid',
4660  $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
4661  ),
4662  $queryBuilder->expr()->eq(
4663  't3ver_oid',
4664  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
4665  ),
4666  $queryBuilder->expr()->eq(
4667  't3ver_wsid',
4668  $queryBuilder->createNamedParameter($workspace, \PDO::PARAM_INT)
4669  )
4670  )
4671  ->execute()
4672  ->fetch();
4673 
4674  return $row;
4675  }
4676  }
4677  return false;
4678  }
4679 
4688  public static function getLiveVersionOfRecord($table, $uid, $fields = '*')
4689  {
4690  $liveVersionId = self::getLiveVersionIdOfRecord($table, $uid);
4691  if ($liveVersionId !== null) {
4692  return self::getRecord($table, $liveVersionId, $fields);
4693  }
4694  return null;
4695  }
4696 
4704  public static function getLiveVersionIdOfRecord($table, $uid)
4705  {
4706  if (!ExtensionManagementUtility::isLoaded('version')) {
4707  return null;
4708  }
4709  $liveVersionId = null;
4710  if (self::isTableWorkspaceEnabled($table)) {
4711  $currentRecord = self::getRecord($table, $uid, 'pid,t3ver_oid');
4712  if (is_array($currentRecord) && $currentRecord['pid'] == -1) {
4713  $liveVersionId = $currentRecord['t3ver_oid'];
4714  }
4715  }
4716  return $liveVersionId;
4717  }
4718 
4726  public static function versioningPlaceholderClause($table)
4727  {
4728  if (static::isTableWorkspaceEnabled($table)) {
4729  $currentWorkspace = (int)static::getBackendUserAuthentication()->workspace;
4730  return ' AND (' . $table . '.t3ver_state <= ' . new VersionState(VersionState::DEFAULT_STATE) . ' OR ' . $table . '.t3ver_wsid = ' . $currentWorkspace . ')';
4731  }
4732  return '';
4733  }
4734 
4742  public static function getWorkspaceWhereClause($table, $workspaceId = null)
4743  {
4744  $whereClause = '';
4745  if (self::isTableWorkspaceEnabled($table)) {
4746  if (is_null($workspaceId)) {
4747  $workspaceId = static::getBackendUserAuthentication()->workspace;
4748  }
4749  $workspaceId = (int)$workspaceId;
4750  $pidOperator = $workspaceId === 0 ? '!=' : '=';
4751  $whereClause = ' AND ' . $table . '.t3ver_wsid=' . $workspaceId . ' AND ' . $table . '.pid' . $pidOperator . '-1';
4752  }
4753  return $whereClause;
4754  }
4755 
4763  public static function wsMapId($table, $uid)
4764  {
4765  $wsRec = self::getWorkspaceVersionOfRecord(
4766  static::getBackendUserAuthentication()->workspace,
4767  $table,
4768  $uid,
4769  'uid'
4770  );
4771  return is_array($wsRec) ? $wsRec['uid'] : $uid;
4772  }
4773 
4783  public static function getMovePlaceholder($table, $uid, $fields = '*', $workspace = null)
4784  {
4785  if ($workspace === null) {
4786  $workspace = static::getBackendUserAuthentication()->workspace;
4787  }
4788  if ((int)$workspace !== 0 && $GLOBALS['TCA'][$table] && static::isTableWorkspaceEnabled($table)) {
4789  // Select workspace version of record:
4790  $queryBuilder = static::getQueryBuilderForTable($table);
4791  $queryBuilder->getRestrictions()
4792  ->removeAll()
4793  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
4794 
4795  $row = $queryBuilder
4796  ->select(...GeneralUtility::trimExplode(',', $fields, true))
4797  ->from($table)
4798  ->where(
4799  $queryBuilder->expr()->neq(
4800  'pid',
4801  $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
4802  ),
4803  $queryBuilder->expr()->eq(
4804  't3ver_state',
4805  $queryBuilder->createNamedParameter(
4807  \PDO::PARAM_INT
4808  )
4809  ),
4810  $queryBuilder->expr()->eq(
4811  't3ver_move_id',
4812  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
4813  ),
4814  $queryBuilder->expr()->eq(
4815  't3ver_wsid',
4816  $queryBuilder->createNamedParameter($workspace, \PDO::PARAM_INT)
4817  )
4818  )
4819  ->execute()
4820  ->fetch();
4821 
4822  return $row ?: false;
4823  }
4824  return false;
4825  }
4826 
4827  /*******************************************
4828  *
4829  * Miscellaneous
4830  *
4831  *******************************************/
4842  public static function TYPO3_copyRightNotice()
4843  {
4844  // Copyright Notice
4845  $loginCopyrightWarrantyProvider = strip_tags(trim($GLOBALS['TYPO3_CONF_VARS']['SYS']['loginCopyrightWarrantyProvider']));
4846  $loginCopyrightWarrantyURL = strip_tags(trim($GLOBALS['TYPO3_CONF_VARS']['SYS']['loginCopyrightWarrantyURL']));
4847 
4848  $lang = static::getLanguageService();
4849 
4850  if (strlen($loginCopyrightWarrantyProvider) >= 2 && strlen($loginCopyrightWarrantyURL) >= 10) {
4851  $warrantyNote = sprintf(
4852  $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_login.xlf:warranty.by'),
4853  htmlspecialchars($loginCopyrightWarrantyProvider),
4854  '<a href="' . htmlspecialchars($loginCopyrightWarrantyURL) . '" target="_blank" rel="noopener noreferrer">',
4855  '</a>'
4856  );
4857  } else {
4858  $warrantyNote = sprintf(
4859  $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_login.xlf:no.warranty'),
4860  '<a href="' . TYPO3_URL_LICENSE . '" target="_blank" rel="noopener noreferrer">',
4861  '</a>'
4862  );
4863  }
4864  $cNotice = '<a href="' . TYPO3_URL_GENERAL . '" target="_blank" rel="noopener noreferrer">' .
4865  $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_login.xlf:typo3.cms') . '</a>. ' .
4866  $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_login.xlf:copyright') . ' &copy; '
4867  . htmlspecialchars(TYPO3_copyright_year) . ' Kasper Sk&aring;rh&oslash;j. ' .
4868  $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_login.xlf:extension.copyright') . ' ' .
4869  sprintf(
4870  $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_login.xlf:details.link'),
4871  ('<a href="' . TYPO3_URL_GENERAL . '" target="_blank" rel="noopener noreferrer">' . TYPO3_URL_GENERAL . '</a>')
4872  ) . ' ' .
4873  strip_tags($warrantyNote, '<a>') . ' ' .
4874  sprintf(
4875  $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_login.xlf:free.software'),
4876  ('<a href="' . TYPO3_URL_LICENSE . '" target="_blank" rel="noopener noreferrer">'),
4877  '</a> '
4878  )
4879  . $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_login.xlf:keep.notice');
4880  return $cNotice;
4881  }
4882 
4890  public static function ADMCMD_previewCmds($pageInfo)
4891  {
4892  $simUser = '';
4893  $simTime = '';
4894  if ($pageInfo['fe_group'] > 0) {
4895  $simUser = '&ADMCMD_simUser=' . $pageInfo['fe_group'];
4896  } elseif ((int)$pageInfo['fe_group'] === -2) {
4897  $tableNameFeGroup = 'fe_groups';
4898 
4899  // -2 means "show at any login". We simulate first available fe_group.
4901  $sysPage = GeneralUtility::makeInstance(PageRepository::class);
4902 
4903  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
4904  ->getQueryBuilderForTable($tableNameFeGroup);
4905  $queryBuilder->getRestrictions()->removeAll();
4906 
4907  $activeFeGroupRow = $queryBuilder->select('uid')
4908  ->from($tableNameFeGroup)
4909  ->where(
4910  QueryHelper::stripLogicalOperatorPrefix('1=1' . $sysPage->enableFields($tableNameFeGroup))
4911  )
4912  ->execute()
4913  ->fetch();
4914 
4915  if (!empty($activeFeGroupRow)) {
4916  $simUser = '&ADMCMD_simUser=' . $activeFeGroupRow['uid'];
4917  }
4918  }
4919  if ($pageInfo['starttime'] > $GLOBALS['EXEC_TIME']) {
4920  $adjustedSimTime = $pageInfo['starttime'] + date('Z', $pageInfo['starttime']);
4921  $simTime = '&ADMCMD_simTime=' . $adjustedSimTime;
4922  }
4923  if ($pageInfo['endtime'] < $GLOBALS['EXEC_TIME'] && $pageInfo['endtime'] != 0) {
4924  $adjustedSimTime = $pageInfo['endtime'] + date('Z', $pageInfo['endtime']);
4925  $simTime = '&ADMCMD_simTime=' . ($adjustedSimTime - 1);
4926  }
4927  return $simUser . $simTime;
4928  }
4929 
4939  public static function processParams($params)
4940  {
4942  $paramArr = [];
4943  $lines = explode(LF, $params);
4944  foreach ($lines as $val) {
4945  $val = trim($val);
4946  if ($val) {
4947  $pair = explode('=', $val, 2);
4948  $paramArr[trim($pair[0])] = trim($pair[1]);
4949  }
4950  }
4951  return $paramArr;
4952  }
4953 
4960  public static function getBackendScript($interface = '')
4961  {
4962  if (!$interface) {
4963  $interface = static::getBackendUserAuthentication()->uc['interfaceSetup'];
4964  }
4965  switch ($interface) {
4966  case 'frontend':
4967  $script = '../.';
4968  break;
4969  case 'backend':
4970  default:
4971  $script = self::getModuleUrl('main');
4972  }
4973  return $script;
4974  }
4975 
4982  public static function isTableWorkspaceEnabled($table)
4983  {
4984  return !empty($GLOBALS['TCA'][$table]['ctrl']['versioningWS']);
4985  }
4986 
4994  public static function getTcaFieldConfiguration($table, $field)
4995  {
4996  $configuration = [];
4997  if (isset($GLOBALS['TCA'][$table]['columns'][$field]['config'])) {
4998  $configuration = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
4999  }
5000  return $configuration;
5001  }
5002 
5011  public static function isWebMountRestrictionIgnored($table)
5012  {
5013  return !empty($GLOBALS['TCA'][$table]['ctrl']['security']['ignoreWebMountRestriction']);
5014  }
5015 
5024  public static function isRootLevelRestrictionIgnored($table)
5025  {
5026  return !empty($GLOBALS['TCA'][$table]['ctrl']['security']['ignoreRootLevelRestriction']);
5027  }
5028 
5036  public static function shortcutExists($url)
5037  {
5038  $queryBuilder = static::getQueryBuilderForTable('sys_be_shortcuts');
5039  $queryBuilder->getRestrictions()->removeAll();
5040 
5041  $count = $queryBuilder
5042  ->count('uid')
5043  ->from('sys_be_shortcuts')
5044  ->where(
5045  $queryBuilder->expr()->eq(
5046  'userid',
5047  $queryBuilder->createNamedParameter(
5048  self::getBackendUserAuthentication()->user['uid'],
5049  \PDO::PARAM_INT
5050  )
5051  ),
5052  $queryBuilder->expr()->eq('url', $queryBuilder->createNamedParameter($url, \PDO::PARAM_STR))
5053  )
5054  ->execute()
5055  ->fetchColumn(0);
5056 
5057  return (bool)$count;
5058  }
5059 
5065  protected static function getSignalSlotDispatcher()
5066  {
5067  return GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class);
5068  }
5069 
5079  protected static function emitGetPagesTSconfigPreIncludeSignal(
5080  array $TSdataArray,
5081  $id,
5082  array $rootLine,
5083  $returnPartArray
5084  ) {
5085  $signalArguments = static::getSignalSlotDispatcher()->dispatch(
5086  __CLASS__,
5087  'getPagesTSconfigPreInclude',
5088  [$TSdataArray, $id, $rootLine, $returnPartArray]
5089  );
5090  return $signalArguments[0];
5091  }
5092 
5097  protected static function getConnectionForTable($table)
5098  {
5099  return GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
5100  }
5101 
5106  protected static function getQueryBuilderForTable($table)
5107  {
5108  return GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
5109  }
5110 
5114  protected static function getLanguageService()
5115  {
5116  return $GLOBALS['LANG'];
5117  }
5118 
5122  protected static function getBackendUserAuthentication()
5123  {
5124  return $GLOBALS['BE_USER'];
5125  }
5126 
5130  protected static function getDocumentTemplate()
5131  {
5132  return $GLOBALS['TBE_TEMPLATE'];
5133  }
5134 }
static translationCount($table, $ref, $msg='')
static getTSconfig_pidValue($table, $uid, $pid)
static getPagesTSconfig($id, $rootLine=null, $returnPartArray=false)
static getRecordWSOL( $table, $uid, $fields=' *', $where='', $useDeleteClause=true, $unsetMovePointers=false)
static getRecordsByField( $theTable, $theField, $theValue, $whereClause='', $groupBy='', $orderBy='', $limit='', $useDeleteClause=true, $queryBuilder=null)
static getWorkspaceWhereClause($table, $workspaceId=null)
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
static readPageAccess($id, $perms_clause)
static getWorkspaceVersionOfRecord($workspace, $table, $uid, $fields=' *')
static createPreviewUrl($pageUid, $rootLine, $anchorSection, $additionalGetVars, $viewScript)
static implodeAttributes(array $arr, $xhtmlSafe=false, $dontOmitBlankAttribs=false)
static wrapClickMenuOnIcon( $content, $table, $uid=0, $context='', $_addParams='', $_enDisItems='', $returnTagParameters=false)
static editOnClick($params, $_='', $requestUri='')
static isFirstPartOfStr($str, $partStr)
static getProcessedValueExtra( $table, $fN, $fV, $fixed_lgd_chars=0, $uid=0, $forceResult=true, $pid=0)
static callUserFunction($funcName, &$params, &$ref, $_='', $errorMode=0)
static openPageTree($pid, $clearExpansion)
static getModuleData( $MOD_MENU, $CHANGED_SETTINGS, $modName, $type='', $dontValidateList='', $setDefaultList='')
static blindUserNames($usernames, $groupArray, $excludeBlindedFlag=false)
static BEenableFields($table, $inv=false)
static getSpecConfParts($defaultExtrasString)
static getRecordsSortedByTitle(array $fields, $table, $titleField, $where='')
static viewOnClick( $pageUid, $backPath='', $rootLine=null, $anchorSection='', $alternativeUrl='', $additionalGetVars='', $switchFocus=true)
static calcAge($seconds, $labels='min|hrs|days|yrs|min|hour|day|year')
static getRecordToolTip(array $row, $table='pages')
static getCommonSelectFields($table, $prefix='', $fields=[])
static purgeComputedPropertiesFromRecord(array $record)
static BEgetRootLine($uid, $clause='', $workspaceOL=false)
static hmac($input, $additionalSecret='')
static getTCAtypes($table, $rec, $useFieldNameAsKey=false)
static getFileAbsFileName($filename, $_=null, $_2=null)
static purgeComputedPropertyNames(array $propertyNames)
static getGroupNames($fields='title, uid', $where='')
static getViewDomain($pageId, $rootLine=null)
static lockRecords($table='', $uid=0, $pid=0)
static getFlexFormDS($conf, $row, $table, $fieldName='', $WSOL=true, $newRecordPidValue=0)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static workspaceOL($table, &$row, $wsid=-99, $unsetMovePointers=false)
static makeInstance($className,... $constructorArguments)
static time($value, $withSeconds=true)
$fields
Definition: pages.php:4
static selectVersionsOfRecord( $table, $uid, $fields=' *', $workspace=0, $includeDeletedRecords=false, $row=null)
static getInlineLocalizationMode($table, $fieldOrConfig)
static create(string $jsonString, array $tcaConfig=[])
static fixVersioningPid($table, &$rr, $ignoreWorkspaceMatch=false)
static implodeArrayForUrl($name, array $theArray, $str='', $skipBlank=false, $rawurlencodeParamName=false)
static getDomainStartPage($domain, $path='')
static emitGetPagesTSconfigPreIncludeSignal(array $TSdataArray, $id, array $rootLine, $returnPartArray)
static blindGroupNames($groups, $groupArray, $excludeBlindedFlag=false)
static getUserNames($fields='username, usergroup, usergroup_cached_list, uid', $where='')
static getRecordLocalization($table, $uid, $language, $andWhereClause='')
static getFuncInput( $mainParams, $elementName, $currentValue, $size=10, $script='', $addParams='')
static makeConfigForm($configArray, $defaults, $dataPrefix)
static setUpdateSignal($set='', $params='')
static referenceCount($table, $ref, $msg='', $count=null)
static cshItem($table, $field, $_='', $wrap='')
static getRecordTitle($table, $row, $prep=false, $forceResult=true)
static getRecordRaw($table, $where='', $fields=' *')
static getTcaFieldConfiguration($table, $field)
static getTSCpidCached($table, $uid, $pid)
static RTEsetup($RTEprop, $table, $field, $type='')
static getFuncCheck( $mainParams, $elementName, $currentValue, $script='', $addParams='', $tagParams='')
static unsetMenuItems($modTSconfig, $itemArray, $TSref)
static mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys=true, $includeEmptyValues=true, $enableUnsetFeature=true)
static getSQLselectableList($in_list, $tablename, $default_tablename)
static xml2array($string, $NSprefix='', $reportDocTag=false)
static getRecordIconAltText($row, $table='pages')
static stripLogicalOperatorPrefix(string $constraint)
static fixed_lgd_cs($string, $chars, $appendString='...')
static getPageForRootline($uid, $clause, $workspaceOL)
static getListGroupNames($fields='title, uid')
static getRecordPath($uid, $clause, $titleLimit, $fullTitleLimit=0)
static getRecordTitlePrep($title, $titleLength=0)
static getDropdownMenu( $mainParams, $elementName, $currentValue, $menuItems, $script='', $addParams='')
static getLabelFromItemListMerged($pageId, $table, $column, $key)
static getPidForModTSconfig($table, $uid, $pid)
static buildScriptUrl($mainParams, $addParams, $script='')
static getLiveVersionOfRecord($table, $uid, $fields=' *')
static getMovePlaceholder($table, $uid, $fields=' *', $workspace=null)
static titleAttribForPages($row, $perms_clause='', $includeAttrib=true)
static getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static getLinkToDataHandlerAction($parameters, $redirectUrl='')
static getFuncMenu( $mainParams, $elementName, $currentValue, $menuItems, $script='', $addParams='')
static dateTimeAge($tstamp, $prefix=1, $date='')
static sortArraysByKey(array $arrays, $key, $ascending=true)
static deleteClause($table, $tableAlias='')
static replaceL10nModeFields($table, array $row)
static getLabelFromItemlist($table, $col, $key)
static uniqueList($in_list, $secondParameter=null)
static getLabelsFromItemsList($table, $column, $keyList, array $columnTsConfig=[])