663bb83fda0ae282cc118e5e878f96a9aa61bac7
[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 use TYPO3\CMS\Backend\Form\Container;
19 use TYPO3\CMS\Backend\Form\Element;
20
21 /**
22 * Create an element object depending on type.
23 */
24 class NodeFactory {
25
26 /**
27 * Default registry of node name to handling class
28 *
29 * @var array
30 */
31 protected $nodeTypes = array(
32 'flex' => Container\FlexFormEntryContainer::class,
33 'flexFormContainerContainer' => Container\FlexFormContainerContainer::class,
34 'flexFormElementContainer' => Container\FlexFormElementContainer::class,
35 'flexFormNoTabsContainer' => Container\FlexFormNoTabsContainer::class,
36 'flexFormSectionContainer' => Container\FlexFormSectionContainer::class,
37 'flexFormTabsContainer' => Container\FlexFormTabsContainer::class,
38 'fullRecordContainer' => Container\FullRecordContainer::class,
39 'inline' => Container\InlineControlContainer::class,
40 'inlineRecordContainer' => Container\InlineRecordContainer::class,
41 'listOfFieldsContainer' => Container\ListOfFieldsContainer::class,
42 'noTabsContainer' => Container\NoTabsContainer::class,
43 'outerWrapContainer' => Container\OuterWrapContainer::class,
44 'paletteAndSingleContainer' => Container\PaletteAndSingleContainer::class,
45 'singleFieldContainer' => Container\SingleFieldContainer::class,
46 'soloFieldContainer' => Container\SoloFieldContainer::class,
47 'tabsContainer' => Container\TabsContainer::class,
48
49 'check' => Element\CheckboxElement::class,
50 'group' => Element\GroupElement::class,
51 'input' => Element\InputTextElement::class,
52 'hidden' => Element\InputHiddenElement::class,
53 // rsaInput is defined with a fallback so extensions can use it even if ext:rsaauth is not loaded
54 'rsaInput' => Element\InputTextElement::class,
55 'imageManipulation' => Element\ImageManipulationElement::class,
56 'none' => Element\NoneElement::class,
57 'radio' => Element\RadioElement::class,
58 'selectCheckBox' => Element\SelectCheckBoxElement::class,
59 'selectMultipleSideBySide' => Element\SelectMultipleSideBySideElement::class,
60 'selectTree' => Element\SelectTreeElement::class,
61 'selectSingle' => Element\SelectSingleElement::class,
62 'selectSingleBox' => Element\SelectSingleBoxElement::class,
63 // t3editor is defined with a fallback so extensions can use it even if ext:t3editor is not loaded
64 't3editor' => Element\TextElement::class,
65 'text' => Element\TextElement::class,
66 'unknown' => Element\UnknownElement::class,
67 'user' => Element\UserElement::class,
68 );
69
70 /**
71 * Node resolver classes
72 * Nested array with nodeName as key, (sorted) priority as sub key and class as value
73 *
74 * @var array
75 */
76 protected $nodeResolver = array();
77
78 /**
79 * Set up factory. Initialize additionally registered nodes.
80 */
81 public function __construct() {
82 $this->registerAdditionalNodeTypesFromConfiguration();
83 $this->initializeNodeResolver();
84 }
85
86 /**
87 * Create a node depending on type
88 *
89 * @param array $data All information to decide which class should be instantiated and given down to sub nodes
90 * @return AbstractNode
91 * @throws Exception
92 */
93 public function create(array $data) {
94 if (empty($data['renderType'])) {
95 throw new Exception('No renderType definition found', 1431452406);
96 }
97 $type = $data['renderType'];
98
99 if ($type === 'select') {
100 $config = $data['parameterArray']['fieldConf']['config'];
101 $maxItems = (int)$config['maxitems'];
102 if (isset($config['renderMode']) && $config['renderMode'] === 'tree') {
103 $type = 'selectTree';
104 } elseif ($maxItems <= 1) {
105 $type = 'selectSingle';
106 } elseif (isset($config['renderMode']) && $config['renderMode'] === 'singlebox') {
107 $type = 'selectSingleBox';
108 } elseif (isset($config['renderMode']) && $config['renderMode'] === 'checkbox') {
109 $type = 'selectCheckBox';
110 } else {
111 // @todo: This "catch all" else should be removed to allow registration of own renderTypes for type=select
112 $type = 'selectMultipleSideBySide';
113 }
114 }
115
116 $className = isset($this->nodeTypes[$type]) ? $this->nodeTypes[$type] : $this->nodeTypes['unknown'];
117
118 if (!empty($this->nodeResolver[$type])) {
119 // Resolver with highest priority is called first. If it returns with a new class name,
120 // it will be taken and loop is aborted, otherwise resolver with next lower priority is called.
121 foreach ($this->nodeResolver[$type] as $priority => $resolverClassName) {
122 /** @var NodeResolverInterface $resolver */
123 $resolver = $this->instantiate($resolverClassName, $data);
124 if (!$resolver instanceof NodeResolverInterface) {
125 throw new Exception(
126 'Node resolver for type ' . $type . ' at priority ' . $priority . ' must implement NodeResolverInterface',
127 1433157422
128 );
129 }
130 // Resolver classes do NOT receive the name of the already resolved class. Single
131 // resolvers should not have dependencies to each other or the default implementation,
132 // so they also shouldn't know the output of a different resolving class.
133 // Additionally, the globalOptions array is NOT given by reference here, changing config is a
134 // task of container classes alone and must not be abused here.
135 $newClassName = $resolver->resolve();
136 if ($newClassName !== NULL) {
137 $className = $newClassName;
138 break;
139 }
140 }
141 }
142
143 /** @var AbstractNode $nodeInstance */
144 $nodeInstance = $this->instantiate($className, $data);
145 if (!$nodeInstance instanceof NodeInterface) {
146 throw new Exception('Node of type ' . get_class($nodeInstance) . ' must implement NodeInterface', 1431872546);
147 }
148 return $nodeInstance;
149 }
150
151 /**
152 * Add node types from nodeRegistry to $this->nodeTypes.
153 * This can be used to add new render types or to overwrite existing node types. The registered class must
154 * implement the NodeInterface and will be called if a node with this renderType is rendered.
155 *
156 * @throws Exception if configuration is incomplete or two nodes with identical priorities are registered
157 */
158 protected function registerAdditionalNodeTypesFromConfiguration() {
159 // List of additional or override nodes
160 $registeredTypeOverrides = $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'];
161 // Sanitize input array
162 $registeredPrioritiesForNodeNames = array();
163 foreach ($registeredTypeOverrides as $override) {
164 if (!isset($override['nodeName']) || !isset($override['class']) || !isset($override['priority'])) {
165 throw new Exception(
166 'Key class, nodeName or priority missing for an entry in $GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'formEngine\'][\'nodeRegistry\']',
167 1432207533
168 );
169 }
170 if ($override['priority'] < 0 || $override['priority'] > 100) {
171 throw new Exception(
172 'Priority of element ' . $override['nodeName'] . ' with class ' . $override['class'] . ' is ' . $override['priority'] . ', but must between 0 and 100',
173 1432223531
174 );
175 }
176 if (isset($registeredPrioritiesForNodeNames[$override['nodeName']][$override['priority']])) {
177 throw new Exception(
178 'Element ' . $override['nodeName'] . ' already has an override registered with priority ' . $override['priority'],
179 1432223893
180 );
181 }
182 $registeredPrioritiesForNodeNames[$override['nodeName']][$override['priority']] = '';
183 }
184 // Add element with highest priority to registry
185 $highestPriority = array();
186 foreach ($registeredTypeOverrides as $override) {
187 if (!isset($highestPriority[$override['nodeName']]) || $override['priority'] > $highestPriority[$override['nodeName']]) {
188 $highestPriority[$override['nodeName']] = $override['priority'];
189 $this->nodeTypes[$override['nodeName']] = $override['class'];
190 }
191 }
192 }
193
194 /**
195 * Add resolver and add them sorted to a local property.
196 * This can be used to manipulate the nodeName to class resolution with own code.
197 *
198 * @throws Exception if configuration is incomplete or two resolver with identical priorities are registered
199 */
200 protected function initializeNodeResolver() {
201 // List of node resolver
202 $registeredNodeResolvers = $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeResolver'];
203 $resolversByType = array();
204 foreach ($registeredNodeResolvers as $nodeResolver) {
205 if (!isset($nodeResolver['nodeName']) || !isset($nodeResolver['class']) || !isset($nodeResolver['priority'])) {
206 throw new Exception(
207 'Key class, nodeName or priority missing for an entry in $GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'formEngine\'][\'nodeResolver\']',
208 1433155522
209 );
210 }
211 if ($nodeResolver['priority'] < 0 || $nodeResolver['priority'] > 100) {
212 throw new Exception(
213 'Priority of element ' . $nodeResolver['nodeName'] . ' with class ' . $nodeResolver['class'] . ' is ' . $nodeResolver['priority'] . ', but must between 0 and 100',
214 1433155563
215 );
216 }
217 if (isset($resolversByType[$nodeResolver['nodeName']][$nodeResolver['priority']])) {
218 throw new Exception(
219 'Element ' . $nodeResolver['nodeName'] . ' already has a resolver registered with priority ' . $nodeResolver['priority'],
220 1433155705
221 );
222 }
223 $resolversByType[$nodeResolver['nodeName']][$nodeResolver['priority']] = $nodeResolver['class'];
224 }
225 $sortedResolversByType = array();
226 foreach ($resolversByType as $nodeName => $prioritiesAndClasses) {
227 krsort($prioritiesAndClasses);
228 $sortedResolversByType[$nodeName] = $prioritiesAndClasses;
229 }
230 $this->nodeResolver = $sortedResolversByType;
231 }
232
233 /**
234 * Instantiate given class name
235 *
236 * @param string $className Given class name
237 * @param array $data Main data array
238 * @return object
239 */
240 protected function instantiate($className, array $data) {
241 return GeneralUtility::makeInstance($className, $this, $data);
242 }
243
244 }