[TASK] Improve requireJS handling for FormEngine
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / AbstractNode.php
1 <?php
2 namespace TYPO3\CMS\Backend\Form;
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\Utility\ArrayUtility;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19
20 /**
21 * Base class for container and single elements - their abstracts extend from here.
22 */
23 abstract class AbstractNode implements NodeInterface {
24
25 /**
26 * A list of global options given from parent to child elements
27 *
28 * @var array
29 */
30 protected $globalOptions = array();
31
32 /**
33 * Handler for single nodes
34 *
35 * @return array As defined in initializeResultArray() of AbstractNode
36 */
37 abstract public function render();
38
39 /**
40 * Set global options from parent instance
41 *
42 * @param array $globalOptions Global options like 'readonly' for all elements
43 * @return $this
44 */
45 public function setGlobalOptions(array $globalOptions) {
46 $this->globalOptions = $globalOptions;
47 return $this;
48 }
49
50 /**
51 * Initialize the array that is returned to parent after calling. This structure
52 * is identical for *all* nodes. Parent will merge the return of a child with its
53 * own stuff and in itself return an array of the same structure.
54 *
55 * @return array
56 */
57 protected function initializeResultArray() {
58 return array(
59 'additionalJavaScriptPost' => array(),
60 'additionalJavaScriptSubmit' => array(),
61 'additionalHiddenFields' => array(),
62 'additionalHeadTags' => array(),
63 // can hold strings or arrays, string = requireJS module, array = requireJS module + callback e.g. array('TYPO3/Foo/Bar', 'function() {}')
64 'requireJsModules' => array(),
65 'extJSCODE' => '',
66 'inlineData' => array(),
67 'html' => '',
68 );
69 }
70
71 /**
72 * Merge existing data with a child return array
73 *
74 * @param array $existing Currently merged array
75 * @param array $childReturn Array returned by child
76 * @return array Result array
77 */
78 protected function mergeChildReturnIntoExistingResult(array $existing, array $childReturn) {
79 if (!empty($childReturn['html'])) {
80 $existing['html'] .= LF . $childReturn['html'];
81 }
82 if (!empty($childReturn['extJSCODE'])) {
83 $existing['extJSCODE'] .= LF . $childReturn['extJSCODE'];
84 }
85 foreach ($childReturn['additionalJavaScriptPost'] as $value) {
86 $existing['additionalJavaScriptPost'][] = $value;
87 }
88 foreach ($childReturn['additionalJavaScriptSubmit'] as $value) {
89 $existing['additionalJavaScriptSubmit'][] = $value;
90 }
91 foreach ($childReturn['additionalHiddenFields'] as $value) {
92 $existing['additionalHiddenFields'][] = $value;
93 }
94 foreach ($childReturn['additionalHeadTags'] as $value) {
95 $existing['additionalHeadTags'][] = $value;
96 }
97 if (!empty($childReturn['requireJsModules'])) {
98 foreach ($childReturn['requireJsModules'] as $module) {
99 $existing['requireJsModules'][] = $module;
100 }
101 }
102 if (!empty($childReturn['inlineData'])) {
103 $existingInlineData = $existing['inlineData'];
104 $childInlineData = $childReturn['inlineData'];
105 ArrayUtility::mergeRecursiveWithOverrule($existingInlineData, $childInlineData);
106 $existing['inlineData'] = $existingInlineData;
107 }
108 return $existing;
109 }
110
111 /**
112 * Determine and get the value for the placeholder for an input field.
113 * Typically used in an inline relation where values from fields down the record chain
114 * are used as "default" values for fields.
115 *
116 * @param string $table
117 * @param array $config
118 * @param array $row
119 * @return mixed
120 */
121 protected function getPlaceholderValue($table, array $config, array $row) {
122 $value = trim($config['placeholder']);
123 if (!$value) {
124 return '';
125 }
126 // Check if we have a reference to another field value from the current record
127 if (substr($value, 0, 6) === '__row|') {
128 /** @var FormDataTraverser $traverser */
129 $traverseFields = GeneralUtility::trimExplode('|', substr($value, 6));
130 $traverser = GeneralUtility::makeInstance(FormDataTraverser::class);
131 $value = $traverser->getTraversedFieldValue($traverseFields, $table, $row, $this->globalOptions['inlineFirstPid']);
132 }
133
134 return $value;
135 }
136
137 /**
138 * Build JSON string for validations rules and return it
139 * as data attribute for HTML elements.
140 *
141 * @param array $config
142 * @return string
143 */
144 protected function getValidationDataAsDataAttribute(array $config) {
145 return sprintf(' data-formengine-validation-rules="%s" ', htmlspecialchars($this->getValidationDataAsJsonString($config)));
146 }
147
148 /**
149 * Build JSON string for validations rules.
150 *
151 * @param array $config
152 * @return string
153 */
154 protected function getValidationDataAsJsonString(array $config) {
155 $validationRules = array();
156 if (!empty($config['maxitems']) || !empty($config['minitems'])) {
157 $minItems = (isset($config['minitems'])) ? (int)$config['minitems'] : 0;
158 $maxItems = (isset($config['maxitems'])) ? (int)$config['maxitems'] : 10000;
159 $type = ($config['type']) ?: 'range';
160 if ($config['renderMode'] !== 'tree' && $maxItems <= 1 && $minItems > 0) {
161 $validationRules[] = array(
162 'type' => $type,
163 'minItems' => 1,
164 'maxItems' => 100000
165 );
166 } else {
167 $validationRules[] = array(
168 'type' => $type,
169 'minItems' => $minItems,
170 'maxItems' => $maxItems
171 );
172 }
173 }
174 if (!empty($config['required'])) {
175 $validationRules[] = array('type' => 'required');
176 }
177 return json_encode($validationRules);
178 }
179
180 }