[!!!][TASK] FormEngine: Unify resizable textareas
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / Element / FlexElement.php
1 <?php
2 namespace TYPO3\CMS\Backend\Form\Element;
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\Utility\BackendUtility;
18 use TYPO3\CMS\Backend\Utility\IconUtility;
19 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21
22 /**
23 * Generation of TCEform elements of the type "flexform"
24 */
25 class FlexElement extends AbstractFormElement {
26
27 /**
28 * Handler for Flex Forms
29 *
30 * @param string $table The table name of the record
31 * @param string $field The field name which this element is supposed to edit
32 * @param array $row The record data array where the value(s) for the field can be found
33 * @param array $additionalInformation An array with additional configuration options.
34 * @return string The HTML code for the TCEform field
35 */
36 public function render($table, $field, $row, &$additionalInformation) {
37 // Data Structure:
38 $dataStructArray = BackendUtility::getFlexFormDS($additionalInformation['fieldConf']['config'], $row, $table, $field);
39 $item = '';
40 // Manipulate Flexform DS via TSConfig and group access lists
41 if (is_array($dataStructArray)) {
42 $flexFormHelper = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Form\FlexFormsHelper::class);
43 $dataStructArray = $flexFormHelper->modifyFlexFormDS($dataStructArray, $table, $field, $row, $additionalInformation['fieldConf']);
44 unset($flexFormHelper);
45 }
46 // Get data structure:
47 if (is_array($dataStructArray)) {
48 $languageService = $this->getLanguageService();
49 // Get data:
50 $xmlData = $additionalInformation['itemFormElValue'];
51 $xmlHeaderAttributes = GeneralUtility::xmlGetHeaderAttribs($xmlData);
52 $storeInCharset = strtolower($xmlHeaderAttributes['encoding']);
53 if ($storeInCharset) {
54 $currentCharset = $languageService->charSet;
55 $xmlData = $languageService->csConvObj->conv($xmlData, $storeInCharset, $currentCharset, 1);
56 }
57 $editData = GeneralUtility::xml2array($xmlData);
58 // Must be XML parsing error...
59 if (!is_array($editData)) {
60 $editData = array();
61 } elseif (!isset($editData['meta']) || !is_array($editData['meta'])) {
62 $editData['meta'] = array();
63 }
64 // Find the data structure if sheets are found:
65 $sheet = $editData['meta']['currentSheetId'] ? $editData['meta']['currentSheetId'] : 'sDEF';
66 // Sheet to display
67 // Create language menu:
68 $langChildren = $dataStructArray['meta']['langChildren'] ? 1 : 0;
69 $langDisabled = $dataStructArray['meta']['langDisable'] ? 1 : 0;
70 $editData['meta']['currentLangId'] = array();
71 // Look up page overlays:
72 $checkPageLanguageOverlay = $this->getBackendUserAuthentication()->getTSConfigVal('options.checkPageLanguageOverlay') ? TRUE : FALSE;
73 if ($checkPageLanguageOverlay) {
74 $where_clause = 'pid=' . (int)$row['pid'] . BackendUtility::deleteClause('pages_language_overlay')
75 . BackendUtility::versioningPlaceholderClause('pages_language_overlay');
76 $pageOverlays = $this->getDatabaseConnection()->exec_SELECTgetRows('*', 'pages_language_overlay', $where_clause, '', '', '', 'sys_language_uid');
77 }
78 $languages = $this->getAvailableLanguages();
79 foreach ($languages as $lInfo) {
80 if (
81 $this->getBackendUserAuthentication()->checkLanguageAccess($lInfo['uid'])
82 && (!$checkPageLanguageOverlay || $lInfo['uid'] <= 0 || is_array($pageOverlays[$lInfo['uid']]))
83 ) {
84 $editData['meta']['currentLangId'][] = $lInfo['ISOcode'];
85 }
86 }
87 if (!is_array($editData['meta']['currentLangId']) || !count($editData['meta']['currentLangId'])) {
88 $editData['meta']['currentLangId'] = array('DEF');
89 }
90 $editData['meta']['currentLangId'] = array_unique($editData['meta']['currentLangId']);
91 $additionalInformation['_noEditDEF'] = FALSE;
92 if ($langChildren || $langDisabled) {
93 $rotateLang = array('DEF');
94 } else {
95 if (!in_array('DEF', $editData['meta']['currentLangId'])) {
96 array_unshift($editData['meta']['currentLangId'], 'DEF');
97 $additionalInformation['_noEditDEF'] = TRUE;
98 }
99 $rotateLang = $editData['meta']['currentLangId'];
100 }
101 // Tabs sheets
102 if (is_array($dataStructArray['sheets'])) {
103 $tabsToTraverse = array_keys($dataStructArray['sheets']);
104 } else {
105 $tabsToTraverse = array($sheet);
106 }
107
108 $this->getControllerDocumentTemplate()->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/FormEngineFlexForm');
109
110 /** @var $elementConditionMatcher \TYPO3\CMS\Backend\Form\ElementConditionMatcher */
111 $elementConditionMatcher = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Form\ElementConditionMatcher::class);
112
113 foreach ($rotateLang as $lKey) {
114 if (!$langChildren && !$langDisabled) {
115 $item .= '<strong>' . $this->formEngine->getLanguageIcon($table, $row, ('v' . $lKey)) . $lKey . ':</strong>';
116 }
117 // Default language, other options are "lUK" or whatever country code (independent of system!!!)
118 $lang = 'l' . $lKey;
119 $tabParts = array();
120 $sheetContent = '';
121 foreach ($tabsToTraverse as $sheet) {
122 list($dataStruct, $sheet) = GeneralUtility::resolveSheetDefInDS($dataStructArray, $sheet);
123 // If sheet has displayCond
124 if ($dataStruct['ROOT']['TCEforms']['displayCond']) {
125 $splitCondition = GeneralUtility::trimExplode(':', $dataStruct['ROOT']['TCEforms']['displayCond']);
126 $skipCondition = FALSE;
127 $fakeRow = array();
128 switch ($splitCondition[0]) {
129 case 'FIELD':
130 list($sheetName, $fieldName) = GeneralUtility::trimExplode('.', $splitCondition[1]);
131 $fieldValue = $editData['data'][$sheetName][$lang][$fieldName];
132 $splitCondition[1] = $fieldName;
133 $dataStruct['ROOT']['TCEforms']['displayCond'] = join(':', $splitCondition);
134 $fakeRow = array($fieldName => $fieldValue);
135 break;
136 case 'HIDE_FOR_NON_ADMINS':
137
138 case 'VERSION':
139
140 case 'HIDE_L10N_SIBLINGS':
141
142 case 'EXT':
143 break;
144 case 'REC':
145 $fakeRow = array('uid' => $row['uid']);
146 break;
147 default:
148 $skipCondition = TRUE;
149 }
150 $displayConditionResult = TRUE;
151 if ($dataStruct['ROOT']['TCEforms']['displayCond']) {
152 $displayConditionResult = $elementConditionMatcher->match($dataStruct['ROOT']['TCEforms']['displayCond'], $fakeRow, 'vDEF');
153 }
154 // If sheets displayCond leads to false
155 if (!$skipCondition && !$displayConditionResult) {
156 // Don't create this sheet
157 continue;
158 }
159 }
160 // Render sheet:
161 if (is_array($dataStruct['ROOT']) && is_array($dataStruct['ROOT']['el'])) {
162 // Default language, other options are "lUK" or whatever country code (independent of system!!!)
163 $additionalInformation['_valLang'] = $langChildren && !$langDisabled ? $editData['meta']['currentLangId'] : 'DEF';
164 $additionalInformation['_lang'] = $lang;
165 // Assemble key for loading the correct CSH file
166 $dsPointerFields = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$table]['columns'][$field]['config']['ds_pointerField'], TRUE);
167 $additionalInformation['_cshKey'] = $table . '.' . $field;
168 foreach ($dsPointerFields as $key) {
169 $additionalInformation['_cshKey'] .= '.' . $row[$key];
170 }
171 // Push the sheet level tab to DynNestedStack
172 $tabIdentString = '';
173 if (is_array($dataStructArray['sheets'])) {
174 $tabIdentString = $this->getDocumentTemplate()->getDynTabMenuId('TCEFORMS:flexform:' . $additionalInformation['itemFormElName'] . $additionalInformation['_lang']);
175 $this->formEngine->pushToDynNestedStack('tab', $tabIdentString . '-' . (count($tabParts) + 1));
176 }
177 // Render flexform:
178 $tRows = $this->getSingleField_typeFlex_draw($dataStruct['ROOT']['el'], $editData['data'][$sheet][$lang], $table, $field, $row, $additionalInformation, '[data][' . $sheet . '][' . $lang . ']');
179 $sheetContent = '<div class="typo3-TCEforms-flexForm t3-form-flexform">' . $tRows . '</div>';
180 // Pop the sheet level tab from DynNestedStack
181 if (is_array($dataStructArray['sheets'])) {
182 $this->formEngine->popFromDynNestedStack('tab', $tabIdentString . '-' . (count($tabParts) + 1));
183 }
184 } else {
185 $sheetContent = 'Data Structure ERROR: No ROOT element found for sheet "' . $sheet . '".';
186 }
187 // Add to tab:
188 $tabParts[] = array(
189 'label' => $dataStruct['ROOT']['TCEforms']['sheetTitle'] ? $languageService->sL($dataStruct['ROOT']['TCEforms']['sheetTitle']) : $sheet,
190 'description' => $dataStruct['ROOT']['TCEforms']['sheetDescription'] ? $languageService->sL($dataStruct['ROOT']['TCEforms']['sheetDescription']) : '',
191 'linkTitle' => $dataStruct['ROOT']['TCEforms']['sheetShortDescr'] ? $languageService->sL($dataStruct['ROOT']['TCEforms']['sheetShortDescr']) : '',
192 'content' => $sheetContent
193 );
194 }
195 if (is_array($dataStructArray['sheets'])) {
196 $item .= $this->formEngine->getDynTabMenu($tabParts, 'TCEFORMS:flexform:' . $additionalInformation['itemFormElName'] . $additionalInformation['_lang']);
197 } else {
198 $item .= $sheetContent;
199 }
200 }
201 } else {
202 $item = 'Data Structure ERROR: ' . $dataStructArray;
203 }
204 return $item;
205 }
206
207 /**
208 * Returns an array of available languages (to use for FlexForms)
209 *
210 * @param bool $onlyIsoCoded If set, only languages which are paired with a static_info_table / static_language record will be returned.
211 * @param bool $setDefault If set, an array entry for a default language is set.
212 * @return array
213 */
214 protected function getAvailableLanguages($onlyIsoCoded = TRUE, $setDefault = TRUE) {
215 $isL = ExtensionManagementUtility::isLoaded('static_info_tables');
216 // Find all language records in the system:
217 $db = $this->getDatabaseConnection();
218 $res = $db->exec_SELECTquery('language_isocode,static_lang_isocode,title,uid', 'sys_language', 'pid=0 AND hidden=0' . BackendUtility::deleteClause('sys_language'), '', 'title');
219 // Traverse them:
220 $output = array();
221 if ($setDefault) {
222 $output[0] = array(
223 'uid' => 0,
224 'title' => 'Default language',
225 'ISOcode' => 'DEF'
226 );
227 }
228 while ($row = $db->sql_fetch_assoc($res)) {
229 $output[$row['uid']] = $row;
230 if (!empty($row['language_isocode'])) {
231 $output[$row['uid']]['ISOcode'] = $row['language_isocode'];
232 } elseif ($isL && $row['static_lang_isocode']) {
233 GeneralUtility::deprecationLog('Usage of the field "static_lang_isocode" is discouraged, and will stop working with CMS 8. Use the built-in language field "language_isocode" in your sys_language records.');
234 $rr = BackendUtility::getRecord('static_languages', $row['static_lang_isocode'], 'lg_iso_2');
235 if ($rr['lg_iso_2']) {
236 $output[$row['uid']]['ISOcode'] = $rr['lg_iso_2'];
237 }
238 }
239 if ($onlyIsoCoded && !$output[$row['uid']]['ISOcode']) {
240 unset($output[$row['uid']]);
241 }
242 }
243 $db->sql_free_result($res);
244 return $output;
245 }
246
247 /**
248 * Recursive rendering of flexforms
249 *
250 * @param array $dataStruct (part of) Data Structure for which to render. Keys on first level is flex-form fields
251 * @param array $editData (part of) Data array of flexform corresponding to the input DS. Keys on first level is flex-form field names
252 * @param string $table Table name, eg. tt_content
253 * @param string $field Field name, eg. tx_templavoila_flex
254 * @param array $row The particular record from $table in which the field $field is found
255 * @param array $PA Array of standard information for rendering of a form field in TCEforms, see other rendering functions too
256 * @param string $formPrefix Form field prefix, eg. "[data][sDEF][lDEF][...][...]
257 * @param int $level Indicates nesting level for the function call
258 * @param string $idPrefix Prefix for ID-values
259 * @param bool $toggleClosed Defines whether the next flexform level is open or closed. Comes from _TOGGLE pseudo field in FlexForm xml.
260 * @return string HTMl code for form.
261 */
262 public function getSingleField_typeFlex_draw($dataStruct, $editData, $table, $field, $row, &$PA, $formPrefix = '', $level = 0, $idPrefix = 'ID', $toggleClosed = FALSE) {
263 $output = '';
264 $mayRestructureFlexforms = $this->getBackendUserAuthentication()->checkLanguageAccess(0);
265 // Data Structure array must be ... and array of course...
266 if (is_array($dataStruct)) {
267 $languageService = $this->getLanguageService();
268 foreach ($dataStruct as $key => $value) {
269 // Traversing fields in structure:
270 if (is_array($value)) {
271 // The value of each entry must be an array.
272 // ********************
273 // Making the row:
274 // ********************
275 // Title of field:
276 // <title>LLL:EXT:cms/locallang_ttc.xml:media.sources</title>
277 $theTitle = $value['title'];
278
279 // If there is a title, check for LLL label
280 if (strlen($theTitle) > 0) {
281 $theTitle = htmlspecialchars(GeneralUtility::fixed_lgd_cs($languageService->sL($theTitle),
282 (int)$this->getBackendUserAuthentication()->uc['titleLen']));
283 }
284 // If it's a "section" or "container":
285 if ($value['type'] == 'array') {
286 // Creating IDs for form fields:
287 // It's important that the IDs "cascade" - otherwise we can't dynamically expand the flex form
288 // because this relies on simple string substitution of the first parts of the id values.
289 // This is a suffix used for forms on this level
290 $thisId = GeneralUtility::shortMd5(uniqid('id', TRUE));
291 // $idPrefix is the prefix for elements on lower levels in the hierarchy and we combine this
292 // with the thisId value to form a new ID on this level.
293 $idTagPrefix = $idPrefix . '-' . $thisId;
294 // If it's a "section" containing other elements:
295 if ($value['section']) {
296 // Load script.aculo.us if flexform sections can be moved by drag'n'drop:
297 $this->getControllerDocumentTemplate()->getPageRenderer()->loadScriptaculous();
298 // Render header of section:
299 $output .= '<div class="t3-form-field-label-flexsection"><strong>' . $theTitle . '</strong></div>';
300 // Render elements in data array for section:
301 $tRows = array();
302 if (is_array($editData[$key]['el'])) {
303 foreach ($editData[$key]['el'] as $k3 => $v3) {
304 $cc = $k3;
305 if (is_array($v3)) {
306 $theType = key($v3);
307 $theDat = $v3[$theType];
308 $newSectionEl = $value['el'][$theType];
309 if (is_array($newSectionEl)) {
310 $tRows[] = $this->getSingleField_typeFlex_draw(array($theType => $newSectionEl),
311 array($theType => $theDat), $table, $field, $row, $PA,
312 $formPrefix . '[' . $key . '][el][' . $cc . ']', $level + 1,
313 $idTagPrefix, $v3['_TOGGLE']);
314 }
315 }
316 }
317 }
318 // Now, we generate "templates" for new elements that could be added to this section
319 // by traversing all possible types of content inside the section:
320 // We have to handle the fact that requiredElements and such may be set during this
321 // rendering process and therefore we save and reset the state of some internal variables
322 // ... little crude, but works...
323 // Preserving internal variables we don't want to change:
324 $TEMP_requiredElements = $this->formEngine->requiredElements;
325 // Traversing possible types of new content in the section:
326 $newElementsLinks = array();
327 foreach ($value['el'] as $nnKey => $nCfg) {
328 $additionalJS_post_saved = $this->formEngine->additionalJS_post;
329 $this->formEngine->additionalJS_post = array();
330 $additionalJS_submit_saved = $this->formEngine->additionalJS_submit;
331 $this->formEngine->additionalJS_submit = array();
332 $newElementTemplate = $this->getSingleField_typeFlex_draw(array($nnKey => $nCfg),
333 array(), $table, $field, $row, $PA,
334 $formPrefix . '[' . $key . '][el][' . $idTagPrefix . '-form]', $level + 1,
335 $idTagPrefix);
336 // Makes a "Add new" link:
337 $var = str_replace('.', '', uniqid('idvar', TRUE));
338 $replace = 'replace(/' . $idTagPrefix . '-/g,"' . $idTagPrefix . '-"+' . $var . '+"-")';
339 $replace .= '.replace(/(tceforms-(datetime|date)field-)/g,"$1" + (new Date()).getTime())';
340 $onClickInsert = 'var ' . $var . ' = "' . 'idx"+(new Date()).getTime();'
341 // Do not replace $isTagPrefix in setActionStatus() because it needs section id!
342 . 'new Insertion.Bottom($("' . $idTagPrefix . '"), ' . json_encode($newElementTemplate)
343 . '.' . $replace . '); TYPO3.jQuery("#' . $idTagPrefix . '").t3FormEngineFlexFormElement();'
344 . 'eval(unescape("' . rawurlencode(implode(';', $this->formEngine->additionalJS_post)) . '").' . $replace . ');'
345 . 'TBE_EDITOR.addActionChecks("submit", unescape("'
346 . rawurlencode(implode(';', $this->formEngine->additionalJS_submit)) . '").' . $replace . ');'
347 . 'TYPO3.FormEngine.reinitialize();'
348 . 'return false;';
349 // Kasper's comment (kept for history):
350 // Maybe there is a better way to do this than store the HTML for the new element
351 // in rawurlencoded format - maybe it even breaks with certain charsets?
352 // But for now this works...
353 $this->formEngine->additionalJS_post = $additionalJS_post_saved;
354 $this->formEngine->additionalJS_submit = $additionalJS_submit_saved;
355 $title = '';
356 if (isset($nCfg['title'])) {
357 $title = $languageService->sL($nCfg['title']);
358 }
359 $newElementsLinks[] = '<a href="#" onclick="' . htmlspecialchars($onClickInsert) . '">'
360 . IconUtility::getSpriteIcon('actions-document-new')
361 . htmlspecialchars(GeneralUtility::fixed_lgd_cs($title, 30)) . '</a>';
362 }
363 // Reverting internal variables we don't want to change:
364 $this->formEngine->requiredElements = $TEMP_requiredElements;
365 // Adding the sections
366
367 // add the "toggle all" button for the sections
368 $toggleAll = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.toggleall', TRUE);
369 $output .= '
370 <div class="t3-form-field-toggle-flexsection t3-form-flexsection-toggle">
371 <a href="#">'. IconUtility::getSpriteIcon('actions-move-right', array('title' => $toggleAll)) . $toggleAll . '</a>
372 </div>
373 <div id="' . $idTagPrefix . '" class="t3-form-field-container-flexsection t3-flex-container" data-t3-flex-allow-restructure="' . ($mayRestructureFlexforms ? 1 : 0) . '">' . implode('', $tRows) . '</div>';
374
375 // add the "new" link
376 if ($mayRestructureFlexforms) {
377 $output .= '<div class="t3-form-field-add-flexsection"><strong>'
378 . $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.addnew', TRUE)
379 . ':</strong> ' . implode(' | ', $newElementsLinks) . '</div>';
380 }
381
382 $output = '<div class="t3-form-field-container t3-form-flex">' . $output . '</div>';
383 } else {
384 // It is a container of a single section
385 $toggleIconOpenState = ($toggleClosed ? 'display: none;' : '');
386 $toggleIconCloseState = (!$toggleClosed ? 'display: none;' : '');
387
388 $toggleIcons = IconUtility::getSpriteIcon('actions-move-down', array('class' => 't3-flex-control-toggle-icon-open', 'style' => $toggleIconOpenState));
389 $toggleIcons .= IconUtility::getSpriteIcon('actions-move-right', array('class' => 't3-flex-control-toggle-icon-close', 'style' => $toggleIconCloseState));
390
391 // Notice: Creating "new" elements after others seemed to be too difficult to do
392 // and since moving new elements created in the bottom is now so easy
393 // with drag'n'drop I didn't see the need.
394 // Putting together header of a section. Sections can be removed, copied, opened/closed, moved up and down:
395 // I didn't know how to make something right-aligned without a table, so I put it in a table.
396 // can be made into <div>'s if someone like to.
397 // Notice: The fact that I make a "Sortable.create" right onmousedown is that if we
398 // initialize this when rendering the form in PHP new and copied elements will not
399 // be possible to move as a sortable. But this way a new sortable is initialized every time
400 // someone tries to move and it will always work.
401 $ctrlHeader = '
402 <div class="pull-left">
403 <a href="#" class="t3-flex-control-toggle-button">' . $toggleIcons . '</a>
404 <span class="t3-record-title">' . $theTitle . '</span>
405 </div>';
406
407 if ($mayRestructureFlexforms) {
408 $ctrlHeader .= '<div class="pull-right">'
409 . IconUtility::getSpriteIcon('actions-move-move', array('title' => 'Drag to Move', 'class' => 't3-js-sortable-handle'))
410 . IconUtility::getSpriteIcon('actions-edit-delete', array('title' => 'Delete', 'class' => 't3-delete'))
411 . '</div>';
412 }
413
414 $ctrlHeader = '<div class="t3-form-field-header-flexsection t3-flex-section-header">' . $ctrlHeader . '</div>';
415
416 $s = GeneralUtility::revExplode('[]', $formPrefix, 2);
417 $actionFieldName = '_ACTION_FLEX_FORM' . $PA['itemFormElName'] . $s[0] . '][_ACTION][' . $s[1];
418 // Push the container to DynNestedStack as it may be toggled
419 $this->formEngine->pushToDynNestedStack('flex', $idTagPrefix);
420 // Putting together the container:
421 $this->formEngine->additionalJS_delete = array();
422 $singleField_typeFlex_draw = $this->getSingleField_typeFlex_draw($value['el'],
423 $editData[$key]['el'], $table, $field, $row, $PA,
424 ($formPrefix . '[' . $key . '][el]'), ($level + 1), $idTagPrefix);
425 $output .= '
426 <div id="' . $idTagPrefix . '" class="t3-form-field-container-flexsections t3-flex-section">
427 <input class="t3-flex-control t3-flex-control-action" type="hidden" name="' . htmlspecialchars($actionFieldName) . '" value=""/>
428
429 ' . $ctrlHeader . '
430 <div class="t3-form-field-record-flexsection t3-flex-section-content"'
431 . ($toggleClosed ? ' style="display:none;"' : '') . '>' . $singleField_typeFlex_draw . '
432 </div>
433 <input class="t3-flex-control t3-flex-control-toggle" id="' . $idTagPrefix . '-toggleClosed" type="hidden" name="'
434 . htmlspecialchars('data[' . $table . '][' . $row['uid'] . '][' . $field . ']' . $formPrefix . '[_TOGGLE]')
435 . '" value="' . ($toggleClosed ? 1 : 0) . '" />
436 </div>';
437 $output = str_replace('/*###REMOVE###*/', GeneralUtility::slashJS(htmlspecialchars(implode('', $this->formEngine->additionalJS_delete))), $output);
438 // NOTICE: We are saving the toggle-state directly in the flexForm XML and "unauthorized"
439 // according to the data structure. It means that flexform XML will report unclean and
440 // a cleaning operation will remove the recorded togglestates. This is not a fatal problem.
441 // Ideally we should save the toggle states in meta-data but it is much harder to do that.
442 // And this implementation was easy to make and with no really harmful impact.
443 // Pop the container from DynNestedStack
444 $this->formEngine->popFromDynNestedStack('flex', $idTagPrefix);
445 }
446 } elseif (is_array($value['TCEforms']['config'])) {
447 // Rendering a single form element:
448 if (is_array($PA['_valLang'])) {
449 $rotateLang = $PA['_valLang'];
450 } else {
451 $rotateLang = array($PA['_valLang']);
452 }
453 $conditionData = is_array($editData) ? $editData : array();
454 // Add current $row to data processed by \TYPO3\CMS\Backend\Form\ElementConditionMatcher
455 $conditionData['parentRec'] = $row;
456 $tRows = array();
457
458 /** @var $elementConditionMatcher \TYPO3\CMS\Backend\Form\ElementConditionMatcher */
459 $elementConditionMatcher = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Form\ElementConditionMatcher::class);
460
461 foreach ($rotateLang as $vDEFkey) {
462 $vDEFkey = 'v' . $vDEFkey;
463 $displayConditionResult = TRUE;
464 if ($value['TCEforms']['displayCond']) {
465 $displayConditionResult = $elementConditionMatcher->match($value['TCEforms']['displayCond'], $conditionData, $vDEFkey);
466 }
467 if ($displayConditionResult) {
468 $fakePA = array();
469 $fakePA['fieldConf'] = array(
470 'label' => $languageService->sL(trim($value['TCEforms']['label'])),
471 'config' => $value['TCEforms']['config'],
472 'defaultExtras' => $value['TCEforms']['defaultExtras'],
473 'onChange' => $value['TCEforms']['onChange']
474 );
475 if ($PA['_noEditDEF'] && $PA['_lang'] === 'lDEF') {
476 $fakePA['fieldConf']['config'] = array(
477 'type' => 'none',
478 'rows' => 2
479 );
480 }
481 if (
482 $fakePA['fieldConf']['onChange'] === 'reload'
483 || !empty($GLOBALS['TCA'][$table]['ctrl']['type'])
484 && (string)$key === $GLOBALS['TCA'][$table]['ctrl']['type']
485 || !empty($GLOBALS['TCA'][$table]['ctrl']['requestUpdate'])
486 && GeneralUtility::inList($GLOBALS['TCA'][$table]['ctrl']['requestUpdate'], $key)
487 ) {
488 if ($this->getBackendUserAuthentication()->jsConfirmation(1)) {
489 $alertMsgOnChange = 'if (confirm(TBE_EDITOR.labels.onChangeAlert) && TBE_EDITOR.checkSubmit(-1)){ TBE_EDITOR.submitForm() };';
490 } else {
491 $alertMsgOnChange = 'if(TBE_EDITOR.checkSubmit(-1)){ TBE_EDITOR.submitForm();}';
492 }
493 } else {
494 $alertMsgOnChange = '';
495 }
496 $fakePA['fieldChangeFunc'] = $PA['fieldChangeFunc'];
497 if (strlen($alertMsgOnChange)) {
498 $fakePA['fieldChangeFunc']['alert'] = $alertMsgOnChange;
499 }
500 $fakePA['onFocus'] = $PA['onFocus'];
501 $fakePA['label'] = $PA['label'];
502 $fakePA['itemFormElName'] = $PA['itemFormElName'] . $formPrefix . '[' . $key . '][' . $vDEFkey . ']';
503 $fakePA['itemFormElName_file'] = $PA['itemFormElName_file'] . $formPrefix . '[' . $key . '][' . $vDEFkey . ']';
504 $fakePA['itemFormElID'] = $fakePA['itemFormElName'];
505 if (isset($editData[$key][$vDEFkey])) {
506 $fakePA['itemFormElValue'] = $editData[$key][$vDEFkey];
507 } else {
508 $fakePA['itemFormElValue'] = $fakePA['fieldConf']['config']['default'];
509 }
510 $theFormEl = $this->formEngine->getSingleField_SW($table, $field, $row, $fakePA);
511 $theTitle = htmlspecialchars($fakePA['fieldConf']['label']);
512 if (!in_array('DEF', $rotateLang)) {
513 $defInfo = '<div class="t3-form-original-language">'
514 . $this->formEngine->getLanguageIcon($table, $row, 0)
515 . $this->formEngine->previewFieldValue($editData[$key]['vDEF'], $fakePA['fieldConf'], $field)
516 . '&nbsp;</div>';
517 } else {
518 $defInfo = '';
519 }
520 if (!$PA['_noEditDEF']) {
521 $prLang = $this->formEngine->getAdditionalPreviewLanguages();
522 foreach ($prLang as $prL) {
523 $defInfo .= '<div class="t3-form-original-language">'
524 . $this->formEngine->getLanguageIcon($table, $row, ('v' . $prL['ISOcode']))
525 . $this->formEngine->previewFieldValue($editData[$key][('v' . $prL['ISOcode'])], $fakePA['fieldConf'], $field)
526 . '&nbsp;</div>';
527 }
528 }
529 $languageIcon = '';
530 if ($vDEFkey != 'vDEF') {
531 $languageIcon = $this->formEngine->getLanguageIcon($table, $row, $vDEFkey);
532 }
533 // Put row together
534 // possible linebreaks in the label through xml: \n => <br/>, usage of nl2br()
535 // not possible, so it's done through str_replace
536 $processedTitle = str_replace('\\n', '<br />', $theTitle);
537 $tRows[] = '<div class="t3-form-field-container t3-form-field-container-flex">'
538 . '<div class="t3-form-field-label t3-form-field-label-flex">' . $languageIcon
539 . BackendUtility::wrapInHelp($PA['_cshKey'], $key, $processedTitle) . '</div>
540 <div class="t3-form-field t3-form-field-flex">' . $theFormEl . $defInfo
541 . $this->renderVDEFDiff($editData[$key], $vDEFkey) . '</div>
542 </div>';
543 }
544 }
545 if (count($tRows)) {
546 $output .= implode('', $tRows);
547 }
548 }
549 }
550 }
551 }
552 return $output;
553 }
554
555 /**
556 * Renders the diff-view of vDEF fields in flexforms
557 *
558 * @param array $vArray Record array of the record being edited
559 * @param string $vDEFkey HTML of the form field. This is what we add the content to.
560 * @return string Item string returned again, possibly with the original value added to.
561 */
562 protected function renderVDEFDiff($vArray, $vDEFkey) {
563 $item = NULL;
564 if (
565 $GLOBALS['TYPO3_CONF_VARS']['BE']['flexFormXMLincludeDiffBase'] && isset($vArray[$vDEFkey . '.vDEFbase'])
566 && (string)$vArray[$vDEFkey . '.vDEFbase'] !== (string)$vArray['vDEF']
567 ) {
568 // Create diff-result:
569 $t3lib_diff_Obj = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Utility\DiffUtility::class);
570 $diffres = $t3lib_diff_Obj->makeDiffDisplay($vArray[$vDEFkey . '.vDEFbase'], $vArray['vDEF']);
571 $item = '<div class="typo3-TCEforms-diffBox">' . '<div class="typo3-TCEforms-diffBox-header">'
572 . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.changeInOrig')) . ':</div>' . $diffres . '</div>';
573 }
574 return $item;
575 }
576
577 }