[TASK] Switch UserStorageCapabilityService to a renderType
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / NodeFactory.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\GeneralUtility;
18
19 /**
20 * Create an element object depending on renderType.
21 *
22 * This is the main factory to instantiate any node within the render
23 * chain of FormEngine. All nodes must implement NodeInterface.
24 *
25 * Nodes are "container" classes of the render chain, "element" classes that
26 * render single elements, as well as "fieldWizard", "fieldInformation" and
27 * "fieldControl" classes which are called by single elements to enrich them.
28 *
29 * This factory gets a string "renderType" and then looks up in a list which
30 * specific class should handle this renderType. This list can be extended with
31 * own renderTypes by extensions, existing renderTypes can be overridden, and
32 * - for complex cases - it is possible to register own resolver classes for single
33 * renderTypes that can return a node class name to override the default lookup list.
34 */
35 class NodeFactory
36 {
37 /**
38 * Node resolver classes
39 * Nested array with nodeName as key, (sorted) priority as sub key and class as value
40 *
41 * @var array
42 */
43 protected $nodeResolver = [];
44
45 /**
46 * Default registry of node name to handling class
47 *
48 * @var array
49 */
50 protected $nodeTypes = [
51 // Default container classes
52 'flex' => Container\FlexFormEntryContainer::class,
53 'flexFormContainerContainer' => Container\FlexFormContainerContainer::class,
54 'flexFormElementContainer' => Container\FlexFormElementContainer::class,
55 'flexFormNoTabsContainer' => Container\FlexFormNoTabsContainer::class,
56 'flexFormSectionContainer' => Container\FlexFormSectionContainer::class,
57 'flexFormTabsContainer' => Container\FlexFormTabsContainer::class,
58 'fullRecordContainer' => Container\FullRecordContainer::class,
59 'inline' => Container\InlineControlContainer::class,
60 'inlineRecordContainer' => Container\InlineRecordContainer::class,
61 'listOfFieldsContainer' => Container\ListOfFieldsContainer::class,
62 'noTabsContainer' => Container\NoTabsContainer::class,
63 'outerWrapContainer' => Container\OuterWrapContainer::class,
64 'paletteAndSingleContainer' => Container\PaletteAndSingleContainer::class,
65 'singleFieldContainer' => Container\SingleFieldContainer::class,
66 'tabsContainer' => Container\TabsContainer::class,
67
68 // Default single element classes
69 'check' => Element\CheckboxElement::class,
70 'checkboxToggle' => Element\CheckboxToggleElement::class,
71 'checkboxLabeledToggle' => Element\CheckboxLabeledToggleElement::class,
72 'group' => Element\GroupElement::class,
73 'input' => Element\InputTextElement::class,
74 'inputDateTime' => Element\InputDateTimeElement::class,
75 'inputLink' => Element\InputLinkElement::class,
76 'hidden' => Element\InputHiddenElement::class,
77 // rsaInput is defined with a fallback so extensions can use it even if ext:rsaauth is not loaded
78 'rsaInput' => Element\InputTextElement::class,
79 'imageManipulation' => Element\ImageManipulationElement::class,
80 'none' => Element\NoneElement::class,
81 'radio' => Element\RadioElement::class,
82 'selectCheckBox' => Element\SelectCheckBoxElement::class,
83 'selectMultipleSideBySide' => Element\SelectMultipleSideBySideElement::class,
84 'selectTree' => Element\SelectTreeElement::class,
85 'selectSingle' => Element\SelectSingleElement::class,
86 'selectSingleBox' => Element\SelectSingleBoxElement::class,
87 'colorpicker' => Element\InputColorPickerElement::class,
88 // t3editor is defined with a fallback so extensions can use it even if ext:t3editor is not loaded
89 't3editor' => Element\TextElement::class,
90 'text' => Element\TextElement::class,
91 'textTable' => Element\TextTableElement::class,
92 'unknown' => Element\UnknownElement::class,
93 'user' => Element\UserElement::class,
94 // special renderType for type="user" on sys_file_storage is_public column
95 'userSysFileStorageIsPublic' => Element\UserSysFileStorageIsPublicElement::class,
96 'fileInfo' => Element\FileInfoElement::class,
97 'slug' => Element\InputSlugElement::class,
98 'passthrough' => Element\PassThroughElement::class,
99
100 // Default classes to enrich single elements
101 'fieldControl' => NodeExpansion\FieldControl::class,
102 'fieldInformation' => NodeExpansion\FieldInformation::class,
103 'fieldWizard' => NodeExpansion\FieldWizard::class,
104
105 // Element information
106 'tcaDescription' => FieldInformation\TcaDescription::class,
107
108 // Element wizards
109 'defaultLanguageDifferences' => FieldWizard\DefaultLanguageDifferences::class,
110 'fileThumbnails' => FieldWizard\FileThumbnails::class,
111 'fileTypeList' => FieldWizard\FileTypeList::class,
112 'fileUpload' => FieldWizard\FileUpload::class,
113 'localizationStateSelector' => FieldWizard\LocalizationStateSelector::class,
114 'otherLanguageContent' => FieldWizard\OtherLanguageContent::class,
115 'recordsOverview' => FieldWizard\RecordsOverview::class,
116 'selectIcons' => FieldWizard\SelectIcons::class,
117 'tableList' => FieldWizard\TableList::class,
118
119 // Element controls
120 'addRecord' => FieldControl\AddRecord::class,
121 'editPopup' => FieldControl\EditPopup::class,
122 'elementBrowser' => FieldControl\ElementBrowser::class,
123 'insertClipboard' => FieldControl\InsertClipboard::class,
124 'linkPopup' => FieldControl\LinkPopup::class,
125 'listModule' => FieldControl\ListModule::class,
126 'resetSelection' => FieldControl\ResetSelection::class,
127 'tableWizard' => FieldControl\TableWizard::class,
128 ];
129
130 /**
131 * Set up factory. Initialize additionally registered nodes.
132 */
133 public function __construct()
134 {
135 $this->registerAdditionalNodeTypesFromConfiguration();
136 $this->initializeNodeResolver();
137 }
138
139 /**
140 * Create a node depending on type
141 *
142 * @param array $data All information to decide which class should be instantiated and given down to sub nodes
143 * @return AbstractNode
144 * @throws Exception
145 */
146 public function create(array $data)
147 {
148 if (empty($data['renderType'])) {
149 throw new Exception(
150 'Missing "renderType" in TCA of field "[' . ($data['tableName'] ?? 'unknown') . '][' . ($data['fieldName'] ?? 'unknown') . ']".',
151 1431452406
152 );
153 }
154 $type = $data['renderType'];
155
156 $className = $this->nodeTypes[$type] ?? $this->nodeTypes['unknown'];
157
158 if (!empty($this->nodeResolver[$type])) {
159 // Resolver with highest priority is called first. If it returns with a new class name,
160 // it will be taken and loop is aborted, otherwise resolver with next lower priority is called.
161 foreach ($this->nodeResolver[$type] as $priority => $resolverClassName) {
162 /** @var NodeResolverInterface $resolver */
163 $resolver = $this->instantiate($resolverClassName, $data);
164 if (!$resolver instanceof NodeResolverInterface) {
165 throw new Exception(
166 'Node resolver for type ' . $type . ' at priority ' . $priority . ' must implement NodeResolverInterface',
167 1433157422
168 );
169 }
170 // Resolver classes do NOT receive the name of the already resolved class. Single
171 // resolvers should not have dependencies to each other or the default implementation,
172 // so they also shouldn't know the output of a different resolving class.
173 // Additionally, the globalOptions array is NOT given by reference here, changing config is a
174 // task of container classes alone and must not be abused here.
175 $newClassName = $resolver->resolve();
176 if ($newClassName !== null) {
177 $className = $newClassName;
178 break;
179 }
180 }
181 }
182
183 /** @var AbstractNode $nodeInstance */
184 $nodeInstance = $this->instantiate($className, $data);
185 if (!$nodeInstance instanceof NodeInterface) {
186 throw new Exception('Node of type ' . get_class($nodeInstance) . ' must implement NodeInterface', 1431872546);
187 }
188 return $nodeInstance;
189 }
190
191 /**
192 * Add node types from nodeRegistry to $this->nodeTypes.
193 * This can be used to add new render types or to overwrite existing node types. The registered class must
194 * implement the NodeInterface and will be called if a node with this renderType is rendered.
195 *
196 * @throws Exception if configuration is incomplete or two nodes with identical priorities are registered
197 */
198 protected function registerAdditionalNodeTypesFromConfiguration()
199 {
200 // List of additional or override nodes
201 $registeredTypeOverrides = $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'];
202 // Sanitize input array
203 $registeredPrioritiesForNodeNames = [];
204 foreach ($registeredTypeOverrides as $override) {
205 if (!isset($override['nodeName']) || !isset($override['class']) || !isset($override['priority'])) {
206 throw new Exception(
207 'Key class, nodeName or priority missing for an entry in $GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'formEngine\'][\'nodeRegistry\']',
208 1432207533
209 );
210 }
211 if ($override['priority'] < 0 || $override['priority'] > 100) {
212 throw new Exception(
213 'Priority of element ' . $override['nodeName'] . ' with class ' . $override['class'] . ' is ' . $override['priority'] . ', but must between 0 and 100',
214 1432223531
215 );
216 }
217 if (isset($registeredPrioritiesForNodeNames[$override['nodeName']][$override['priority']])) {
218 throw new Exception(
219 'Element ' . $override['nodeName'] . ' already has an override registered with priority ' . $override['priority'],
220 1432223893
221 );
222 }
223 $registeredPrioritiesForNodeNames[$override['nodeName']][$override['priority']] = '';
224 }
225 // Add element with highest priority to registry
226 $highestPriority = [];
227 foreach ($registeredTypeOverrides as $override) {
228 if (!isset($highestPriority[$override['nodeName']]) || $override['priority'] > $highestPriority[$override['nodeName']]) {
229 $highestPriority[$override['nodeName']] = $override['priority'];
230 $this->nodeTypes[$override['nodeName']] = $override['class'];
231 }
232 }
233 }
234
235 /**
236 * Add resolver and add them sorted to a local property.
237 * This can be used to manipulate the nodeName to class resolution with own code.
238 *
239 * @throws Exception if configuration is incomplete or two resolver with identical priorities are registered
240 */
241 protected function initializeNodeResolver()
242 {
243 // List of node resolver
244 $registeredNodeResolvers = $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeResolver'];
245 $resolversByType = [];
246 foreach ($registeredNodeResolvers as $nodeResolver) {
247 if (!isset($nodeResolver['nodeName']) || !isset($nodeResolver['class']) || !isset($nodeResolver['priority'])) {
248 throw new Exception(
249 'Key class, nodeName or priority missing for an entry in $GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'formEngine\'][\'nodeResolver\']',
250 1433155522
251 );
252 }
253 if ($nodeResolver['priority'] < 0 || $nodeResolver['priority'] > 100) {
254 throw new Exception(
255 'Priority of element ' . $nodeResolver['nodeName'] . ' with class ' . $nodeResolver['class'] . ' is ' . $nodeResolver['priority'] . ', but must between 0 and 100',
256 1433155563
257 );
258 }
259 if (isset($resolversByType[$nodeResolver['nodeName']][$nodeResolver['priority']])) {
260 throw new Exception(
261 'Element ' . $nodeResolver['nodeName'] . ' already has a resolver registered with priority ' . $nodeResolver['priority'],
262 1433155705
263 );
264 }
265 $resolversByType[$nodeResolver['nodeName']][$nodeResolver['priority']] = $nodeResolver['class'];
266 }
267 $sortedResolversByType = [];
268 foreach ($resolversByType as $nodeName => $prioritiesAndClasses) {
269 krsort($prioritiesAndClasses);
270 $sortedResolversByType[$nodeName] = $prioritiesAndClasses;
271 }
272 $this->nodeResolver = $sortedResolversByType;
273 }
274
275 /**
276 * Instantiate given class name
277 *
278 * @param string $className Given class name
279 * @param array $data Main data array
280 * @return object
281 */
282 protected function instantiate($className, array $data)
283 {
284 return GeneralUtility::makeInstance($className, $this, $data);
285 }
286 }