89076f6043d182e51d17108c2d93535200a62c7e
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / Utility / FormEngineUtility.php
1 <?php
2 namespace TYPO3\CMS\Backend\Form\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\Core\Imaging\Icon;
18 use TYPO3\CMS\Core\Imaging\IconFactory;
19 use TYPO3\CMS\Core\Utility\ArrayUtility;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21 use TYPO3\CMS\Core\Utility\PathUtility;
22 use TYPO3\CMS\Core\Utility\StringUtility;
23 use TYPO3\CMS\Lang\LanguageService;
24 use TYPO3\CMS\Backend\Utility\BackendUtility;
25 use TYPO3\CMS\Core\Database\DatabaseConnection;
26 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
27
28 /**
29 * This is a static, internal and intermediate helper class for various
30 * FormEngine related tasks.
31 *
32 * This class was introduced to help disentangling FormEngine and
33 * its sub classes. It MUST NOT be used in other extensions and will
34 * change or vanish without further notice.
35 *
36 * @internal
37 * @todo: These helpers are target to be dropped if further FormEngine refactoring is done
38 */
39 class FormEngineUtility
40 {
41 /**
42 * Whitelist that allows TCA field configuration to be overridden by TSconfig
43 *
44 * @see overrideFieldConf()
45 * @var array
46 */
47 protected static $allowOverrideMatrix = array(
48 'input' => array('size', 'max', 'readOnly'),
49 'text' => array('cols', 'rows', 'wrap', 'readOnly'),
50 'check' => array('cols', 'showIfRTE', 'readOnly'),
51 'select' => array('size', 'autoSizeMax', 'maxitems', 'minitems', 'readOnly', 'treeConfig'),
52 'group' => array('size', 'autoSizeMax', 'max_size', 'show_thumbs', 'maxitems', 'minitems', 'disable_controls', 'readOnly'),
53 'inline' => array('appearance', 'behaviour', 'foreign_label', 'foreign_selector', 'foreign_unique', 'maxitems', 'minitems', 'size', 'autoSizeMax', 'symmetric_label', 'readOnly'),
54 );
55
56 /**
57 * Overrides the TCA field configuration by TSconfig settings.
58 *
59 * Example TSconfig: TCEform.<table>.<field>.config.appearance.useSortable = 1
60 * This overrides the setting in $GLOBALS['TCA'][<table>]['columns'][<field>]['config']['appearance']['useSortable'].
61 *
62 * @param array $fieldConfig $GLOBALS['TCA'] field configuration
63 * @param array $TSconfig TSconfig
64 * @return array Changed TCA field configuration
65 * @internal
66 */
67 public static function overrideFieldConf($fieldConfig, $TSconfig)
68 {
69 if (is_array($TSconfig)) {
70 $TSconfig = GeneralUtility::removeDotsFromTS($TSconfig);
71 $type = $fieldConfig['type'];
72 if (is_array($TSconfig['config']) && is_array(static::$allowOverrideMatrix[$type])) {
73 // Check if the keys in TSconfig['config'] are allowed to override TCA field config:
74 foreach ($TSconfig['config'] as $key => $_) {
75 if (!in_array($key, static::$allowOverrideMatrix[$type], true)) {
76 unset($TSconfig['config'][$key]);
77 }
78 }
79 // Override $GLOBALS['TCA'] field config by remaining TSconfig['config']:
80 if (!empty($TSconfig['config'])) {
81 ArrayUtility::mergeRecursiveWithOverrule($fieldConfig, $TSconfig['config']);
82 }
83 }
84 }
85 return $fieldConfig;
86 }
87
88 /**
89 * Returns TSconfig for given table and row
90 *
91 * @param string $table The table name
92 * @param array $row The table row - Must at least contain the "uid" value, even if "NEW..." string.
93 * The "pid" field is important as well, negative values will be interpreted as pointing to a record from the same table.
94 * @param string $field Optionally specify the field name as well. In that case the TSconfig for this field is returned.
95 * @return mixed The TSconfig values - probably in an array
96 * @internal
97 */
98 public static function getTSconfigForTableRow($table, $row, $field = '')
99 {
100 static $cache;
101 if (is_null($cache)) {
102 $cache = array();
103 }
104 $cacheIdentifier = $table . ':' . $row['uid'];
105 if (!isset($cache[$cacheIdentifier])) {
106 $cache[$cacheIdentifier] = BackendUtility::getTCEFORM_TSconfig($table, $row);
107 }
108 if ($field) {
109 return $cache[$cacheIdentifier][$field];
110 }
111 return $cache[$cacheIdentifier];
112 }
113
114 /**
115 * Renders the $icon, supports a filename for skinImg or sprite-icon-name
116 *
117 * @param string $icon The icon passed, could be a file-reference or a sprite Icon name
118 * @param string $alt Alt attribute of the icon returned
119 * @param string $title Title attribute of the icon return
120 * @return string A tag representing to show the asked icon
121 * @internal
122 */
123 public static function getIconHtml($icon, $alt = '', $title = '')
124 {
125 $icon = (string)$icon;
126 $iconFile = '';
127 $iconInfo = false;
128
129 if (StringUtility::beginsWith($icon, 'EXT:')) {
130 $absoluteFilePath = GeneralUtility::getFileAbsFileName($icon);
131 if (!empty($absoluteFilePath)) {
132 $iconFile = '../' . PathUtility::stripPathSitePrefix($absoluteFilePath);
133 $iconInfo = (StringUtility::endsWith($absoluteFilePath, '.svg'))
134 ? true
135 : getimagesize($absoluteFilePath);
136 }
137 } elseif (StringUtility::beginsWith($icon, '../')) {
138 // @TODO: this is special modList, files from folders and selicon
139 $iconFile = GeneralUtility::resolveBackPath($icon);
140 if (is_file(PATH_site . GeneralUtility::resolveBackPath(substr($icon, 3)))) {
141 $iconInfo = (StringUtility::endsWith($icon, '.svg'))
142 ? true
143 : getimagesize((PATH_site . GeneralUtility::resolveBackPath(substr($icon, 3))));
144 }
145 }
146
147 if ($iconInfo !== false && is_file(GeneralUtility::resolveBackPath(PATH_typo3 . $iconFile))) {
148 return '<img'
149 . ' src="' . htmlspecialchars($iconFile) . '"'
150 . ' alt="' . htmlspecialchars($alt) . '" '
151 . ($title ? 'title="' . htmlspecialchars($title) . '"' : '')
152 . ' />';
153 }
154
155 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
156 return '<span alt="' . htmlspecialchars($alt) . '" title="' . htmlspecialchars($title) . '">'
157 . $iconFactory->getIcon($icon, Icon::SIZE_SMALL)->render()
158 . '</span>';
159 }
160
161 /**
162 * Determine the configuration and the type of a record selector.
163 * This is a helper method for inline / IRRE handling
164 *
165 * @param array $conf TCA configuration of the parent(!) field
166 * @param string $field Field name
167 * @return array Associative array with the keys 'PA' and 'type', both are FALSE if the selector was not valid.
168 * @internal
169 */
170 public static function getInlinePossibleRecordsSelectorConfig($conf, $field = '')
171 {
172 $foreign_table = $conf['foreign_table'];
173 $foreign_selector = $conf['foreign_selector'];
174 $PA = false;
175 $type = false;
176 $table = false;
177 $selector = false;
178 if ($field) {
179 $PA = array();
180 $PA['fieldConf'] = $GLOBALS['TCA'][$foreign_table]['columns'][$field];
181 if ($PA['fieldConf'] && $conf['foreign_selector_fieldTcaOverride']) {
182 ArrayUtility::mergeRecursiveWithOverrule($PA['fieldConf'], $conf['foreign_selector_fieldTcaOverride']);
183 }
184 $PA['fieldTSConfig'] = FormEngineUtility::getTSconfigForTableRow($foreign_table, array(), $field);
185 $config = $PA['fieldConf']['config'];
186 // Determine type of Selector:
187 $type = static::getInlinePossibleRecordsSelectorType($config);
188 // Return table on this level:
189 $table = $type === 'select' ? $config['foreign_table'] : $config['allowed'];
190 // Return type of the selector if foreign_selector is defined and points to the same field as in $field:
191 if ($foreign_selector && $foreign_selector == $field && $type) {
192 $selector = $type;
193 }
194 }
195 return array(
196 'PA' => $PA,
197 'type' => $type,
198 'table' => $table,
199 'selector' => $selector
200 );
201 }
202
203 /**
204 * Determine the type of a record selector, e.g. select or group/db.
205 *
206 * @param array $config TCE configuration of the selector
207 * @return mixed The type of the selector, 'select' or 'groupdb' - FALSE not valid
208 * @internal
209 */
210 protected static function getInlinePossibleRecordsSelectorType($config)
211 {
212 $type = false;
213 if ($config['type'] === 'select') {
214 $type = 'select';
215 } elseif ($config['type'] === 'group' && $config['internal_type'] === 'db') {
216 $type = 'groupdb';
217 }
218 return $type;
219 }
220
221 /**
222 * Update expanded/collapsed states on new inline records if any.
223 *
224 * @param array $uc The uc array to be processed and saved (by reference)
225 * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tce Instance of FormEngine that saved data before
226 * @return void
227 * @internal
228 */
229 public static function updateInlineView(&$uc, $tce)
230 {
231 $backendUser = static::getBackendUserAuthentication();
232 if (isset($uc['inlineView']) && is_array($uc['inlineView'])) {
233 $inlineView = (array)unserialize($backendUser->uc['inlineView']);
234 foreach ($uc['inlineView'] as $topTable => $topRecords) {
235 foreach ($topRecords as $topUid => $childElements) {
236 foreach ($childElements as $childTable => $childRecords) {
237 $uids = array_keys($tce->substNEWwithIDs_table, $childTable);
238 if (!empty($uids)) {
239 $newExpandedChildren = array();
240 foreach ($childRecords as $childUid => $state) {
241 if ($state && in_array($childUid, $uids)) {
242 $newChildUid = $tce->substNEWwithIDs[$childUid];
243 $newExpandedChildren[] = $newChildUid;
244 }
245 }
246 // Add new expanded child records to UC (if any):
247 if (!empty($newExpandedChildren)) {
248 $inlineViewCurrent = &$inlineView[$topTable][$topUid][$childTable];
249 if (is_array($inlineViewCurrent)) {
250 $inlineViewCurrent = array_unique(array_merge($inlineViewCurrent, $newExpandedChildren));
251 } else {
252 $inlineViewCurrent = $newExpandedChildren;
253 }
254 }
255 }
256 }
257 }
258 }
259 $backendUser->uc['inlineView'] = serialize($inlineView);
260 $backendUser->writeUC();
261 }
262 }
263
264 /**
265 * Gets an array with the uids of related records out of a list of items.
266 * This list could contain more information than required. This methods just
267 * extracts the uids.
268 *
269 * @param string $itemList The list of related child records
270 * @return array An array with uids
271 * @internal
272 */
273 public static function getInlineRelatedRecordsUidArray($itemList)
274 {
275 $itemArray = GeneralUtility::trimExplode(',', $itemList, true);
276 // Perform modification of the selected items array:
277 foreach ($itemArray as &$value) {
278 $parts = explode('|', $value, 2);
279 $value = $parts[0];
280 }
281 unset($value);
282 return $itemArray;
283 }
284
285 /**
286 * Compatibility layer for methods not in FormEngine scope.
287 *
288 * databaseRow was a flat array with single elements in select and group fields as comma separated list.
289 * With new data handling in FormEngine, this is now an array of element values. There are however "old"
290 * methods that still expect the flat array.
291 * This method implodes the array again to fake the old behavior of a database row before it is given
292 * to those methods.
293 *
294 * @param array $row Incoming array
295 * @return array Flat array
296 * @internal
297 */
298 public static function databaseRowCompatibility(array $row)
299 {
300 $newRow = [];
301 foreach ($row as $fieldName => $fieldValue) {
302 if (!is_array($fieldValue)) {
303 $newRow[$fieldName] = $fieldValue;
304 } else {
305 $newElementValue = [];
306 foreach ($fieldValue as $itemNumber => $itemValue) {
307 if (is_array($itemValue) && array_key_exists(1, $itemValue)) {
308 $newElementValue[] = $itemValue[1];
309 } else {
310 $newElementValue[] = $itemValue;
311 }
312 }
313 $newRow[$fieldName] = implode(',', $newElementValue);
314 }
315 }
316 return $newRow;
317 }
318
319 /**
320 * @return LanguageService
321 */
322 protected static function getLanguageService()
323 {
324 return $GLOBALS['LANG'];
325 }
326
327 /**
328 * @return DatabaseConnection
329 */
330 protected static function getDatabaseConnection()
331 {
332 return $GLOBALS['TYPO3_DB'];
333 }
334
335 /**
336 * @return BackendUserAuthentication
337 */
338 protected static function getBackendUserAuthentication()
339 {
340 return $GLOBALS['BE_USER'];
341 }
342 }