4682295424b5040c5973c49d37df829b71661c17
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Utility / BackendUtility.php
1 <?php
2 namespace TYPO3\CMS\Backend\Utility;
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
17 use TYPO3\CMS\Backend\Routing\UriBuilder;
18 use TYPO3\CMS\Backend\Template\DocumentTemplate;
19 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
20 use TYPO3\CMS\Core\Cache\CacheManager;
21 use TYPO3\CMS\Core\Cache\Frontend\VariableFrontend;
22 use TYPO3\CMS\Core\Database\Connection;
23 use TYPO3\CMS\Core\Database\ConnectionPool;
24 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
25 use TYPO3\CMS\Core\Database\Query\QueryHelper;
26 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
27 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
28 use TYPO3\CMS\Core\Database\RelationHandler;
29 use TYPO3\CMS\Core\Imaging\Icon;
30 use TYPO3\CMS\Core\Imaging\IconFactory;
31 use TYPO3\CMS\Core\Resource\AbstractFile;
32 use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
33 use TYPO3\CMS\Core\Resource\File;
34 use TYPO3\CMS\Core\Resource\ProcessedFile;
35 use TYPO3\CMS\Core\Resource\ResourceFactory;
36 use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
37 use TYPO3\CMS\Core\Utility\ArrayUtility;
38 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
39 use TYPO3\CMS\Core\Utility\GeneralUtility;
40 use TYPO3\CMS\Core\Utility\MathUtility;
41 use TYPO3\CMS\Core\Utility\PathUtility;
42 use TYPO3\CMS\Core\Versioning\VersionState;
43 use TYPO3\CMS\Frontend\Page\PageRepository;
44 use TYPO3\CMS\Lang\LanguageService;
45
46 /**
47 * Standard functions available for the TYPO3 backend.
48 * You are encouraged to use this class in your own applications (Backend Modules)
49 * Don't instantiate - call functions with "\TYPO3\CMS\Backend\Utility\BackendUtility::" prefixed the function name.
50 *
51 * Call ALL methods without making an object!
52 * Eg. to get a page-record 51 do this: '\TYPO3\CMS\Backend\Utility\BackendUtility::getRecord('pages',51)'
53 */
54 class BackendUtility
55 {
56 /**
57 * Cache the TCA configuration of tables with their types during runtime
58 *
59 * @var array
60 * @see self::getTCAtypes()
61 */
62 protected static $tcaTableTypeConfigurationCache = [];
63
64 /*******************************************
65 *
66 * SQL-related, selecting records, searching
67 *
68 *******************************************/
69 /**
70 * Returns the WHERE clause " AND NOT [tablename].[deleted-field]" if a deleted-field
71 * is configured in $GLOBALS['TCA'] for the tablename, $table
72 * This function should ALWAYS be called in the backend for selection on tables which
73 * are configured in $GLOBALS['TCA'] since it will ensure consistent selection of records,
74 * even if they are marked deleted (in which case the system must always treat them as non-existent!)
75 * In the frontend a function, ->enableFields(), is known to filter hidden-field, start- and endtime
76 * and fe_groups as well. But that is a job of the frontend, not the backend. If you need filtering
77 * on those fields as well in the backend you can use ->BEenableFields() though.
78 *
79 * @param string $table Table name present in $GLOBALS['TCA']
80 * @param string $tableAlias Table alias if any
81 * @return string WHERE clause for filtering out deleted records, eg " AND tablename.deleted=0
82 */
83 public static function deleteClause($table, $tableAlias = '')
84 {
85 if (empty($GLOBALS['TCA'][$table]['ctrl']['delete'])) {
86 return '';
87 }
88 $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
89 ->getQueryBuilderForTable($table)
90 ->expr();
91 return ' AND ' . $expressionBuilder->eq(
92 ($tableAlias ?: $table) . '.' . $GLOBALS['TCA'][$table]['ctrl']['delete'],
93 0
94 );
95 }
96
97 /**
98 * Gets record with uid = $uid from $table
99 * You can set $field to a list of fields (default is '*')
100 * Additional WHERE clauses can be added by $where (fx. ' AND blabla = 1')
101 * Will automatically check if records has been deleted and if so, not return anything.
102 * $table must be found in $GLOBALS['TCA']
103 *
104 * @param string $table Table name present in $GLOBALS['TCA']
105 * @param int $uid UID of record
106 * @param string $fields List of fields to select
107 * @param string $where Additional WHERE clause, eg. " AND blablabla = 0
108 * @param bool $useDeleteClause Use the deleteClause to check if a record is deleted (default TRUE)
109 * @return array|NULL Returns the row if found, otherwise NULL
110 */
111 public static function getRecord($table, $uid, $fields = '*', $where = '', $useDeleteClause = true)
112 {
113 // Ensure we have a valid uid (not 0 and not NEWxxxx) and a valid TCA
114 if ((int)$uid && !empty($GLOBALS['TCA'][$table])) {
115 $queryBuilder = static::getQueryBuilderForTable($table);
116
117 // do not use enabled fields here
118 $queryBuilder->getRestrictions()->removeAll();
119
120 // should the delete clause be used
121 if ($useDeleteClause) {
122 $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
123 }
124
125 // set table and where clause
126 $queryBuilder
127 ->select(...GeneralUtility::trimExplode(',', $fields, true))
128 ->from($table)
129 ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)));
130
131 // add custom where clause
132 if ($where) {
133 $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($where));
134 }
135
136 $row = $queryBuilder->execute()->fetch();
137 if ($row) {
138 return $row;
139 }
140 }
141 return null;
142 }
143
144 /**
145 * Like getRecord(), but overlays workspace version if any.
146 *
147 * @param string $table Table name present in $GLOBALS['TCA']
148 * @param int $uid UID of record
149 * @param string $fields List of fields to select
150 * @param string $where Additional WHERE clause, eg. " AND blablabla = 0
151 * @param bool $useDeleteClause Use the deleteClause to check if a record is deleted (default TRUE)
152 * @param bool $unsetMovePointers If TRUE the function does not return a "pointer" row for moved records in a workspace
153 * @return array Returns the row if found, otherwise nothing
154 */
155 public static function getRecordWSOL(
156 $table,
157 $uid,
158 $fields = '*',
159 $where = '',
160 $useDeleteClause = true,
161 $unsetMovePointers = false
162 ) {
163 if ($fields !== '*') {
164 $internalFields = GeneralUtility::uniqueList($fields . ',uid,pid');
165 $row = self::getRecord($table, $uid, $internalFields, $where, $useDeleteClause);
166 self::workspaceOL($table, $row, -99, $unsetMovePointers);
167 if (is_array($row)) {
168 foreach ($row as $key => $_) {
169 if (!GeneralUtility::inList($fields, $key) && $key[0] !== '_') {
170 unset($row[$key]);
171 }
172 }
173 }
174 } else {
175 $row = self::getRecord($table, $uid, $fields, $where, $useDeleteClause);
176 self::workspaceOL($table, $row, -99, $unsetMovePointers);
177 }
178 return $row;
179 }
180
181 /**
182 * Returns the first record found from $table with $where as WHERE clause
183 * This function does NOT check if a record has the deleted flag set.
184 * $table does NOT need to be configured in $GLOBALS['TCA']
185 * The query used is simply this:
186 * $query = 'SELECT ' . $fields . ' FROM ' . $table . ' WHERE ' . $where;
187 *
188 * @param string $table Table name (not necessarily in TCA)
189 * @param string $where WHERE clause
190 * @param string $fields $fields is a list of fields to select, default is '*'
191 * @return array|bool First row found, if any, FALSE otherwise
192 */
193 public static function getRecordRaw($table, $where = '', $fields = '*')
194 {
195 $queryBuilder = static::getQueryBuilderForTable($table);
196 $queryBuilder->getRestrictions()->removeAll();
197
198 $row = $queryBuilder
199 ->select(...GeneralUtility::trimExplode(',', $fields, true))
200 ->from($table)
201 ->where(QueryHelper::stripLogicalOperatorPrefix($where))
202 ->execute()
203 ->fetch();
204
205 return $row ?: false;
206 }
207
208 /**
209 * Returns records from table, $theTable, where a field ($theField) equals the value, $theValue
210 * The records are returned in an array
211 * If no records were selected, the function returns nothing
212 *
213 * @param string $theTable Table name present in $GLOBALS['TCA']
214 * @param string $theField Field to select on
215 * @param string $theValue Value that $theField must match
216 * @param string $whereClause Optional additional WHERE clauses put in the end of the query. DO NOT PUT IN GROUP BY, ORDER BY or LIMIT!
217 * @param string $groupBy Optional GROUP BY field(s), if none, supply blank string.
218 * @param string $orderBy Optional ORDER BY field(s), if none, supply blank string.
219 * @param string $limit Optional LIMIT value ([begin,]max), if none, supply blank string.
220 * @param bool $useDeleteClause Use the deleteClause to check if a record is deleted (default TRUE)
221 * @param null|QueryBuilder $queryBuilder The queryBuilder must be provided, if the parameter $whereClause is given and the concept of prepared statement was used. Example within self::firstDomainRecord()
222 * @return mixed Multidimensional array with selected records (if any is selected)
223 */
224 public static function getRecordsByField(
225 $theTable,
226 $theField,
227 $theValue,
228 $whereClause = '',
229 $groupBy = '',
230 $orderBy = '',
231 $limit = '',
232 $useDeleteClause = true,
233 $queryBuilder = null
234 ) {
235 if (is_array($GLOBALS['TCA'][$theTable])) {
236 if (null === $queryBuilder) {
237 $queryBuilder = static::getQueryBuilderForTable($theTable);
238 }
239
240 // Show all records except versioning placeholders
241 $queryBuilder->getRestrictions()
242 ->removeAll()
243 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
244
245 // Remove deleted records from the query result
246 if ($useDeleteClause) {
247 $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
248 }
249
250 // build fields to select
251 $queryBuilder
252 ->select('*')
253 ->from($theTable)
254 ->where($queryBuilder->expr()->eq($theField, $queryBuilder->createNamedParameter($theValue)));
255
256 // additional where
257 if ($whereClause) {
258 $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($whereClause));
259 }
260
261 // group by
262 if ($groupBy !== '') {
263 $queryBuilder->groupBy(QueryHelper::parseGroupBy($groupBy));
264 }
265
266 // order by
267 if ($orderBy !== '') {
268 foreach (QueryHelper::parseOrderBy($orderBy) as $orderPair) {
269 list($fieldName, $order) = $orderPair;
270 $queryBuilder->addOrderBy($fieldName, $order);
271 }
272 }
273
274 // limit
275 if ($limit !== '') {
276 if (strpos($limit, ',')) {
277 $limitOffsetAndMax = GeneralUtility::intExplode(',', $limit);
278 $queryBuilder->setFirstResult((int)$limitOffsetAndMax[0]);
279 $queryBuilder->setMaxResults((int)$limitOffsetAndMax[1]);
280 } else {
281 $queryBuilder->setMaxResults((int)$limit);
282 }
283 }
284
285 $rows = $queryBuilder->execute()->fetchAll();
286 return $rows;
287 }
288 return null;
289 }
290
291 /**
292 * Makes an backwards explode on the $str and returns an array with ($table, $uid).
293 * Example: tt_content_45 => array('tt_content', 45)
294 *
295 * @param string $str [tablename]_[uid] string to explode
296 * @return array
297 */
298 public static function splitTable_Uid($str)
299 {
300 list($uid, $table) = explode('_', strrev($str), 2);
301 return [strrev($table), strrev($uid)];
302 }
303
304 /**
305 * Returns a list of pure ints based on $in_list being a list of records with table-names prepended.
306 * Ex: $in_list = "pages_4,tt_content_12,45" would result in a return value of "4,45" if $tablename is "pages" and $default_tablename is 'pages' as well.
307 *
308 * @param string $in_list Input list
309 * @param string $tablename Table name from which ids is returned
310 * @param string $default_tablename $default_tablename denotes what table the number '45' is from (if nothing is prepended on the value)
311 * @return string List of ids
312 * @deprecated since TYPO3 CMS 8, will be removed in TYPO3 CMS 9.
313 */
314 public static function getSQLselectableList($in_list, $tablename, $default_tablename)
315 {
316 GeneralUtility::logDeprecatedFunction();
317 $list = [];
318 if ((string)trim($in_list) != '') {
319 $tempItemArray = explode(',', trim($in_list));
320 foreach ($tempItemArray as $key => $val) {
321 $val = strrev($val);
322 $parts = explode('_', $val, 2);
323 if ((string)trim($parts[0]) != '') {
324 $theID = (int)strrev($parts[0]);
325 $theTable = trim($parts[1]) ? strrev(trim($parts[1])) : $default_tablename;
326 if ($theTable == $tablename) {
327 $list[] = $theID;
328 }
329 }
330 }
331 }
332 return implode(',', $list);
333 }
334
335 /**
336 * Backend implementation of enableFields()
337 * Notice that "fe_groups" is not selected for - only disabled, starttime and endtime.
338 * Notice that deleted-fields are NOT filtered - you must ALSO call deleteClause in addition.
339 * $GLOBALS["SIM_ACCESS_TIME"] is used for date.
340 *
341 * @param string $table The table from which to return enableFields WHERE clause. Table name must have a 'ctrl' section in $GLOBALS['TCA'].
342 * @param bool $inv Means that the query will select all records NOT VISIBLE records (inverted selection)
343 * @return string WHERE clause part
344 */
345 public static function BEenableFields($table, $inv = false)
346 {
347 $ctrl = $GLOBALS['TCA'][$table]['ctrl'];
348 $query = [];
349 $invQuery = [];
350 if (is_array($ctrl)) {
351 if (is_array($ctrl['enablecolumns'])) {
352 if ($ctrl['enablecolumns']['disabled']) {
353 $field = $table . '.' . $ctrl['enablecolumns']['disabled'];
354 $query[] = $field . '=0';
355 $invQuery[] = $field . '<>0';
356 }
357 if ($ctrl['enablecolumns']['starttime']) {
358 $field = $table . '.' . $ctrl['enablecolumns']['starttime'];
359 $query[] = '(' . $field . '<=' . $GLOBALS['SIM_ACCESS_TIME'] . ')';
360 $invQuery[] = '(' . $field . '<>0 AND ' . $field . '>' . $GLOBALS['SIM_ACCESS_TIME'] . ')';
361 }
362 if ($ctrl['enablecolumns']['endtime']) {
363 $field = $table . '.' . $ctrl['enablecolumns']['endtime'];
364 $query[] = '(' . $field . '=0 OR ' . $field . '>' . $GLOBALS['SIM_ACCESS_TIME'] . ')';
365 $invQuery[] = '(' . $field . '<>0 AND ' . $field . '<=' . $GLOBALS['SIM_ACCESS_TIME'] . ')';
366 }
367 }
368 }
369 $outQ = $inv ? '(' . implode(' OR ', $invQuery) . ')' : implode(' AND ', $query);
370 return $outQ ? ' AND ' . $outQ : '';
371 }
372
373 /**
374 * Fetches the localization for a given record.
375 *
376 * @param string $table Table name present in $GLOBALS['TCA']
377 * @param int $uid The uid of the record
378 * @param int $language The uid of the language record in sys_language
379 * @param string $andWhereClause Optional additional WHERE clause (default: '')
380 * @return mixed Multidimensional array with selected records; if none exist, FALSE is returned
381 */
382 public static function getRecordLocalization($table, $uid, $language, $andWhereClause = '')
383 {
384 $recordLocalization = false;
385
386 // Pages still stores translations in the pages_language_overlay table, all other tables store in themself
387 if ($table === 'pages') {
388 $table = 'pages_language_overlay';
389 }
390
391 if (self::isTableLocalizable($table)) {
392 $tcaCtrl = $GLOBALS['TCA'][$table]['ctrl'];
393
394 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
395 ->getQueryBuilderForTable($table);
396
397 $constraint = $queryBuilder->expr()->andX(
398 $queryBuilder->expr()->eq(
399 $tcaCtrl['languageField'],
400 $queryBuilder->createNamedParameter($language, \PDO::PARAM_INT)
401 ),
402 QueryHelper::stripLogicalOperatorPrefix($andWhereClause)
403 );
404
405 $recordLocalization = self::getRecordsByField(
406 $table,
407 $tcaCtrl['transOrigPointerField'],
408 $uid,
409 (string)$constraint,
410 '',
411 '',
412 1,
413 true,
414 $queryBuilder
415 );
416 }
417 return $recordLocalization;
418 }
419
420 /*******************************************
421 *
422 * Page tree, TCA related
423 *
424 *******************************************/
425 /**
426 * Returns what is called the 'RootLine'. That is an array with information about the page records from a page id
427 * ($uid) and back to the root.
428 * By default deleted pages are filtered.
429 * This RootLine will follow the tree all the way to the root. This is opposite to another kind of root line known
430 * from the frontend where the rootline stops when a root-template is found.
431 *
432 * @param int $uid Page id for which to create the root line.
433 * @param string $clause Clause can be used to select other criteria. It would typically be where-clauses that
434 * stops the process if we meet a page, the user has no reading access to.
435 * @param bool $workspaceOL If TRUE, version overlay is applied. This must be requested specifically because it is
436 * usually only wanted when the rootline is used for visual output while for permission checking you want the raw thing!
437 * @return array Root line array, all the way to the page tree root (or as far as $clause allows!)
438 */
439 public static function BEgetRootLine($uid, $clause = '', $workspaceOL = false)
440 {
441 static $BEgetRootLine_cache = [];
442 $output = [];
443 $pid = $uid;
444 $ident = $pid . '-' . $clause . '-' . $workspaceOL;
445 if (is_array($BEgetRootLine_cache[$ident])) {
446 $output = $BEgetRootLine_cache[$ident];
447 } else {
448 $loopCheck = 100;
449 $theRowArray = [];
450 while ($uid != 0 && $loopCheck) {
451 $loopCheck--;
452 $row = self::getPageForRootline($uid, $clause, $workspaceOL);
453 if (is_array($row)) {
454 $uid = $row['pid'];
455 $theRowArray[] = $row;
456 } else {
457 break;
458 }
459 }
460 if ($uid == 0) {
461 $theRowArray[] = ['uid' => 0, 'title' => ''];
462 }
463 $c = count($theRowArray);
464 foreach ($theRowArray as $val) {
465 $c--;
466 $output[$c] = [
467 'uid' => $val['uid'],
468 'pid' => $val['pid'],
469 'title' => $val['title'],
470 'doktype' => $val['doktype'],
471 'tsconfig_includes' => $val['tsconfig_includes'],
472 'TSconfig' => $val['TSconfig'],
473 'is_siteroot' => $val['is_siteroot'],
474 't3ver_oid' => $val['t3ver_oid'],
475 't3ver_wsid' => $val['t3ver_wsid'],
476 't3ver_state' => $val['t3ver_state'],
477 't3ver_stage' => $val['t3ver_stage'],
478 'backend_layout_next_level' => $val['backend_layout_next_level']
479 ];
480 if (isset($val['_ORIG_pid'])) {
481 $output[$c]['_ORIG_pid'] = $val['_ORIG_pid'];
482 }
483 }
484 $BEgetRootLine_cache[$ident] = $output;
485 }
486 return $output;
487 }
488
489 /**
490 * Gets the cached page record for the rootline
491 *
492 * @param int $uid Page id for which to create the root line.
493 * @param string $clause Clause can be used to select other criteria. It would typically be where-clauses that stops the process if we meet a page, the user has no reading access to.
494 * @param bool $workspaceOL If TRUE, version overlay is applied. This must be requested specifically because it is usually only wanted when the rootline is used for visual output while for permission checking you want the raw thing!
495 * @return array Cached page record for the rootline
496 * @see BEgetRootLine
497 */
498 protected static function getPageForRootline($uid, $clause, $workspaceOL)
499 {
500 static $getPageForRootline_cache = [];
501 $ident = $uid . '-' . $clause . '-' . $workspaceOL;
502 if (is_array($getPageForRootline_cache[$ident])) {
503 $row = $getPageForRootline_cache[$ident];
504 } else {
505 $queryBuilder = static::getQueryBuilderForTable('pages');
506 $queryBuilder->getRestrictions()
507 ->removeAll()
508 ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
509
510 $row = $queryBuilder
511 ->select(
512 'pid',
513 'uid',
514 'title',
515 'doktype',
516 'tsconfig_includes',
517 'TSconfig',
518 'is_siteroot',
519 't3ver_oid',
520 't3ver_wsid',
521 't3ver_state',
522 't3ver_stage',
523 'backend_layout_next_level'
524 )
525 ->from('pages')
526 ->where(
527 $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)),
528 QueryHelper::stripLogicalOperatorPrefix($clause)
529 )
530 ->execute()
531 ->fetch();
532
533 if ($row) {
534 $newLocation = false;
535 if ($workspaceOL) {
536 self::workspaceOL('pages', $row);
537 $newLocation = self::getMovePlaceholder('pages', $row['uid'], 'pid');
538 }
539 if (is_array($row)) {
540 if ($newLocation !== false) {
541 $row['pid'] = $newLocation['pid'];
542 } else {
543 self::fixVersioningPid('pages', $row);
544 }
545 $getPageForRootline_cache[$ident] = $row;
546 }
547 }
548 }
549 return $row;
550 }
551
552 /**
553 * Opens the page tree to the specified page id
554 *
555 * @param int $pid Page id.
556 * @param bool $clearExpansion If set, then other open branches are closed.
557 * @return void
558 */
559 public static function openPageTree($pid, $clearExpansion)
560 {
561 $beUser = static::getBackendUserAuthentication();
562 // Get current expansion data:
563 if ($clearExpansion) {
564 $expandedPages = [];
565 } else {
566 $expandedPages = unserialize($beUser->uc['browseTrees']['browsePages']);
567 }
568 // Get rootline:
569 $rL = self::BEgetRootLine($pid);
570 // First, find out what mount index to use (if more than one DB mount exists):
571 $mountIndex = 0;
572 $mountKeys = array_flip($beUser->returnWebmounts());
573 foreach ($rL as $rLDat) {
574 if (isset($mountKeys[$rLDat['uid']])) {
575 $mountIndex = $mountKeys[$rLDat['uid']];
576 break;
577 }
578 }
579 // Traverse rootline and open paths:
580 foreach ($rL as $rLDat) {
581 $expandedPages[$mountIndex][$rLDat['uid']] = 1;
582 }
583 // Write back:
584 $beUser->uc['browseTrees']['browsePages'] = serialize($expandedPages);
585 $beUser->writeUC();
586 }
587
588 /**
589 * Returns the path (visually) of a page $uid, fx. "/First page/Second page/Another subpage"
590 * Each part of the path will be limited to $titleLimit characters
591 * Deleted pages are filtered out.
592 *
593 * @param int $uid Page uid for which to create record path
594 * @param string $clause Clause is additional where clauses, eg.
595 * @param int $titleLimit Title limit
596 * @param int $fullTitleLimit Title limit of Full title (typ. set to 1000 or so)
597 * @return mixed Path of record (string) OR array with short/long title if $fullTitleLimit is set.
598 */
599 public static function getRecordPath($uid, $clause, $titleLimit, $fullTitleLimit = 0)
600 {
601 if (!$titleLimit) {
602 $titleLimit = 1000;
603 }
604 $output = $fullOutput = '/';
605 $clause = trim($clause);
606 if ($clause !== '' && substr($clause, 0, 3) !== 'AND') {
607 $clause = 'AND ' . $clause;
608 }
609 $data = self::BEgetRootLine($uid, $clause);
610 foreach ($data as $record) {
611 if ($record['uid'] === 0) {
612 continue;
613 }
614 $output = '/' . GeneralUtility::fixed_lgd_cs(strip_tags($record['title']), $titleLimit) . $output;
615 if ($fullTitleLimit) {
616 $fullOutput = '/' . GeneralUtility::fixed_lgd_cs(strip_tags($record['title']), $fullTitleLimit) . $fullOutput;
617 }
618 }
619 if ($fullTitleLimit) {
620 return [$output, $fullOutput];
621 } else {
622 return $output;
623 }
624 }
625
626 /**
627 * Gets the original translation pointer table.
628 * That is now the same table, apart from pages_language_overlay
629 * where pages is the original.
630 *
631 * @param string $table Name of the table
632 * @return string Pointer table (if any)
633 */
634 public static function getOriginalTranslationTable($table)
635 {
636 return $table === 'pages_language_overlay' ? 'pages' : $table;
637 }
638
639 /**
640 * Determines whether a table is localizable and has the languageField and transOrigPointerField set in $GLOBALS['TCA'].
641 *
642 * @param string $table The table to check
643 * @return bool Whether a table is localizable
644 */
645 public static function isTableLocalizable($table)
646 {
647 $isLocalizable = false;
648 if (isset($GLOBALS['TCA'][$table]['ctrl']) && is_array($GLOBALS['TCA'][$table]['ctrl'])) {
649 $tcaCtrl = $GLOBALS['TCA'][$table]['ctrl'];
650 $isLocalizable = isset($tcaCtrl['languageField']) && $tcaCtrl['languageField'] && isset($tcaCtrl['transOrigPointerField']) && $tcaCtrl['transOrigPointerField'];
651 }
652 return $isLocalizable;
653 }
654
655 /**
656 * Returns the value of the property localizationMode in the given $config array ($GLOBALS['TCA'][<table>]['columns'][<field>]['config']).
657 * If the table is prepared for localization and no localizationMode is set, 'select' is returned by default.
658 * If the table is not prepared for localization or not defined at all in $GLOBALS['TCA'], FALSE is returned.
659 *
660 * @param string $table The name of the table to lookup in TCA
661 * @param mixed $fieldOrConfig The fieldname (string) or the configuration of the field to check (array)
662 * @return mixed If table is localizable, the set localizationMode is returned (if property is not set, 'select' is returned by default); if table is not localizable, FALSE is returned
663 */
664 public static function getInlineLocalizationMode($table, $fieldOrConfig)
665 {
666 $localizationMode = false;
667 $config = null;
668 if (is_array($fieldOrConfig) && !empty($fieldOrConfig)) {
669 $config = $fieldOrConfig;
670 } elseif (is_string($fieldOrConfig) && isset($GLOBALS['TCA'][$table]['columns'][$fieldOrConfig]['config'])) {
671 $config = $GLOBALS['TCA'][$table]['columns'][$fieldOrConfig]['config'];
672 }
673 if (is_array($config) && isset($config['type']) && $config['type'] === 'inline' && self::isTableLocalizable($table)) {
674 $localizationMode = isset($config['behaviour']['localizationMode']) && $config['behaviour']['localizationMode']
675 ? $config['behaviour']['localizationMode']
676 : 'select';
677 // The mode 'select' is not possible when child table is not localizable at all:
678 if ($localizationMode === 'select' && !self::isTableLocalizable($config['foreign_table'])) {
679 $localizationMode = false;
680 }
681 }
682 return $localizationMode;
683 }
684
685 /**
686 * Returns a page record (of page with $id) with an extra field "_thePath" set to the record path IF the WHERE clause, $perms_clause, selects the record. Thus is works as an access check that returns a page record if access was granted, otherwise not.
687 * If $id is zero a pseudo root-page with "_thePath" set is returned IF the current BE_USER is admin.
688 * In any case ->isInWebMount must return TRUE for the user (regardless of $perms_clause)
689 *
690 * @param int $id Page uid for which to check read-access
691 * @param string $perms_clause This is typically a value generated with static::getBackendUserAuthentication()->getPagePermsClause(1);
692 * @return array|bool Returns page record if OK, otherwise FALSE.
693 */
694 public static function readPageAccess($id, $perms_clause)
695 {
696 if ((string)$id !== '') {
697 $id = (int)$id;
698 if (!$id) {
699 if (static::getBackendUserAuthentication()->isAdmin()) {
700 $path = '/';
701 $pageinfo['_thePath'] = $path;
702 return $pageinfo;
703 }
704 } else {
705 $pageinfo = self::getRecord('pages', $id, '*', $perms_clause);
706 if ($pageinfo['uid'] && static::getBackendUserAuthentication()->isInWebMount($id, $perms_clause)) {
707 self::workspaceOL('pages', $pageinfo);
708 if (is_array($pageinfo)) {
709 self::fixVersioningPid('pages', $pageinfo);
710 list($pageinfo['_thePath'], $pageinfo['_thePathFull']) = self::getRecordPath((int)$pageinfo['uid'], $perms_clause, 15, 1000);
711 return $pageinfo;
712 }
713 }
714 }
715 }
716 return false;
717 }
718
719 /**
720 * Returns the "types" configuration parsed into an array for the record, $rec, from table, $table
721 *
722 * @param string $table Table name (present in TCA)
723 * @param array $rec Record from $table
724 * @param bool $useFieldNameAsKey If $useFieldNameAsKey is set, then the fieldname is associative keys in the return array, otherwise just numeric keys.
725 * @return array|NULL
726 */
727 public static function getTCAtypes($table, $rec, $useFieldNameAsKey = false)
728 {
729 if ($GLOBALS['TCA'][$table]) {
730 // Get type value:
731 $fieldValue = self::getTCAtypeValue($table, $rec);
732 $cacheIdentifier = $table . '-type-' . $fieldValue . '-fnk-' . $useFieldNameAsKey;
733
734 // Fetch from first-level-cache if available
735 if (isset(self::$tcaTableTypeConfigurationCache[$cacheIdentifier])) {
736 return self::$tcaTableTypeConfigurationCache[$cacheIdentifier];
737 }
738
739 // Get typesConf
740 $typesConf = $GLOBALS['TCA'][$table]['types'][$fieldValue];
741 // Get fields list and traverse it
742 $fieldList = explode(',', $typesConf['showitem']);
743
744 // Add subtype fields e.g. for a valid RTE transformation
745 // The RTE runs the DB -> RTE transformation only, if the RTE field is part of the getTCAtypes array
746 if (isset($typesConf['subtype_value_field'])) {
747 $subType = $rec[$typesConf['subtype_value_field']];
748 if (isset($typesConf['subtypes_addlist'][$subType])) {
749 $subFields = GeneralUtility::trimExplode(',', $typesConf['subtypes_addlist'][$subType], true);
750 $fieldList = array_merge($fieldList, $subFields);
751 }
752 }
753
754 // Add palette fields e.g. for a valid RTE transformation
755 $paletteFieldList = [];
756 foreach ($fieldList as $fieldData) {
757 list($pFieldName, $pAltTitle, $pPalette) = GeneralUtility::trimExplode(';', $fieldData);
758 if ($pPalette
759 && isset($GLOBALS['TCA'][$table]['palettes'][$pPalette])
760 && is_array($GLOBALS['TCA'][$table]['palettes'][$pPalette])
761 && isset($GLOBALS['TCA'][$table]['palettes'][$pPalette]['showitem'])
762 ) {
763 $paletteFields = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$table]['palettes'][$pPalette]['showitem'], true);
764 foreach ($paletteFields as $paletteField) {
765 if ($paletteField !== '--linebreak--') {
766 $paletteFieldList[] = $paletteField;
767 }
768 }
769 }
770 }
771 $fieldList = array_merge($fieldList, $paletteFieldList);
772
773 $altFieldList = [];
774 // Traverse fields in types config and parse the configuration into a nice array:
775 foreach ($fieldList as $k => $v) {
776 list($pFieldName, $pAltTitle, $pPalette) = GeneralUtility::trimExplode(';', $v);
777 $defaultExtras = '';
778 if (!empty($typesConf['columnsOverrides'][$pFieldName]['defaultExtras'])) {
779 // Use defaultExtras from columnsOverrides if given
780 $defaultExtras = $typesConf['columnsOverrides'][$pFieldName]['defaultExtras'];
781 } elseif (!empty($GLOBALS['TCA'][$table]['columns'][$pFieldName]['defaultExtras'])) {
782 // Use defaultExtras from columns if given
783 $defaultExtras = $GLOBALS['TCA'][$table]['columns'][$pFieldName]['defaultExtras'];
784 }
785 $specConfParts = self::getSpecConfParts($defaultExtras);
786 $fieldList[$k] = [
787 'field' => $pFieldName,
788 'title' => $pAltTitle,
789 'palette' => $pPalette,
790 'spec' => $specConfParts,
791 'origString' => $v
792 ];
793 if ($useFieldNameAsKey) {
794 $altFieldList[$fieldList[$k]['field']] = $fieldList[$k];
795 }
796 }
797 if ($useFieldNameAsKey) {
798 $fieldList = $altFieldList;
799 }
800
801 // Add to first-level-cache
802 self::$tcaTableTypeConfigurationCache[$cacheIdentifier] = $fieldList;
803
804 // Return array:
805 return $fieldList;
806 }
807 return null;
808 }
809
810 /**
811 * Returns the "type" value of $rec from $table which can be used to look up the correct "types" rendering section in $GLOBALS['TCA']
812 * If no "type" field is configured in the "ctrl"-section of the $GLOBALS['TCA'] for the table, zero is used.
813 * If zero is not an index in the "types" section of $GLOBALS['TCA'] for the table, then the $fieldValue returned will default to 1 (no matter if that is an index or not)
814 *
815 * Note: This method is very similar to the type determination of FormDataProvider/DatabaseRecordTypeValue,
816 * however, it has two differences:
817 * 1) The method in TCEForms also takes care of localization (which is difficult to do here as the whole infrastructure for language overlays is only in TCEforms).
818 * 2) The $row array looks different in TCEForms, as in there it's not the raw record but the prepared data from other providers is handled, which changes e.g. how "select"
819 * and "group" field values are stored, which makes different processing of the "foreign pointer field" type field variant necessary.
820 *
821 * @param string $table Table name present in TCA
822 * @param array $row Record from $table
823 * @throws \RuntimeException
824 * @return string Field value
825 * @see getTCAtypes()
826 */
827 public static function getTCAtypeValue($table, $row)
828 {
829 $typeNum = 0;
830 if ($GLOBALS['TCA'][$table]) {
831 $field = $GLOBALS['TCA'][$table]['ctrl']['type'];
832 if (strpos($field, ':') !== false) {
833 list($pointerField, $foreignTableTypeField) = explode(':', $field);
834 // Get field value from database if field is not in the $row array
835 if (!isset($row[$pointerField])) {
836 $localRow = self::getRecord($table, $row['uid'], $pointerField);
837 $foreignUid = $localRow[$pointerField];
838 } else {
839 $foreignUid = $row[$pointerField];
840 }
841 if ($foreignUid) {
842 $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$pointerField]['config'];
843 $relationType = $fieldConfig['type'];
844 if ($relationType === 'select') {
845 $foreignTable = $fieldConfig['foreign_table'];
846 } elseif ($relationType === 'group') {
847 $allowedTables = explode(',', $fieldConfig['allowed']);
848 $foreignTable = $allowedTables[0];
849 } else {
850 throw new \RuntimeException(
851 'TCA foreign field pointer fields are only allowed to be used with group or select field types.',
852 1325862240
853 );
854 }
855 $foreignRow = self::getRecord($foreignTable, $foreignUid, $foreignTableTypeField);
856 if ($foreignRow[$foreignTableTypeField]) {
857 $typeNum = $foreignRow[$foreignTableTypeField];
858 }
859 }
860 } else {
861 $typeNum = $row[$field];
862 }
863 // If that value is an empty string, set it to "0" (zero)
864 if (empty($typeNum)) {
865 $typeNum = 0;
866 }
867 }
868 // If current typeNum doesn't exist, set it to 0 (or to 1 for historical reasons, if 0 doesn't exist)
869 if (!$GLOBALS['TCA'][$table]['types'][$typeNum]) {
870 $typeNum = $GLOBALS['TCA'][$table]['types']['0'] ? 0 : 1;
871 }
872 // Force to string. Necessary for eg '-1' to be recognized as a type value.
873 $typeNum = (string)$typeNum;
874 return $typeNum;
875 }
876
877 /**
878 * Parses "defaultExtras" of $GLOBALS['TCA'] columns config section to an array.
879 * Elements are split by ":" and within those parts, parameters are split by "|".
880 *
881 * See unit tests for details.
882 *
883 * @param string $defaultExtrasString "defaultExtras" string from columns config
884 * @return array
885 */
886 public static function getSpecConfParts($defaultExtrasString)
887 {
888 $specConfParts = GeneralUtility::trimExplode(':', $defaultExtrasString, true);
889 $reg = [];
890 if (!empty($specConfParts)) {
891 foreach ($specConfParts as $k2 => $v2) {
892 unset($specConfParts[$k2]);
893 if (preg_match('/(.*)\\[(.*)\\]/', $v2, $reg)) {
894 $specConfParts[trim($reg[1])] = [
895 'parameters' => GeneralUtility::trimExplode('|', $reg[2], true)
896 ];
897 } else {
898 $specConfParts[trim($v2)] = 1;
899 }
900 }
901 } else {
902 $specConfParts = [];
903 }
904 return $specConfParts;
905 }
906
907 /**
908 * Takes an array of "[key] = [value]" strings and returns an array with the keys set as keys pointing to the value.
909 * Better see it in action! Find example in Inside TYPO3
910 *
911 * @param array $pArr Array of "[key] = [value]" strings to convert.
912 * @return array
913 * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
914 */
915 public static function getSpecConfParametersFromArray($pArr)
916 {
917 GeneralUtility::logDeprecatedFunction();
918 $out = [];
919 if (is_array($pArr)) {
920 foreach ($pArr as $k => $v) {
921 $parts = explode('=', $v, 2);
922 if (count($parts) === 2) {
923 $out[trim($parts[0])] = trim($parts[1]);
924 } else {
925 $out[$k] = $v;
926 }
927 }
928 }
929 return $out;
930 }
931
932 /**
933 * Finds the Data Structure for a FlexForm field
934 *
935 * NOTE ON data structures for deleted records: This function may fail to deliver the data structure
936 * for a record for a few reasons:
937 * a) The data structure could be deleted (either with deleted-flagged or hard-deleted),
938 * b) the data structure is fetched using the ds_pointerField_searchParent in which case any
939 * deleted record on the route to the final location of the DS will make it fail.
940 * In theory, we can solve the problem in the case where records that are deleted-flagged keeps us
941 * from finding the DS - this is done at the markers ###NOTE_A### where we make sure to also select deleted records.
942 * However, we generally want the DS lookup to fail for deleted records since for the working website we expect a
943 * deleted-flagged record to be as inaccessible as one that is completely deleted from the DB. Any way we look
944 * at it, this may lead to integrity problems of the reference index and even lost files if attached.
945 * However, that is not really important considering that a single change to a data structure can instantly
946 * invalidate large amounts of the reference index which we do accept as a cost for the flexform features.
947 * Other than requiring a reference index update, deletion of/changes in data structure or the failure to look
948 * them up when completely deleting records may lead to lost files in the uploads/ folders since those are now
949 * without a proper reference.
950 *
951 * @param array $conf Field config array
952 * @param array $row Record data
953 * @param string $table The table name
954 * @param string $fieldName Optional fieldname passed to hook object
955 * @param bool $WSOL If set, workspace overlay is applied to records. This is correct behaviour for all presentation and export, but NOT if you want a TRUE reflection of how things are in the live workspace.
956 * @param int $newRecordPidValue SPECIAL CASES: Use this, if the DataStructure may come from a parent record and the INPUT row doesn't have a uid yet (hence, the pid cannot be looked up). Then it is necessary to supply a PID value to search recursively in for the DS (used from DataHandler)
957 * @return mixed If array, the data structure was found and returned as an array. Otherwise (string) it is an error message.
958 * @deprecated since TYPO3 v8, will be removed in TYPO3 v9. This is now integrated as FlexFormTools->getDataStructureIdentifier()
959 */
960 public static function getFlexFormDS($conf, $row, $table, $fieldName = '', $WSOL = true, $newRecordPidValue = 0)
961 {
962 GeneralUtility::logDeprecatedFunction();
963 // Get pointer field etc from TCA-config:
964 $ds_pointerField = $conf['ds_pointerField'];
965 $ds_array = $conf['ds'];
966 $ds_tableField = $conf['ds_tableField'];
967 $ds_searchParentField = $conf['ds_pointerField_searchParent'];
968 // If there is a data source array, that takes precedence
969 if (is_array($ds_array)) {
970 // If a pointer field is set, take the value from that field in the $row array and use as key.
971 if ($ds_pointerField) {
972 // Up to two pointer fields can be specified in a comma separated list.
973 $pointerFields = GeneralUtility::trimExplode(',', $ds_pointerField);
974 // If we have two pointer fields, the array keys should contain both field values separated by comma.
975 // The asterisk "*" catches all values. For backwards compatibility, it's also possible to specify only
976 // the value of the first defined ds_pointerField.
977 if (count($pointerFields) === 2) {
978 if ($ds_array[$row[$pointerFields[0]] . ',' . $row[$pointerFields[1]]]) {
979 // Check if we have a DS for the combination of both pointer fields values
980 $srcPointer = $row[$pointerFields[0]] . ',' . $row[$pointerFields[1]];
981 } elseif ($ds_array[$row[$pointerFields[1]] . ',*']) {
982 // Check if we have a DS for the value of the first pointer field suffixed with ",*"
983 $srcPointer = $row[$pointerFields[1]] . ',*';
984 } elseif ($ds_array['*,' . $row[$pointerFields[1]]]) {
985 // Check if we have a DS for the value of the second pointer field prefixed with "*,"
986 $srcPointer = '*,' . $row[$pointerFields[1]];
987 } elseif ($ds_array[$row[$pointerFields[0]]]) {
988 // Check if we have a DS for just the value of the first pointer field (mainly for backwards compatibility)
989 $srcPointer = $row[$pointerFields[0]];
990 } else {
991 $srcPointer = null;
992 }
993 } else {
994 $srcPointer = $row[$pointerFields[0]];
995 }
996 $srcPointer = $srcPointer !== null && isset($ds_array[$srcPointer]) ? $srcPointer : 'default';
997 } else {
998 $srcPointer = 'default';
999 }
1000 // 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.
1001 if (substr($ds_array[$srcPointer], 0, 5) == 'FILE:') {
1002 $file = GeneralUtility::getFileAbsFileName(substr($ds_array[$srcPointer], 5));
1003 if ($file && @is_file($file)) {
1004 $dataStructArray = GeneralUtility::xml2array(file_get_contents($file));
1005 } else {
1006 $dataStructArray = 'The file "' . substr($ds_array[$srcPointer], 5) . '" in ds-array key "' . $srcPointer . '" was not found ("' . $file . '")';
1007 }
1008 } else {
1009 $dataStructArray = GeneralUtility::xml2array($ds_array[$srcPointer]);
1010 }
1011 } elseif ($ds_pointerField) {
1012 // If pointer field AND possibly a table/field is set:
1013 // Value of field pointed to:
1014 $srcPointer = $row[$ds_pointerField];
1015 // Searching recursively back if 'ds_pointerField_searchParent' is defined (typ. a page rootline, or maybe a tree-table):
1016 if ($ds_searchParentField && !$srcPointer) {
1017 $rr = self::getRecord($table, $row['uid'], 'uid,' . $ds_searchParentField);
1018 // Get the "pid" field - we cannot know that it is in the input record! ###NOTE_A###
1019 if ($WSOL) {
1020 self::workspaceOL($table, $rr);
1021 self::fixVersioningPid($table, $rr, true);
1022 }
1023
1024 $queryBuilder = static::getQueryBuilderForTable($table);
1025 $queryBuilder->getRestrictions()
1026 ->removeAll()
1027 ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1028
1029 $uidAcc = [];
1030 // Used to avoid looping, if any should happen.
1031 $subFieldPointer = $conf['ds_pointerField_searchParent_subField'];
1032 while (!$srcPointer) {
1033 // select fields
1034 $queryBuilder
1035 ->select('uid', $ds_pointerField, $ds_searchParentField)
1036 ->from($table)
1037 ->where(
1038 $queryBuilder->expr()->eq(
1039 'uid',
1040 $queryBuilder->createNamedParameter(
1041 ($newRecordPidValue ?: $rr[$ds_searchParentField]),
1042 \PDO::PARAM_INT
1043 )
1044 )
1045 );
1046 if ($subFieldPointer) {
1047 $queryBuilder->addSelect($subFieldPointer);
1048 }
1049
1050 $rr = $queryBuilder->execute()->fetch();
1051
1052 $newRecordPidValue = 0;
1053 // Break if no result from SQL db or if looping...
1054 if (!is_array($rr) || isset($uidAcc[$rr['uid']])) {
1055 break;
1056 }
1057 $uidAcc[$rr['uid']] = 1;
1058 if ($WSOL) {
1059 self::workspaceOL($table, $rr);
1060 self::fixVersioningPid($table, $rr, true);
1061 }
1062 $srcPointer = $subFieldPointer && $rr[$subFieldPointer] ? $rr[$subFieldPointer] : $rr[$ds_pointerField];
1063 }
1064 }
1065 // If there is a srcPointer value:
1066 if ($srcPointer) {
1067 if (MathUtility::canBeInterpretedAsInteger($srcPointer)) {
1068 // If integer, then its a record we will look up:
1069 list($tName, $fName) = explode(':', $ds_tableField, 2);
1070 if ($tName && $fName && is_array($GLOBALS['TCA'][$tName])) {
1071 $dataStructRec = self::getRecord($tName, $srcPointer);
1072 if ($WSOL) {
1073 self::workspaceOL($tName, $dataStructRec);
1074 }
1075 if (strpos($dataStructRec[$fName], '<') === false) {
1076 if (is_file(PATH_site . $dataStructRec[$fName])) {
1077 // The value is a pointer to a file
1078 $dataStructArray = GeneralUtility::xml2array(file_get_contents(PATH_site . $dataStructRec[$fName]));
1079 } else {
1080 $dataStructArray = sprintf('File \'%s\' was not found', $dataStructRec[$fName]);
1081 }
1082 } else {
1083 // No file pointer, handle as being XML (default behaviour)
1084 $dataStructArray = GeneralUtility::xml2array($dataStructRec[$fName]);
1085 }
1086 } else {
1087 $dataStructArray = 'No tablename (' . $tName . ') or fieldname (' . $fName . ') was found an valid!';
1088 }
1089 } else {
1090 // Otherwise expect it to be a file:
1091 $file = GeneralUtility::getFileAbsFileName($srcPointer);
1092 if ($file && @is_file($file)) {
1093 $dataStructArray = GeneralUtility::xml2array(file_get_contents($file));
1094 } else {
1095 // Error message.
1096 $dataStructArray = 'The file "' . $srcPointer . '" was not found ("' . $file . '")';
1097 }
1098 }
1099 } else {
1100 // Error message.
1101 $dataStructArray = 'No source value in fieldname "' . $ds_pointerField . '"';
1102 }
1103 } else {
1104 $dataStructArray = 'No proper configuration!';
1105 }
1106 // Hook for post-processing the Flexform DS. Introduces the possibility to configure Flexforms via TSConfig
1107 // This hook isn't called anymore from within the core, the whole method is deprecated.
1108 // There are alternative hooks, see FlexFormTools->getDataStructureIdentifier() and ->parseDataStructureByIdentifier()
1109 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['getFlexFormDSClass'])) {
1110 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['getFlexFormDSClass'] as $classRef) {
1111 $hookObj = GeneralUtility::getUserObj($classRef);
1112 if (method_exists($hookObj, 'getFlexFormDS_postProcessDS')) {
1113 $hookObj->getFlexFormDS_postProcessDS($dataStructArray, $conf, $row, $table, $fieldName);
1114 }
1115 }
1116 }
1117 return $dataStructArray;
1118 }
1119
1120 /*******************************************
1121 *
1122 * Caching related
1123 *
1124 *******************************************/
1125 /**
1126 * Stores $data in the 'cache_hash' cache with the hash key, $hash
1127 * and visual/symbolic identification, $ident
1128 *
1129 * IDENTICAL to the function by same name found in \TYPO3\CMS\Frontend\Page\PageRepository
1130 *
1131 * @param string $hash 32 bit hash string (eg. a md5 hash of a serialized array identifying the data being stored)
1132 * @param mixed $data The data to store
1133 * @param string $ident $ident is just a textual identification in order to inform about the content!
1134 * @return void
1135 */
1136 public static function storeHash($hash, $data, $ident)
1137 {
1138 /** @var CacheManager $cacheManager */
1139 $cacheManager = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Cache\CacheManager::class);
1140 $cacheManager->getCache('cache_hash')->set($hash, $data, ['ident_' . $ident], 0);
1141 }
1142
1143 /**
1144 * Returns data stored for the hash string in the cache "cache_hash"
1145 * Can be used to retrieved a cached value, array or object
1146 *
1147 * IDENTICAL to the function by same name found in \TYPO3\CMS\Frontend\Page\PageRepository
1148 *
1149 * @param string $hash The hash-string which was used to store the data value
1150 * @return mixed The "data" from the cache
1151 */
1152 public static function getHash($hash)
1153 {
1154 /** @var CacheManager $cacheManager */
1155 $cacheManager = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Cache\CacheManager::class);
1156 $cacheEntry = $cacheManager->getCache('cache_hash')->get($hash);
1157 $hashContent = null;
1158 if ($cacheEntry) {
1159 $hashContent = $cacheEntry;
1160 }
1161 return $hashContent;
1162 }
1163
1164 /*******************************************
1165 *
1166 * TypoScript related
1167 *
1168 *******************************************/
1169 /**
1170 * Returns the Page TSconfig for page with id, $id
1171 *
1172 * @param int $id Page uid for which to create Page TSconfig
1173 * @param array $rootLine If $rootLine is an array, that is used as rootline, otherwise rootline is just calculated
1174 * @param bool $returnPartArray If $returnPartArray is set, then the array with accumulated Page TSconfig is returned non-parsed. Otherwise the output will be parsed by the TypoScript parser.
1175 * @return array Page TSconfig
1176 * @see \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser
1177 */
1178 public static function getPagesTSconfig($id, $rootLine = null, $returnPartArray = false)
1179 {
1180 static $pagesTSconfig_cacheReference = [];
1181 static $combinedTSconfig_cache = [];
1182
1183 $id = (int)$id;
1184 if ($returnPartArray === false
1185 && $rootLine === null
1186 && isset($pagesTSconfig_cacheReference[$id])
1187 ) {
1188 return $combinedTSconfig_cache[$pagesTSconfig_cacheReference[$id]];
1189 } else {
1190 $TSconfig = [];
1191 if (!is_array($rootLine)) {
1192 $useCacheForCurrentPageId = true;
1193 $rootLine = self::BEgetRootLine($id, '', true);
1194 } else {
1195 $useCacheForCurrentPageId = false;
1196 }
1197
1198 // Order correctly
1199 ksort($rootLine);
1200 $TSdataArray = [];
1201 // Setting default configuration
1202 $TSdataArray['defaultPageTSconfig'] = $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'];
1203 foreach ($rootLine as $k => $v) {
1204 if (trim($v['tsconfig_includes'])) {
1205 $includeTsConfigFileList = GeneralUtility::trimExplode(',', $v['tsconfig_includes'], true);
1206 // Traversing list
1207 foreach ($includeTsConfigFileList as $key => $includeTsConfigFile) {
1208 if (strpos($includeTsConfigFile, 'EXT:') === 0) {
1209 list($includeTsConfigFileExtensionKey, $includeTsConfigFilename) = explode(
1210 '/',
1211 substr($includeTsConfigFile, 4),
1212 2
1213 );
1214 if ((string)$includeTsConfigFileExtensionKey !== ''
1215 && ExtensionManagementUtility::isLoaded($includeTsConfigFileExtensionKey)
1216 && (string)$includeTsConfigFilename !== ''
1217 ) {
1218 $includeTsConfigFileAndPath = ExtensionManagementUtility::extPath($includeTsConfigFileExtensionKey) .
1219 $includeTsConfigFilename;
1220 if (file_exists($includeTsConfigFileAndPath)) {
1221 $TSdataArray['uid_' . $v['uid'] . '_static_' . $key] = file_get_contents($includeTsConfigFileAndPath);
1222 }
1223 }
1224 }
1225 }
1226 }
1227 $TSdataArray['uid_' . $v['uid']] = $v['TSconfig'];
1228 }
1229 $TSdataArray = static::emitGetPagesTSconfigPreIncludeSignal($TSdataArray, $id, $rootLine, $returnPartArray);
1230 $TSdataArray = TypoScriptParser::checkIncludeLines_array($TSdataArray);
1231 if ($returnPartArray) {
1232 return $TSdataArray;
1233 }
1234 // Parsing the page TS-Config
1235 $pageTS = implode(LF . '[GLOBAL]' . LF, $TSdataArray);
1236 /* @var $parseObj \TYPO3\CMS\Backend\Configuration\TsConfigParser */
1237 $parseObj = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Configuration\TsConfigParser::class);
1238 $res = $parseObj->parseTSconfig($pageTS, 'PAGES', $id, $rootLine);
1239 if ($res) {
1240 $TSconfig = $res['TSconfig'];
1241 }
1242 $cacheHash = $res['hash'];
1243 // Get User TSconfig overlay
1244 $userTSconfig = static::getBackendUserAuthentication()->userTS['page.'];
1245 if (is_array($userTSconfig)) {
1246 ArrayUtility::mergeRecursiveWithOverrule($TSconfig, $userTSconfig);
1247 $cacheHash .= '_user' . $GLOBALS['BE_USER']->user['uid'];
1248 }
1249
1250 if ($useCacheForCurrentPageId) {
1251 if (!isset($combinedTSconfig_cache[$cacheHash])) {
1252 $combinedTSconfig_cache[$cacheHash] = $TSconfig;
1253 }
1254 $pagesTSconfig_cacheReference[$id] = $cacheHash;
1255 }
1256 }
1257 return $TSconfig;
1258 }
1259
1260 /*******************************************
1261 *
1262 * Users / Groups related
1263 *
1264 *******************************************/
1265 /**
1266 * Returns an array with be_users records of all user NOT DELETED sorted by their username
1267 * Keys in the array is the be_users uid
1268 *
1269 * @param string $fields Optional $fields list (default: username,usergroup,usergroup_cached_list,uid) can be used to set the selected fields
1270 * @param string $where Optional $where clause (fx. "AND username='pete'") can be used to limit query
1271 * @return array
1272 */
1273 public static function getUserNames($fields = 'username,usergroup,usergroup_cached_list,uid', $where = '')
1274 {
1275 return self::getRecordsSortedByTitle(
1276 GeneralUtility::trimExplode(',', $fields, true),
1277 'be_users',
1278 'username',
1279 'AND pid=0 ' . $where
1280 );
1281 }
1282
1283 /**
1284 * Returns an array with be_groups records (title, uid) of all groups NOT DELETED sorted by their title
1285 *
1286 * @param string $fields Field list
1287 * @param string $where WHERE clause
1288 * @return array
1289 */
1290 public static function getGroupNames($fields = 'title,uid', $where = '')
1291 {
1292 return self::getRecordsSortedByTitle(
1293 GeneralUtility::trimExplode(',', $fields, true),
1294 'be_groups',
1295 'title',
1296 'AND pid=0 ' . $where
1297 );
1298 }
1299
1300 /**
1301 * Returns an array of all non-deleted records of a table sorted by a given title field.
1302 * The value of the title field will be replaced by the return value
1303 * of self::getRecordTitle() before the sorting is performed.
1304 *
1305 * @param array $fields Fields to select
1306 * @param string $table Table name
1307 * @param string $titleField Field that will contain the record title
1308 * @param string $where Additional where clause
1309 * @return array Array of sorted records
1310 */
1311 protected static function getRecordsSortedByTitle(array $fields, $table, $titleField, $where = '')
1312 {
1313 $fieldsIndex = array_flip($fields);
1314 // Make sure the titleField is amongst the fields when getting sorted
1315 $fieldsIndex[$titleField] = 1;
1316
1317 $result = [];
1318
1319 $queryBuilder = static::getQueryBuilderForTable($table);
1320 $queryBuilder->getRestrictions()
1321 ->removeAll()
1322 ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1323
1324 $res = $queryBuilder
1325 ->select('*')
1326 ->from($table)
1327 ->where(QueryHelper::stripLogicalOperatorPrefix($where))
1328 ->execute();
1329
1330 while ($record = $res->fetch()) {
1331 // store the uid, because it might be unset if it's not among the requested $fields
1332 $recordId = $record['uid'];
1333 $record[$titleField] = self::getRecordTitle($table, $record);
1334
1335 // include only the requested fields in the result
1336 $result[$recordId] = array_intersect_key($record, $fieldsIndex);
1337 }
1338
1339 // sort records by $sortField. This is not done in the query because the title might have been overwritten by
1340 // self::getRecordTitle();
1341 return ArrayUtility::sortArraysByKey($result, $titleField);
1342 }
1343
1344 /**
1345 * Returns an array with be_groups records (like ->getGroupNames) but:
1346 * - if the current BE_USER is admin, then all groups are returned, otherwise only groups that the current user is member of (usergroup_cached_list) will be returned.
1347 *
1348 * @param string $fields Field list; $fields specify the fields selected (default: title,uid)
1349 * @return array
1350 */
1351 public static function getListGroupNames($fields = 'title, uid')
1352 {
1353 $beUser = static::getBackendUserAuthentication();
1354 $exQ = ' AND hide_in_lists=0';
1355 if (!$beUser->isAdmin()) {
1356 $exQ .= ' AND uid IN (' . ($beUser->user['usergroup_cached_list'] ?: 0) . ')';
1357 }
1358 return self::getGroupNames($fields, $exQ);
1359 }
1360
1361 /**
1362 * Returns the array $usernames with the names of all users NOT IN $groupArray changed to the uid (hides the usernames!).
1363 * If $excludeBlindedFlag is set, then these records are unset from the array $usernames
1364 * Takes $usernames (array made by \TYPO3\CMS\Backend\Utility\BackendUtility::getUserNames()) and a $groupArray (array with the groups a certain user is member of) as input
1365 *
1366 * @param array $usernames User names
1367 * @param array $groupArray Group names
1368 * @param bool $excludeBlindedFlag If $excludeBlindedFlag is set, then these records are unset from the array $usernames
1369 * @return array User names, blinded
1370 */
1371 public static function blindUserNames($usernames, $groupArray, $excludeBlindedFlag = false)
1372 {
1373 if (is_array($usernames) && is_array($groupArray)) {
1374 foreach ($usernames as $uid => $row) {
1375 $userN = $uid;
1376 $set = 0;
1377 if ($row['uid'] != static::getBackendUserAuthentication()->user['uid']) {
1378 foreach ($groupArray as $v) {
1379 if ($v && GeneralUtility::inList($row['usergroup_cached_list'], $v)) {
1380 $userN = $row['username'];
1381 $set = 1;
1382 }
1383 }
1384 } else {
1385 $userN = $row['username'];
1386 $set = 1;
1387 }
1388 $usernames[$uid]['username'] = $userN;
1389 if ($excludeBlindedFlag && !$set) {
1390 unset($usernames[$uid]);
1391 }
1392 }
1393 }
1394 return $usernames;
1395 }
1396
1397 /**
1398 * Corresponds to blindUserNames but works for groups instead
1399 *
1400 * @param array $groups Group names
1401 * @param array $groupArray Group names (reference)
1402 * @param bool $excludeBlindedFlag If $excludeBlindedFlag is set, then these records are unset from the array $usernames
1403 * @return array
1404 */
1405 public static function blindGroupNames($groups, $groupArray, $excludeBlindedFlag = false)
1406 {
1407 if (is_array($groups) && is_array($groupArray)) {
1408 foreach ($groups as $uid => $row) {
1409 $groupN = $uid;
1410 $set = 0;
1411 if (ArrayUtility::inArray($groupArray, $uid)) {
1412 $groupN = $row['title'];
1413 $set = 1;
1414 }
1415 $groups[$uid]['title'] = $groupN;
1416 if ($excludeBlindedFlag && !$set) {
1417 unset($groups[$uid]);
1418 }
1419 }
1420 }
1421 return $groups;
1422 }
1423
1424 /*******************************************
1425 *
1426 * Output related
1427 *
1428 *******************************************/
1429 /**
1430 * Returns the difference in days between input $tstamp and $EXEC_TIME
1431 *
1432 * @param int $tstamp Time stamp, seconds
1433 * @return int
1434 */
1435 public static function daysUntil($tstamp)
1436 {
1437 $delta_t = $tstamp - $GLOBALS['EXEC_TIME'];
1438 return ceil($delta_t / (3600 * 24));
1439 }
1440
1441 /**
1442 * Returns $tstamp formatted as "ddmmyy" (According to $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'])
1443 *
1444 * @param int $tstamp Time stamp, seconds
1445 * @return string Formatted time
1446 */
1447 public static function date($tstamp)
1448 {
1449 return date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], (int)$tstamp);
1450 }
1451
1452 /**
1453 * Returns $tstamp formatted as "ddmmyy hhmm" (According to $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] AND $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'])
1454 *
1455 * @param int $value Time stamp, seconds
1456 * @return string Formatted time
1457 */
1458 public static function datetime($value)
1459 {
1460 return date(
1461 $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'],
1462 $value
1463 );
1464 }
1465
1466 /**
1467 * Returns $value (in seconds) formatted as hh:mm:ss
1468 * For instance $value = 3600 + 60*2 + 3 should return "01:02:03"
1469 *
1470 * @param int $value Time stamp, seconds
1471 * @param bool $withSeconds Output hh:mm:ss. If FALSE: hh:mm
1472 * @return string Formatted time
1473 */
1474 public static function time($value, $withSeconds = true)
1475 {
1476 $hh = floor($value / 3600);
1477 $min = floor(($value - $hh * 3600) / 60);
1478 $sec = $value - $hh * 3600 - $min * 60;
1479 $l = sprintf('%02d', $hh) . ':' . sprintf('%02d', $min);
1480 if ($withSeconds) {
1481 $l .= ':' . sprintf('%02d', $sec);
1482 }
1483 return $l;
1484 }
1485
1486 /**
1487 * Returns the "age" in minutes / hours / days / years of the number of $seconds inputted.
1488 *
1489 * @param int $seconds Seconds could be the difference of a certain timestamp and time()
1490 * @param string $labels Labels should be something like ' min| hrs| days| yrs| min| hour| day| year'. This value is typically delivered by this function call: $GLOBALS["LANG"]->sL("LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears")
1491 * @return string Formatted time
1492 */
1493 public static function calcAge($seconds, $labels = ' min| hrs| days| yrs| min| hour| day| year')
1494 {
1495 $labelArr = explode('|', $labels);
1496 $absSeconds = abs($seconds);
1497 $sign = $seconds < 0 ? -1 : 1;
1498 if ($absSeconds < 3600) {
1499 $val = round($absSeconds / 60);
1500 $seconds = $sign * $val . ($val == 1 ? $labelArr[4] : $labelArr[0]);
1501 } elseif ($absSeconds < 24 * 3600) {
1502 $val = round($absSeconds / 3600);
1503 $seconds = $sign * $val . ($val == 1 ? $labelArr[5] : $labelArr[1]);
1504 } elseif ($absSeconds < 365 * 24 * 3600) {
1505 $val = round($absSeconds / (24 * 3600));
1506 $seconds = $sign * $val . ($val == 1 ? $labelArr[6] : $labelArr[2]);
1507 } else {
1508 $val = round($absSeconds / (365 * 24 * 3600));
1509 $seconds = $sign * $val . ($val == 1 ? $labelArr[7] : $labelArr[3]);
1510 }
1511 return $seconds;
1512 }
1513
1514 /**
1515 * Returns a formatted timestamp if $tstamp is set.
1516 * The date/datetime will be followed by the age in parenthesis.
1517 *
1518 * @param int $tstamp Time stamp, seconds
1519 * @param int $prefix 1/-1 depending on polarity of age.
1520 * @param string $date $date=="date" will yield "dd:mm:yy" formatting, otherwise "dd:mm:yy hh:mm
1521 * @return string
1522 */
1523 public static function dateTimeAge($tstamp, $prefix = 1, $date = '')
1524 {
1525 if (!$tstamp) {
1526 return '';
1527 }
1528 $label = static::getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears');
1529 $age = ' (' . self::calcAge($prefix * ($GLOBALS['EXEC_TIME'] - $tstamp), $label) . ')';
1530 return ($date === 'date' ? self::date($tstamp) : self::datetime($tstamp)) . $age;
1531 }
1532
1533 /**
1534 * Returns alt="" and title="" attributes with the value of $content.
1535 *
1536 * @param string $content Value for 'alt' and 'title' attributes (will be htmlspecialchars()'ed before output)
1537 * @return string
1538 * @deprecated since TYPO3 CMS 8, will be removed in TYPO3 CMS 9.
1539 */
1540 public static function titleAltAttrib($content)
1541 {
1542 GeneralUtility::logDeprecatedFunction();
1543 $out = '';
1544 $out .= ' alt="' . htmlspecialchars($content) . '"';
1545 $out .= ' title="' . htmlspecialchars($content) . '"';
1546 return $out;
1547 }
1548
1549 /**
1550 * Resolves file references for a given record.
1551 *
1552 * @param string $tableName Name of the table of the record
1553 * @param string $fieldName Name of the field of the record
1554 * @param array $element Record data
1555 * @param NULL|int $workspaceId Workspace to fetch data for
1556 * @return NULL|\TYPO3\CMS\Core\Resource\FileReference[]
1557 */
1558 public static function resolveFileReferences($tableName, $fieldName, $element, $workspaceId = null)
1559 {
1560 if (empty($GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'])) {
1561 return null;
1562 }
1563 $configuration = $GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
1564 if (empty($configuration['type']) || $configuration['type'] !== 'inline'
1565 || empty($configuration['foreign_table']) || $configuration['foreign_table'] !== 'sys_file_reference'
1566 ) {
1567 return null;
1568 }
1569
1570 $fileReferences = [];
1571 /** @var $relationHandler RelationHandler */
1572 $relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
1573 if ($workspaceId !== null) {
1574 $relationHandler->setWorkspaceId($workspaceId);
1575 }
1576 $relationHandler->start(
1577 $element[$fieldName],
1578 $configuration['foreign_table'],
1579 $configuration['MM'],
1580 $element['uid'],
1581 $tableName,
1582 $configuration
1583 );
1584 $relationHandler->processDeletePlaceholder();
1585 $referenceUids = $relationHandler->tableArray[$configuration['foreign_table']];
1586
1587 foreach ($referenceUids as $referenceUid) {
1588 try {
1589 $fileReference = ResourceFactory::getInstance()->getFileReferenceObject(
1590 $referenceUid,
1591 [],
1592 ($workspaceId === 0)
1593 );
1594 $fileReferences[$fileReference->getUid()] = $fileReference;
1595 } catch (\TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException $e) {
1596 /**
1597 * We just catch the exception here
1598 * Reasoning: There is nothing an editor or even admin could do
1599 */
1600 } catch (\InvalidArgumentException $e) {
1601 /**
1602 * The storage does not exist anymore
1603 * Log the exception message for admins as they maybe can restore the storage
1604 */
1605 $logMessage = $e->getMessage() . ' (table: "' . $tableName . '", fieldName: "' . $fieldName . '", referenceUid: ' . $referenceUid . ')';
1606 GeneralUtility::sysLog($logMessage, 'core', GeneralUtility::SYSLOG_SEVERITY_ERROR);
1607 }
1608 }
1609
1610 return $fileReferences;
1611 }
1612
1613 /**
1614 * Returns a linked image-tag for thumbnail(s)/fileicons/truetype-font-previews from a database row with a list of image files in a field
1615 * All $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] extension are made to thumbnails + ttf file (renders font-example)
1616 * Thumbsnails are linked to the show_item.php script which will display further details.
1617 *
1618 * @param array $row Row is the database row from the table, $table.
1619 * @param string $table Table name for $row (present in TCA)
1620 * @param string $field Field is pointing to the list of image files
1621 * @param string $backPath Back path prefix for image tag src="" field
1622 * @param string $thumbScript UNUSED since FAL
1623 * @param string $uploaddir Optional: $uploaddir is the directory relative to PATH_site where the image files from the $field value is found (Is by default set to the entry in $GLOBALS['TCA'] for that field! so you don't have to!)
1624 * @param int $abs UNUSED
1625 * @param string $tparams Optional: $tparams is additional attributes for the image tags
1626 * @param int|string $size Optional: $size is [w]x[h] of the thumbnail. 64 is default.
1627 * @param bool $linkInfoPopup Whether to wrap with a link opening the info popup
1628 * @return string Thumbnail image tag.
1629 */
1630 public static function thumbCode(
1631 $row,
1632 $table,
1633 $field,
1634 $backPath = '',
1635 $thumbScript = '',
1636 $uploaddir = null,
1637 $abs = 0,
1638 $tparams = '',
1639 $size = '',
1640 $linkInfoPopup = true
1641 ) {
1642 // Check and parse the size parameter
1643 $size = trim($size);
1644 $sizeParts = [64, 64];
1645 if ($size) {
1646 $sizeParts = explode('x', $size . 'x' . $size);
1647 }
1648 $thumbData = '';
1649 $fileReferences = static::resolveFileReferences($table, $field, $row);
1650 // FAL references
1651 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
1652 if ($fileReferences !== null) {
1653 foreach ($fileReferences as $fileReferenceObject) {
1654 // Do not show previews of hidden references
1655 if ($fileReferenceObject->getProperty('hidden')) {
1656 continue;
1657 }
1658 $fileObject = $fileReferenceObject->getOriginalFile();
1659
1660 if ($fileObject->isMissing()) {
1661 $thumbData .= '<span class="label label-danger">'
1662 . htmlspecialchars(
1663 static::getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:warning.file_missing')
1664 )
1665 . '</span>&nbsp;' . htmlspecialchars($fileObject->getName()) . '<br />';
1666 continue;
1667 }
1668
1669 // Preview web image or media elements
1670 if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['thumbnails']
1671 && GeneralUtility::inList(
1672 $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] . ',' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['mediafile_ext'],
1673 $fileReferenceObject->getExtension()
1674 )
1675 ) {
1676 $processedImage = $fileObject->process(
1677 ProcessedFile::CONTEXT_IMAGECROPSCALEMASK,
1678 [
1679 'width' => $sizeParts[0],
1680 'height' => $sizeParts[1] . 'c',
1681 'crop' => $fileReferenceObject->getProperty('crop')
1682 ]
1683 );
1684 $imageUrl = $processedImage->getPublicUrl(true);
1685 $imgTag = '<img src="' . $imageUrl . '" '
1686 . 'width="' . $processedImage->getProperty('width') . '" '
1687 . 'height="' . $processedImage->getProperty('height') . '" '
1688 . 'alt="' . htmlspecialchars($fileReferenceObject->getName()) . '" />';
1689 } else {
1690 // Icon
1691 $imgTag = '<span title="' . htmlspecialchars($fileObject->getName()) . '">'
1692 . $iconFactory->getIconForResource($fileObject, Icon::SIZE_SMALL)->render()
1693 . '</span>';
1694 }
1695 if ($linkInfoPopup) {
1696 $onClick = 'top.launchView(\'_FILE\',\'' . (int)$fileObject->getUid() . '\'); return false;';
1697 $thumbData .= '<a href="#" onclick="' . htmlspecialchars($onClick) . '">' . $imgTag . '</a> ';
1698 } else {
1699 $thumbData .= $imgTag;
1700 }
1701 }
1702 } else {
1703 // Find uploaddir automatically
1704 if (is_null($uploaddir)) {
1705 $uploaddir = $GLOBALS['TCA'][$table]['columns'][$field]['config']['uploadfolder'];
1706 }
1707 $uploaddir = rtrim($uploaddir, '/');
1708 // Traverse files:
1709 $thumbs = GeneralUtility::trimExplode(',', $row[$field], true);
1710 $thumbData = '';
1711 foreach ($thumbs as $theFile) {
1712 if ($theFile) {
1713 $fileName = trim($uploaddir . '/' . $theFile, '/');
1714 try {
1715 /** @var File $fileObject */
1716 $fileObject = ResourceFactory::getInstance()->retrieveFileOrFolderObject($fileName);
1717 // Skip the resource if it's not of type AbstractFile. One case where this can happen if the
1718 // storage has been externally modified and the field value now points to a folder
1719 // instead of a file.
1720 if (!$fileObject instanceof AbstractFile) {
1721 continue;
1722 }
1723 if ($fileObject->isMissing()) {
1724 $thumbData .= '<span class="label label-danger">'
1725 . htmlspecialchars(static::getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:warning.file_missing'))
1726 . '</span>&nbsp;' . htmlspecialchars($fileObject->getName()) . '<br />';
1727 continue;
1728 }
1729 } catch (ResourceDoesNotExistException $exception) {
1730 $thumbData .= '<span class="label label-danger">'
1731 . htmlspecialchars(static::getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:warning.file_missing'))
1732 . '</span>&nbsp;' . htmlspecialchars($fileName) . '<br />';
1733 continue;
1734 }
1735
1736 $fileExtension = $fileObject->getExtension();
1737 if ($fileExtension == 'ttf'
1738 || GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], $fileExtension)
1739 ) {
1740 $imageUrl = $fileObject->process(
1741 ProcessedFile::CONTEXT_IMAGEPREVIEW,
1742 [
1743 'width' => $sizeParts[0],
1744 'height' => $sizeParts[1]
1745 ]
1746 )->getPublicUrl(true);
1747
1748 $image = '<img src="' . htmlspecialchars($imageUrl) . '" hspace="2" border="0" title="' . htmlspecialchars($fileObject->getName()) . '"' . $tparams . ' alt="" />';
1749 if ($linkInfoPopup) {
1750 $onClick = 'top.launchView(\'_FILE\', ' . GeneralUtility::quoteJSvalue($fileName) . ',\'\');return false;';
1751 $thumbData .= '<a href="#" onclick="' . htmlspecialchars($onClick) . '">' . $image . '</a> ';
1752 } else {
1753 $thumbData .= $image;
1754 }
1755 } else {
1756 // Gets the icon
1757 $fileIcon = '<span title="' . htmlspecialchars($fileObject->getName()) . '">'
1758 . $iconFactory->getIconForResource($fileObject, Icon::SIZE_SMALL)->render()
1759 . '</span>';
1760 if ($linkInfoPopup) {
1761 $onClick = 'top.launchView(\'_FILE\', ' . GeneralUtility::quoteJSvalue($fileName) . ',\'\'); return false;';
1762 $thumbData .= '<a href="#" onclick="' . htmlspecialchars($onClick) . '">' . $fileIcon . '</a> ';
1763 } else {
1764 $thumbData .= $fileIcon;
1765 }
1766 }
1767 }
1768 }
1769 }
1770 return $thumbData;
1771 }
1772
1773 /**
1774 * Returns title-attribute information for a page-record informing about id, alias, doktype, hidden, starttime, endtime, fe_group etc.
1775 *
1776 * @param array $row Input must be a page row ($row) with the proper fields set (be sure - send the full range of fields for the table)
1777 * @param string $perms_clause This is used to get the record path of the shortcut page, if any (and doktype==4)
1778 * @param bool $includeAttrib If $includeAttrib is set, then the 'title=""' attribute is wrapped about the return value, which is in any case htmlspecialchar()'ed already
1779 * @return string
1780 */
1781 public static function titleAttribForPages($row, $perms_clause = '', $includeAttrib = true)
1782 {
1783 $lang = static::getLanguageService();
1784 $parts = [];
1785 $parts[] = 'id=' . $row['uid'];
1786 if ($row['alias']) {
1787 $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['alias']['label']) . ' ' . $row['alias'];
1788 }
1789 if ($row['pid'] < 0) {
1790 $parts[] = 'v#1.' . $row['t3ver_id'];
1791 }
1792 switch (VersionState::cast($row['t3ver_state'])) {
1793 case new VersionState(VersionState::NEW_PLACEHOLDER):
1794 $parts[] = 'PLH WSID#' . $row['t3ver_wsid'];
1795 break;
1796 case new VersionState(VersionState::DELETE_PLACEHOLDER):
1797 $parts[] = 'Deleted element!';
1798 break;
1799 case new VersionState(VersionState::MOVE_PLACEHOLDER):
1800 $parts[] = 'NEW LOCATION (PLH) WSID#' . $row['t3ver_wsid'];
1801 break;
1802 case new VersionState(VersionState::MOVE_POINTER):
1803 $parts[] = 'OLD LOCATION (PNT) WSID#' . $row['t3ver_wsid'];
1804 break;
1805 case new VersionState(VersionState::NEW_PLACEHOLDER_VERSION):
1806 $parts[] = 'New element!';
1807 break;
1808 }
1809 if ($row['doktype'] == PageRepository::DOKTYPE_LINK) {
1810 $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['url']['label']) . ' ' . $row['url'];
1811 } elseif ($row['doktype'] == PageRepository::DOKTYPE_SHORTCUT) {
1812 if ($perms_clause) {
1813 $label = self::getRecordPath((int)$row['shortcut'], $perms_clause, 20);
1814 } else {
1815 $row['shortcut'] = (int)$row['shortcut'];
1816 $lRec = self::getRecordWSOL('pages', $row['shortcut'], 'title');
1817 $label = $lRec['title'] . ' (id=' . $row['shortcut'] . ')';
1818 }
1819 if ($row['shortcut_mode'] != PageRepository::SHORTCUT_MODE_NONE) {
1820 $label .= ', ' . $lang->sL($GLOBALS['TCA']['pages']['columns']['shortcut_mode']['label']) . ' '
1821 . $lang->sL(self::getLabelFromItemlist('pages', 'shortcut_mode', $row['shortcut_mode']));
1822 }
1823 $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['shortcut']['label']) . ' ' . $label;
1824 } elseif ($row['doktype'] == PageRepository::DOKTYPE_MOUNTPOINT) {
1825 if ($perms_clause) {
1826 $label = self::getRecordPath((int)$row['mount_pid'], $perms_clause, 20);
1827 } else {
1828 $lRec = self::getRecordWSOL('pages', (int)$row['mount_pid'], 'title');
1829 $label = $lRec['title'] . ' (id=' . $row['mount_pid'] . ')';
1830 }
1831 $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['mount_pid']['label']) . ' ' . $label;
1832 if ($row['mount_pid_ol']) {
1833 $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['mount_pid_ol']['label']);
1834 }
1835 }
1836 if ($row['nav_hide']) {
1837 $parts[] = rtrim($lang->sL($GLOBALS['TCA']['pages']['columns']['nav_hide']['label']), ':');
1838 }
1839 if ($row['hidden']) {
1840 $parts[] = $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.hidden');
1841 }
1842 if ($row['starttime']) {
1843 $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['starttime']['label'])
1844 . ' ' . self::dateTimeAge($row['starttime'], -1, 'date');
1845 }
1846 if ($row['endtime']) {
1847 $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['endtime']['label']) . ' '
1848 . self::dateTimeAge($row['endtime'], -1, 'date');
1849 }
1850 if ($row['fe_group']) {
1851 $fe_groups = [];
1852 foreach (GeneralUtility::intExplode(',', $row['fe_group']) as $fe_group) {
1853 if ($fe_group < 0) {
1854 $fe_groups[] = $lang->sL(self::getLabelFromItemlist('pages', 'fe_group', $fe_group));
1855 } else {
1856 $lRec = self::getRecordWSOL('fe_groups', $fe_group, 'title');
1857 $fe_groups[] = $lRec['title'];
1858 }
1859 }
1860 $label = implode(', ', $fe_groups);
1861 $parts[] = $lang->sL($GLOBALS['TCA']['pages']['columns']['fe_group']['label']) . ' ' . $label;
1862 }
1863 $out = htmlspecialchars(implode(' - ', $parts));
1864 return $includeAttrib ? 'title="' . $out . '"' : $out;
1865 }
1866
1867 /**
1868 * Returns the combined markup for Bootstraps tooltips
1869 *
1870 * @param array $row
1871 * @param string $table
1872 * @return string
1873 */
1874 public static function getRecordToolTip(array $row, $table = 'pages')
1875 {
1876 $toolTipText = self::getRecordIconAltText($row, $table);
1877 $toolTipCode = 'data-toggle="tooltip" data-title=" '
1878 . str_replace(' - ', '<br>', $toolTipText)
1879 . '" data-html="true" data-placement="right"';
1880 return $toolTipCode;
1881 }
1882
1883 /**
1884 * Returns title-attribute information for ANY record (from a table defined in TCA of course)
1885 * The included information depends on features of the table, but if hidden, starttime, endtime and fe_group fields are configured for, information about the record status in regard to these features are is included.
1886 * "pages" table can be used as well and will return the result of ->titleAttribForPages() for that page.
1887 *
1888 * @param array $row Table row; $row is a row from the table, $table
1889 * @param string $table Table name
1890 * @return string
1891 */
1892 public static function getRecordIconAltText($row, $table = 'pages')
1893 {
1894 if ($table == 'pages') {
1895 $out = self::titleAttribForPages($row, '', 0);
1896 } else {
1897 $out = !empty(trim($GLOBALS['TCA'][$table]['ctrl']['descriptionColumn'])) ? $row[$GLOBALS['TCA'][$table]['ctrl']['descriptionColumn']] . ' ' : '';
1898 $ctrl = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns'];
1899 // Uid is added
1900 $out .= 'id=' . $row['uid'];
1901 if ($table == 'pages' && $row['alias']) {
1902 $out .= ' / ' . $row['alias'];
1903 }
1904 if (static::isTableWorkspaceEnabled($table) && $row['pid'] < 0) {
1905 $out .= ' - v#1.' . $row['t3ver_id'];
1906 }
1907 if (static::isTableWorkspaceEnabled($table)) {
1908 switch (VersionState::cast($row['t3ver_state'])) {
1909 case new VersionState(VersionState::NEW_PLACEHOLDER):
1910 $out .= ' - PLH WSID#' . $row['t3ver_wsid'];
1911 break;
1912 case new VersionState(VersionState::DELETE_PLACEHOLDER):
1913 $out .= ' - Deleted element!';
1914 break;
1915 case new VersionState(VersionState::MOVE_PLACEHOLDER):
1916 $out .= ' - NEW LOCATION (PLH) WSID#' . $row['t3ver_wsid'];
1917 break;
1918 case new VersionState(VersionState::MOVE_POINTER):
1919 $out .= ' - OLD LOCATION (PNT) WSID#' . $row['t3ver_wsid'];
1920 break;
1921 case new VersionState(VersionState::NEW_PLACEHOLDER_VERSION):
1922 $out .= ' - New element!';
1923 break;
1924 }
1925 }
1926 // Hidden
1927 $lang = static::getLanguageService();
1928 if ($ctrl['disabled']) {
1929 $out .= $row[$ctrl['disabled']] ? ' - ' . $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.hidden') : '';
1930 }
1931 if ($ctrl['starttime']) {
1932 if ($row[$ctrl['starttime']] > $GLOBALS['EXEC_TIME']) {
1933 $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') . ')';
1934 }
1935 }
1936 if ($row[$ctrl['endtime']]) {
1937 $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') . ')';
1938 }
1939 }
1940 return htmlspecialchars($out);
1941 }
1942
1943 /**
1944 * Returns the label of the first found entry in an "items" array from $GLOBALS['TCA'] (tablename = $table/fieldname = $col) where the value is $key
1945 *
1946 * @param string $table Table name, present in $GLOBALS['TCA']
1947 * @param string $col Field name, present in $GLOBALS['TCA']
1948 * @param string $key items-array value to match
1949 * @return string Label for item entry
1950 */
1951 public static function getLabelFromItemlist($table, $col, $key)
1952 {
1953 // Check, if there is an "items" array:
1954 if (is_array($GLOBALS['TCA'][$table])
1955 && is_array($GLOBALS['TCA'][$table]['columns'][$col])
1956 && is_array($GLOBALS['TCA'][$table]['columns'][$col]['config']['items'])
1957 ) {
1958 // Traverse the items-array...
1959 foreach ($GLOBALS['TCA'][$table]['columns'][$col]['config']['items'] as $v) {
1960 // ... and return the first found label where the value was equal to $key
1961 if ((string)$v[1] === (string)$key) {
1962 return $v[0];
1963 }
1964 }
1965 }
1966 return '';
1967 }
1968
1969 /**
1970 * Return the label of a field by additionally checking TsConfig values
1971 *
1972 * @param int $pageId Page id
1973 * @param string $table Table name
1974 * @param string $column Field Name
1975 * @param string $key item value
1976 * @return string Label for item entry
1977 */
1978 public static function getLabelFromItemListMerged($pageId, $table, $column, $key)
1979 {
1980 $pageTsConfig = static::getPagesTSconfig($pageId);
1981 $label = '';
1982 if (is_array($pageTsConfig['TCEFORM.'])
1983 && is_array($pageTsConfig['TCEFORM.'][$table . '.'])
1984 && is_array($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.'])
1985 ) {
1986 if (is_array($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['addItems.'])
1987 && isset($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['addItems.'][$key])
1988 ) {
1989 $label = $pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['addItems.'][$key];
1990 } elseif (is_array($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['altLabels.'])
1991 && isset($pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['altLabels.'][$key])
1992 ) {
1993 $label = $pageTsConfig['TCEFORM.'][$table . '.'][$column . '.']['altLabels.'][$key];
1994 }
1995 }
1996 if (empty($label)) {
1997 $tcaValue = self::getLabelFromItemlist($table, $column, $key);
1998 if (!empty($tcaValue)) {
1999 $label = $tcaValue;
2000 }
2001 }
2002 return $label;
2003 }
2004
2005 /**
2006 * Splits the given key with commas and returns the list of all the localized items labels, separated by a comma.
2007 * NOTE: this does not take itemsProcFunc into account
2008 *
2009 * @param string $table Table name, present in TCA
2010 * @param string $column Field name
2011 * @param string $keyList Key or comma-separated list of keys.
2012 * @param array $columnTsConfig page TSConfig for $column (TCEMAIN.<table>.<column>)
2013 * @return string Comma-separated list of localized labels
2014 */
2015 public static function getLabelsFromItemsList($table, $column, $keyList, array $columnTsConfig = [])
2016 {
2017 // Check if there is an "items" array
2018 if (
2019 !isset($GLOBALS['TCA'][$table]['columns'][$column]['config']['items'])
2020 || !is_array($GLOBALS['TCA'][$table]['columns'][$column]['config']['items'])
2021 || $keyList === ''
2022 ) {
2023 return '';
2024 }
2025
2026 $keys = GeneralUtility::trimExplode(',', $keyList, true);
2027 $labels = [];
2028 // Loop on all selected values
2029 foreach ($keys as $key) {
2030 $label = null;
2031 if ($columnTsConfig) {
2032 // Check if label has been defined or redefined via pageTsConfig
2033 if (isset($columnTsConfig['addItems.'][$key])) {
2034 $label = $columnTsConfig['addItems.'][$key];
2035 } elseif (isset($columnTsConfig['altLabels.'][$key])) {
2036 $label = $columnTsConfig['altLabels.'][$key];
2037 }
2038 }
2039 if ($label === null) {
2040 // Otherwise lookup the label in TCA items list
2041 foreach ($GLOBALS['TCA'][$table]['columns'][$column]['config']['items'] as $itemConfiguration) {
2042 list($currentLabel, $currentKey) = $itemConfiguration;
2043 if ((string)$key === (string)$currentKey) {
2044 $label = $currentLabel;
2045 break;
2046 }
2047 }
2048 }
2049 if ($label !== null) {
2050 $labels[] = static::getLanguageService()->sL($label);
2051 }
2052 }
2053 return implode(', ', $labels);
2054 }
2055
2056 /**
2057 * Returns the label-value for fieldname $col in table, $table
2058 * If $printAllWrap is set (to a "wrap") then it's wrapped around the $col value IF THE COLUMN $col DID NOT EXIST in TCA!, eg. $printAllWrap = '<strong>|</strong>' and the fieldname was 'not_found_field' then the return value would be '<strong>not_found_field</strong>'
2059 *
2060 * @param string $table Table name, present in $GLOBALS['TCA']
2061 * @param string $col Field name
2062 * @return string or NULL if $col is not found in the TCA table
2063 */
2064 public static function getItemLabel($table, $col)
2065 {
2066 // Check if column exists
2067 if (is_array($GLOBALS['TCA'][$table]) && is_array($GLOBALS['TCA'][$table]['columns'][$col])) {
2068 return $GLOBALS['TCA'][$table]['columns'][$col]['label'];
2069 }
2070
2071 return null;
2072 }
2073
2074 /**
2075 * Replace field values in given row with values from the original language
2076 * if l10n_mode TCA settings require to do so.
2077 *
2078 * @param string $table Table name
2079 * @param array $row Row to fill with original language values
2080 * @return array Row with values from the original language
2081 */
2082 protected static function replaceL10nModeFields($table, array $row)
2083 {
2084 $originalUidField = isset($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])
2085 ? $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
2086 : '';
2087 if (empty($row[$originalUidField])) {
2088 return $row;
2089 }
2090
2091 $originalTable = self::getOriginalTranslationTable($table);
2092 $originalRow = self::getRecord($originalTable, $row[$originalUidField]);
2093 foreach ($row as $field => $_) {
2094 $l10n_mode = isset($GLOBALS['TCA'][$originalTable]['columns'][$field]['l10n_mode'])
2095 ? $GLOBALS['TCA'][$originalTable]['columns'][$field]['l10n_mode']
2096 : '';
2097 if ($l10n_mode === 'exclude') {
2098 $row[$field] = $originalRow[$field];
2099 }
2100 }
2101 return $row;
2102 }
2103
2104 /**
2105 * Returns the "title"-value in record, $row, from table, $table
2106 * The field(s) from which the value is taken is determined by the "ctrl"-entries 'label', 'label_alt' and 'label_alt_force'
2107 *
2108 * @param string $table Table name, present in TCA
2109 * @param array $row Row from table
2110 * @param bool $prep If set, result is prepared for output: The output is cropped to a limited length (depending on BE_USER->uc['titleLen']) and if no value is found for the title, '<em>[No title]</em>' is returned (localized). Further, the output is htmlspecialchars()'ed
2111 * @param bool $forceResult If set, the function always returns an output. If no value is found for the title, '[No title]' is returned (localized).
2112 * @return string
2113 */
2114 public static function getRecordTitle($table, $row, $prep = false, $forceResult = true)
2115 {
2116 $recordTitle = '';
2117 if (is_array($GLOBALS['TCA'][$table])) {
2118 // If configured, call userFunc
2119 if ($GLOBALS['TCA'][$table]['ctrl']['label_userFunc']) {
2120 $params['table'] = $table;
2121 $params['row'] = $row;
2122 $params['title'] = '';
2123 $params['options'] = $GLOBALS['TCA'][$table]['ctrl']['label_userFunc_options'] ?? [];
2124
2125 // Create NULL-reference
2126 $null = null;
2127 GeneralUtility::callUserFunction($GLOBALS['TCA'][$table]['ctrl']['label_userFunc'], $params, $null);
2128 $recordTitle = $params['title'];
2129 } else {
2130 if (is_array($row)) {
2131 $row = self::replaceL10nModeFields($table, $row);
2132 }
2133
2134 // No userFunc: Build label
2135 $recordTitle = self::getProcessedValue(
2136 $table,
2137 $GLOBALS['TCA'][$table]['ctrl']['label'],
2138 $row[$GLOBALS['TCA'][$table]['ctrl']['label']],
2139 0,
2140 0,
2141 false,
2142 $row['uid'],
2143 $forceResult
2144 );
2145 if ($GLOBALS['TCA'][$table]['ctrl']['label_alt']
2146 && ($GLOBALS['TCA'][$table]['ctrl']['label_alt_force'] || (string)$recordTitle === '')
2147 ) {
2148 $altFields = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$table]['ctrl']['label_alt'], true);
2149 $tA = [];
2150 if (!empty($recordTitle)) {
2151 $tA[] = $recordTitle;
2152 }
2153 foreach ($altFields as $fN) {
2154 $recordTitle = trim(strip_tags($row[$fN]));
2155 if ((string)$recordTitle !== '') {
2156 $recordTitle = self::getProcessedValue($table, $fN, $recordTitle, 0, 0, false, $row['uid']);
2157 if (!$GLOBALS['TCA'][$table]['ctrl']['label_alt_force']) {
2158 break;
2159 }
2160 $tA[] = $recordTitle;
2161 }
2162 }
2163 if ($GLOBALS['TCA'][$table]['ctrl']['label_alt_force']) {
2164 $recordTitle = implode(', ', $tA);
2165 }
2166 }
2167 }
2168 // If the current result is empty, set it to '[No title]' (localized) and prepare for output if requested
2169 if ($prep || $forceResult) {
2170 if ($prep) {
2171 $recordTitle = self::getRecordTitlePrep($recordTitle);
2172 }
2173 if (trim($recordTitle) === '') {
2174 $recordTitle = self::getNoRecordTitle($prep);
2175 }
2176 }
2177 }
2178
2179 return $recordTitle;
2180 }
2181
2182 /**
2183 * Crops a title string to a limited length and if it really was cropped, wrap it in a <span title="...">|</span>,
2184 * which offers a tooltip with the original title when moving mouse over it.
2185 *
2186 * @param string $title The title string to be cropped
2187 * @param int $titleLength Crop title after this length - if not set, BE_USER->uc['titleLen'] is used
2188 * @return string The processed title string, wrapped in <span title="...">|</span> if cropped
2189 */
2190 public static function getRecordTitlePrep($title, $titleLength = 0)
2191 {
2192 // If $titleLength is not a valid positive integer, use BE_USER->uc['titleLen']:
2193 if (!$titleLength || !MathUtility::canBeInterpretedAsInteger($titleLength) || $titleLength < 0) {
2194 $titleLength = static::getBackendUserAuthentication()->uc['titleLen'];
2195 }
2196 $titleOrig = htmlspecialchars($title);
2197 $title = htmlspecialchars(GeneralUtility::fixed_lgd_cs($title, $titleLength));
2198 // If title was cropped, offer a tooltip:
2199 if ($titleOrig != $title) {
2200 $title = '<span title="' . $titleOrig . '">' . $title . '</span>';
2201 }
2202 return $title;
2203 }
2204
2205 /**
2206 * Get a localized [No title] string, wrapped in <em>|</em> if $prep is TRUE.
2207 *
2208 * @param bool $prep Wrap result in <em>|</em>
2209 * @return string Localized [No title] string
2210 */
2211 public static function getNoRecordTitle($prep = false)
2212 {
2213 $noTitle = '[' .
2214 htmlspecialchars(static::getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.no_title'))
2215 . ']';
2216 if ($prep) {
2217 $noTitle = '<em>' . $noTitle . '</em>';
2218 }
2219 return $noTitle;
2220 }
2221
2222 /**
2223 * Returns a human readable output of a value from a record
2224 * For instance a database record relation would be looked up to display the title-value of that record. A checkbox with a "1" value would be "Yes", etc.
2225 * $table/$col is tablename and fieldname
2226 * REMEMBER to pass the output through htmlspecialchars() if you output it to the browser! (To protect it from XSS attacks and be XHTML compliant)
2227 *
2228 * @param string $table Table name, present in TCA
2229 * @param string $col Field name, present in TCA
2230 * @param string $value The value of that field from a selected record
2231 * @param int $fixed_lgd_chars The max amount of characters the value may occupy
2232 * @param bool $defaultPassthrough Flag means that values for columns that has no conversion will just be pass through directly (otherwise cropped to 200 chars or returned as "N/A")
2233 * @param bool $noRecordLookup If set, no records will be looked up, UIDs are just shown.
2234 * @param int $uid Uid of the current record
2235 * @param bool $forceResult If BackendUtility::getRecordTitle is used to process the value, this parameter is forwarded.
2236 * @param int $pid Optional page uid is used to evaluate page TSConfig for the given field
2237 * @throws \InvalidArgumentException
2238 * @return string|NULL
2239 */
2240 public static function getProcessedValue(
2241 $table,
2242 $col,
2243 $value,
2244 $fixed_lgd_chars = 0,
2245 $defaultPassthrough = false,
2246 $noRecordLookup = false,
2247 $uid = 0,
2248 $forceResult = true,
2249 $pid = 0
2250 ) {
2251 if ($col === 'uid') {
2252 // uid is not in TCA-array
2253 return $value;
2254 }
2255 // Check if table and field is configured
2256 if (!is_array($GLOBALS['TCA'][$table]) || !is_array($GLOBALS['TCA'][$table]['columns'][$col])) {
2257 return null;
2258 }
2259 // Depending on the fields configuration, make a meaningful output value.
2260 $theColConf = $GLOBALS['TCA'][$table]['columns'][$col]['config'];
2261 /*****************
2262 *HOOK: pre-processing the human readable output from a record
2263 ****************/
2264 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['preProcessValue'])) {
2265 // Create NULL-reference
2266 $null = null;
2267 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['preProcessValue'] as $_funcRef) {
2268 GeneralUtility::callUserFunction($_funcRef, $theColConf, $null);
2269 }
2270 }
2271 $l = '';
2272 $lang = static::getLanguageService();
2273 switch ((string)$theColConf['type']) {
2274 case 'radio':
2275 $l = self::getLabelFromItemlist($table, $col, $value);
2276 $l = $lang->sL($l);
2277 break;
2278 case 'inline':
2279 case 'select':
2280 if ($theColConf['MM']) {
2281 if ($uid) {
2282 // Display the title of MM related records in lists
2283 if ($noRecordLookup) {
2284 $MMfield = $theColConf['foreign_table'] . '.uid';
2285 } else {
2286 $MMfields = [$theColConf['foreign_table'] . '.' . $GLOBALS['TCA'][$theColConf['foreign_table']]['ctrl']['label']];
2287 foreach (GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$theColConf['foreign_table']]['ctrl']['label_alt'], true) as $f) {
2288 $MMfields[] = $theColConf['foreign_table'] . '.' . $f;
2289 }
2290 $MMfield = implode(',', $MMfields);
2291 }
2292 /** @var $dbGroup RelationHandler */
2293 $dbGroup = GeneralUtility::makeInstance(RelationHandler::class);
2294 $dbGroup->start(
2295 $value,
2296 $theColConf['foreign_table'],
2297 $theColConf['MM'],
2298 $uid,
2299 $table,
2300 $theColConf
2301 );
2302 $selectUids = $dbGroup->tableArray[$theColConf['foreign_table']];
2303 if (is_array($selectUids) && !empty($selectUids)) {
2304 $queryBuilder = static::getQueryBuilderForTable($theColConf['foreign_table']);
2305 $queryBuilder->getRestrictions()
2306 ->removeAll()
2307 ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
2308
2309 $result = $queryBuilder
2310 ->select('uid', $MMfield)
2311 ->from($theColConf['foreign_table'])
2312 ->where(
2313 $queryBuilder->expr()->in(
2314 'uid',
2315 $queryBuilder->createNamedParameter($selectUids, Connection::PARAM_INT_ARRAY)
2316 )
2317 )
2318 ->execute();
2319
2320 $mmlA = [];
2321 while ($MMrow = $result->fetch()) {
2322 // Keep sorting of $selectUids
2323 $selectedUid = array_search($MMrow['uid'], $selectUids);
2324 $mmlA[$selectedUid] = $MMrow['uid'];
2325 if (!$noRecordLookup) {
2326 $mmlA[$selectedUid] = static::getRecordTitle(
2327 $theColConf['foreign_table'],
2328 $MMrow,
2329 false,
2330 $forceResult
2331 );
2332 }
2333 }
2334
2335 if (!empty($mmlA)) {
2336 ksort($mmlA);
2337 $l = implode('; ', $mmlA);
2338 } else {
2339 $l = 'N/A';
2340 }
2341 } else {
2342 $l = 'N/A';
2343 }
2344 } else {
2345 $l = 'N/A';
2346 }
2347 } else {
2348 $columnTsConfig = [];
2349 if ($pid) {
2350 $pageTsConfig = self::getPagesTSconfig($pid);
2351 if (isset($pageTsConfig['TCEFORM.'][$table . '.'][$col . '.']) && is_array($pageTsConfig['TCEFORM.'][$table . '.'][$col . '.'])) {
2352 $columnTsConfig = $pageTsConfig['TCEFORM.'][$table . '.'][$col . '.'];
2353 }
2354 }
2355 $l = self::getLabelsFromItemsList($table, $col, $value, $columnTsConfig);
2356 if ($theColConf['foreign_table'] && !$l && $GLOBALS['TCA'][$theColConf['foreign_table']]) {
2357 if ($noRecordLookup) {
2358 $l = $value;
2359 } else {
2360 $rParts = [];
2361 if ($uid && isset($theColConf['foreign_field']) && $theColConf['foreign_field'] !== '') {
2362 $queryBuilder = static::getQueryBuilderForTable($theColConf['foreign_table']);
2363 $queryBuilder->getRestrictions()
2364 ->removeAll()
2365 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
2366 $constraints = [
2367 $queryBuilder->expr()->eq(
2368 $theColConf['foreign_field'],
2369 $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
2370 )
2371 ];
2372
2373 if (!empty($theColConf['foreign_table_field'])) {
2374 $constraints[] = $queryBuilder->expr()->eq(
2375 $theColConf['foreign_table_field'],
2376 $queryBuilder->createNamedParameter($table, \PDO::PARAM_STR)
2377 );
2378 }
2379
2380 // Add additional where clause if foreign_match_fields are defined
2381 $foreignMatchFields = [];
2382 if (is_array($theColConf['foreign_match_fields'])) {
2383 $foreignMatchFields = $theColConf['foreign_match_fields'];
2384 }
2385
2386 foreach ($foreignMatchFields as $matchField => $matchValue) {
2387 $constraints[] = $queryBuilder->expr()->eq(
2388 $matchField,
2389 $queryBuilder->createNamedParameter($matchValue)
2390 );
2391 }
2392
2393 $result = $queryBuilder
2394 ->select('*')
2395 ->from($theColConf['foreign_table'])
2396 ->where(...$constraints)
2397 ->execute();
2398
2399 while ($record = $result->fetch()) {
2400 $rParts[] = $record['uid'];
2401 }
2402 }
2403 if (empty($rParts)) {
2404 $rParts = GeneralUtility::trimExplode(',', $value, true);
2405 }
2406 $lA = [];
2407 foreach ($rParts as $rVal) {
2408 $rVal = (int)$rVal;
2409 $r = self::getRecordWSOL($theColConf['foreign_table'], $rVal);
2410 if (is_array($r)) {
2411 $lA[] = $lang->sL($theColConf['foreign_table_prefix'])
2412 . self::getRecordTitle($theColConf['foreign_table'], $r, false, $forceResult);
2413 } else {
2414 $lA[] = $rVal ? '[' . $rVal . '!]' : '';
2415 }
2416 }
2417 $l = implode(', ', $lA);
2418 }
2419 }
2420 if (empty($l) && !empty($value)) {
2421 // Use plain database value when label is empty
2422 $l = $value;
2423 }
2424 }
2425 break;
2426 case 'group':
2427 // resolve the titles for DB records
2428 if ($theColConf['internal_type'] === 'db') {
2429 if ($theColConf['MM']) {
2430 if ($uid) {
2431 // Display the title of MM related records in lists
2432 if ($noRecordLookup) {
2433 $MMfield = $theColConf['foreign_table'] . '.uid';
2434 } else {
2435 $MMfields = [$theColConf['foreign_table'] . '.' . $GLOBALS['TCA'][$theColConf['foreign_table']]['ctrl']['label']];
2436 $altLabelFields = explode(
2437 ',',
2438 $GLOBALS['TCA'][$theColConf['foreign_table']]['ctrl']['label_alt']
2439 );
2440 foreach ($altLabelFields as $f) {
2441 $f = trim($f);
2442 if ($f !== '') {
2443 $MMfields[] = $theColConf['foreign_table'] . '.' . $f;
2444 }
2445 }
2446 $MMfield = implode(',', $MMfields);
2447 }
2448 /** @var $dbGroup RelationHandler */
2449 $dbGroup = GeneralUtility::makeInstance(RelationHandler::class);
2450 $dbGroup->start(
2451 $value,
2452 $theColConf['foreign_table'],
2453 $theColConf['MM'],
2454 $uid,
2455 $table,
2456 $theColConf
2457 );
2458 $selectUids = $dbGroup->tableArray[$theColConf['foreign_table']];
2459 if (!empty($selectUids) && is_array($selectUids)) {
2460 $queryBuilder = static::getQueryBuilderForTable($theColConf['foreign_table']);
2461 $queryBuilder->getRestrictions()
2462 ->removeAll()
2463 ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
2464
2465 $result = $queryBuilder
2466 ->select('uid', $MMfield)
2467 ->from($theColConf['foreign_table'])
2468 ->where(
2469 $queryBuilder->expr()->in(
2470 'uid',
2471 $queryBuilder->createNamedParameter(
2472 $selectUids,
2473 Connection::PARAM_INT_ARRAY
2474 )
2475 )
2476 )
2477 ->execute();
2478
2479 $mmlA = [];
2480 while ($MMrow = $result->fetch()) {
2481 // Keep sorting of $selectUids
2482 $selectedUid = array_search($MMrow['uid'], $selectUids);
2483 $mmlA[$selectedUid] = $MMrow['uid'];
2484 if (!$noRecordLookup) {
2485 $mmlA[$selectedUid] = static::getRecordTitle(
2486 $theColConf['foreign_table'],
2487 $MMrow,
2488 false,
2489 $forceResult
2490 );
2491 }
2492 }
2493
2494 if (!empty($mmlA)) {
2495 ksort($mmlA);
2496 $l = implode('; ', $mmlA);
2497 } else {
2498 $l = 'N/A';
2499 }
2500 } else {
2501 $l = 'N/A';
2502 }
2503 } else {
2504 $l = 'N/A';
2505 }
2506 } else {
2507 $finalValues = [];
2508 $relationTableName = $theColConf['allowed'];
2509 $explodedValues = GeneralUtility::trimExplode(',', $value, true);
2510
2511 foreach ($explodedValues as $explodedValue) {
2512 if (MathUtility::canBeInterpretedAsInteger($explodedValue)) {
2513 $relationTableNameForField = $relationTableName;
2514 } else {
2515 list($relationTableNameForField, $explodedValue) = self::splitTable_Uid($explodedValue);
2516 }
2517
2518 $relationRecord = static::getRecordWSOL($relationTableNameForField, $explodedValue);
2519 $finalValues[] = static::getRecordTitle($relationTableNameForField, $relationRecord);
2520 }
2521 $l = implode(', ', $finalValues);
2522 }
2523 } else {
2524 $l = implode(', ', GeneralUtility::trimExplode(',', $value, true));
2525 }
2526 break;
2527 case 'check':
2528 if (!is_array($theColConf['items']) || count($theColConf['items']) === 1) {
2529 $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');
2530 } else {
2531 $lA = [];
2532 foreach ($theColConf['items'] as $key => $val) {
2533 if ($value & pow(2, $key)) {
2534 $lA[] = $lang->sL($val[0]);
2535 }
2536 }
2537 $l = implode(', ', $lA);
2538 }
2539 break;
2540 case 'input':
2541 // Hide value 0 for dates, but show it for everything else
2542 if (isset($value)) {
2543 if (GeneralUtility::inList($theColConf['eval'], 'date')) {
2544 // Handle native date field
2545 if (isset($theColConf['dbType']) && $theColConf['dbType'] === 'date') {
2546 $dateTimeFormats = QueryHelper::getDateTimeFormats();
2547 $emptyValue = $dateTimeFormats['date']['empty'];
2548 $value = $value !== $emptyValue ? strtotime($value) : 0;
2549 }
2550 if (!empty($value)) {
2551 $ageSuffix = '';
2552 $dateColumnConfiguration = $GLOBALS['TCA'][$table]['columns'][$col]['config'];
2553 $ageDisplayKey = 'disableAgeDisplay';
2554
2555 // generate age suffix as long as not explicitly suppressed
2556 if (!isset($dateColumnConfiguration[$ageDisplayKey])
2557 // non typesafe comparison on intention
2558 || $dateColumnConfiguration[$ageDisplayKey] == false
2559 ) {
2560 $ageSuffix = ' (' . ($GLOBALS['EXEC_TIME'] - $value > 0 ? '-' : '')
2561 . self::calcAge(
2562 abs(($GLOBALS['EXEC_TIME'] - $value)),
2563 $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears')
2564 )
2565 . ')';
2566 }
2567
2568 $l = self::date($value) . $ageSuffix;
2569 }
2570 } elseif (GeneralUtility::inList($theColConf['eval'], 'time')) {
2571 if (!empty($value)) {
2572 $l = self::time($value, false);
2573 }
2574 } elseif (GeneralUtility::inList($theColConf['eval'], 'timesec')) {
2575 if (!empty($value)) {
2576 $l = self::time($value);
2577 }
2578 } elseif (GeneralUtility::inList($theColConf['eval'], 'datetime')) {
2579 // Handle native date/time field
2580 if (isset($theColConf['dbType']) && $theColConf['dbType'] === 'datetime') {
2581 $dateTimeFormats = QueryHelper::getDateTimeFormats();
2582 $emptyValue = $dateTimeFormats['datetime']['empty'];
2583 $value = $value !== $emptyValue ? strtotime($value) : 0;
2584 }
2585 if (!empty($value)) {
2586 $l = self::datetime($value);
2587 }
2588 } else {
2589 $l = $value;
2590 }
2591 }
2592 break;
2593 case 'flex':
2594 $l = strip_tags($value);
2595 break;
2596 default:
2597 if ($defaultPassthrough) {
2598 $l = $value;
2599 } elseif ($theColConf['MM']) {
2600 $l = 'N/A';
2601 } elseif ($value) {
2602 $l = GeneralUtility::fixed_lgd_cs(strip_tags($value), 200);
2603 }
2604 }
2605 // If this field is a password field, then hide the password by changing it to a random number of asterisk (*)
2606 if (stristr($theColConf['eval'], 'password')) {
2607 $l = '';
2608 $randomNumber = rand(5, 12);
2609 for ($i = 0; $i < $randomNumber; $i++) {
2610 $l .= '*';
2611 }
2612 }
2613 /*****************
2614 *HOOK: post-processing the human readable output from a record
2615 ****************/
2616 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['postProcessValue'])) {
2617 // Create NULL-reference
2618 $null = null;
2619 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['postProcessValue'] as $_funcRef) {
2620 $params = [
2621 'value' => $l,
2622 'colConf' => $theColConf
2623 ];
2624 $l = GeneralUtility::callUserFunction($_funcRef, $params, $null);
2625 }
2626 }
2627 if ($fixed_lgd_chars) {
2628 return GeneralUtility::fixed_lgd_cs($l, $fixed_lgd_chars);
2629 } else {
2630 return $l;
2631 }
2632 }
2633
2634 /**
2635 * Same as ->getProcessedValue() but will go easy on fields like "tstamp" and "pid" which are not configured in TCA - they will be formatted by this function instead.
2636 *
2637 * @param string $table Table name, present in TCA
2638 * @param string $fN Field name
2639 * @param string $fV Field value
2640 * @param int $fixed_lgd_chars The max amount of characters the value may occupy
2641 * @param int $uid Uid of the current record
2642 * @param bool $forceResult If BackendUtility::getRecordTitle is used to process the value, this parameter is forwarded.
2643 * @param int $pid Optional page uid is used to evaluate page TSConfig for the given field
2644 * @return string
2645 * @see getProcessedValue()
2646 */
2647 public static function getProcessedValueExtra(
2648 $table,
2649 $fN,
2650 $fV,
2651 $fixed_lgd_chars = 0,
2652 $uid = 0,
2653 $forceResult = true,
2654 $pid = 0
2655 ) {
2656 $fVnew = self::getProcessedValue($table, $fN, $fV, $fixed_lgd_chars, 1, 0, $uid, $forceResult, $pid);
2657 if (!isset($fVnew)) {
2658 if (is_array($GLOBALS['TCA'][$table])) {
2659 if ($fN == $GLOBALS['TCA'][$table]['ctrl']['tstamp'] || $fN == $GLOBALS['TCA'][$table]['ctrl']['crdate']) {
2660 $fVnew = self::datetime($fV);
2661 } elseif ($fN == 'pid') {
2662 // Fetches the path with no regard to the users permissions to select pages.
2663 $fVnew = self::getRecordPath($fV, '1=1', 20);
2664 } else {
2665 $fVnew = $fV;
2666 }
2667 }
2668 }
2669 return $fVnew;
2670 }
2671
2672 /**
2673 * Returns fields for a table, $table, which would typically be interesting to select
2674 * This includes uid, the fields defined for title, icon-field.
2675 * Returned as a list ready for query ($prefix can be set to eg. "pages." if you are selecting from the pages table and want the table name prefixed)
2676 *
2677 * @param string $table Table name, present in $GLOBALS['TCA']
2678 * @param string $prefix Table prefix
2679 * @param array $fields Preset fields (must include prefix if that is used)
2680 * @return string List of fields.
2681 */
2682 public static function getCommonSelectFields($table, $prefix = '', $fields = [])
2683 {
2684 $fields[] = $prefix . 'uid';
2685 if (isset($GLOBALS['TCA'][$table]['ctrl']['label']) && $GLOBALS['TCA'][$table]['ctrl']['label'] != '') {
2686 $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['label'];
2687 }
2688 if ($GLOBALS['TCA'][$table]['ctrl']['label_alt']) {
2689 $secondFields = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$table]['ctrl']['label_alt'], true);
2690 foreach ($secondFields as $fieldN) {
2691 $fields[] = $prefix . $fieldN;
2692 }
2693 }
2694 if (static::isTableWorkspaceEnabled($table)) {
2695 $fields[] = $prefix . 't3ver_id';
2696 $fields[] = $prefix . 't3ver_state';
2697 $fields[] = $prefix . 't3ver_wsid';
2698 $fields[] = $prefix . 't3ver_count';
2699 }
2700 if ($GLOBALS['TCA'][$table]['ctrl']['selicon_field']) {
2701 $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['selicon_field'];
2702 }
2703 if ($GLOBALS['TCA'][$table]['ctrl']['typeicon_column']) {
2704 $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['typeicon_column'];
2705 }
2706 if (is_array($GLOBALS['TCA'][$table]['ctrl']['enablecolumns'])) {
2707 if ($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled']) {
2708 $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'];
2709 }
2710 if ($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['starttime']) {
2711 $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['starttime'];
2712 }
2713 if ($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['endtime']) {
2714 $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['endtime'];
2715 }
2716 if ($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['fe_group']) {
2717 $fields[] = $prefix . $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['fe_group'];
2718 }
2719 }
2720 return implode(',', array_unique($fields));
2721 }
2722
2723 /**
2724 * Makes a form for configuration of some values based on configuration found in the array $configArray,
2725 * with default values from $defaults and a data-prefix $dataPrefix
2726 * <form>-tags must be supplied separately
2727 * Needs more documentation and examples, in particular syntax for configuration array. See Inside TYPO3.
2728 * That's were you can expect to find example, if anywhere.
2729 *
2730 * @param array $configArray Field configuration code.
2731 * @param array $defaults Defaults
2732 * @param string $dataPrefix Prefix for formfields
2733 * @return string HTML for a form.
2734 * @deprecated since TYPO3 CMS 8, will be removed in TYPO3 CMS 9.
2735 */
2736 public static function makeConfigForm($configArray, $defaults, $dataPrefix)
2737 {
2738 GeneralUtility::logDeprecatedFunction();
2739 $params = $defaults;
2740 $lines = [];
2741 if (is_array($configArray)) {
2742 foreach ($configArray as $fname => $config) {
2743 if (is_array($config)) {
2744 $lines[$fname] = '<strong>' . htmlspecialchars($config[1]) . '</strong><br />';
2745 $lines[$fname] .= $config[2] . '<br />';
2746 switch ($config[0]) {
2747 case 'string':
2748
2749 case 'short':
2750 $formEl = '<input type="text" name="' . $dataPrefix . '[' . $fname . ']" value="' . $params[$fname] . '"' . static::getDocumentTemplate()->formWidth(($config[0] == 'short' ? 24 : 48)) . ' />';
2751 break;
2752 case 'check':
2753 $formEl = '<input type="hidden" name="' . $dataPrefix . '[' . $fname . ']" value="0" /><input type="checkbox" name="' . $dataPrefix . '[' . $fname . ']" value="1"' . ($params[$fname] ? ' checked="checked"' : '') . ' />';
2754 break;
2755 case 'comment':
2756 $formEl = '';
2757 break;
2758 case 'select':
2759 $opt = [];
2760 foreach ($config[3] as $k => $v) {
2761 $opt[] = '<option value="' . htmlspecialchars($k) . '"' . ($params[$fname] == $k ? ' selected="selected"' : '') . '>' . htmlspecialchars($v) . '</option>';
2762 }
2763 $formEl = '<select name="' . $dataPrefix . '[' . $fname . ']">'
2764 . implode('', $opt) . '</select>';
2765 break;
2766 default:
2767 $formEl = '<strong>Should not happen. Bug in config.</strong>';
2768 }
2769 $lines[$fname] .= $formEl;
2770 $lines[$fname] .= '<br /><br />';
2771 } else {
2772 $lines[$fname] = '<hr />';
2773 if ($config) {
2774 $lines[$fname] .= '<strong>' . strtoupper(htmlspecialchars($config)) . '</strong><br />';
2775 }
2776 if ($config) {
2777 $lines[$fname] .= '<br />';
2778 }
2779 }
2780 }
2781 }
2782 $out = implode('', $lines);
2783 $out .= '<input class="btn btn-default" type="submit" name="submit" value="Update configuration" />';
2784 return $out;
2785 }
2786
2787 /*******************************************
2788 *
2789 * Backend Modules API functions
2790 *
2791 *******************************************/
2792
2793 /**
2794 * Returns CSH help text (description), if configured for, as an array (title, description)
2795 *
2796 * @param string $table Table name
2797 * @param string $field Field name
2798 * @return array With keys 'description' (raw, as available in locallang), 'title' (optional), 'moreInfo'
2799 */
2800 public static function helpTextArray($table, $field)
2801 {
2802 if (!isset($GLOBALS['TCA_DESCR'][$table]['columns'])) {
2803 static::getLanguageService()->loadSingleTableDescription($table);
2804 }
2805 $output = [
2806 'description' => null,
2807 'title' => null,