[BUGFIX] BE select tree needs to validate minitems
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / Element / SelectTreeElement.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\Core\Authentication\BackendUserAuthentication;
19 use TYPO3\CMS\Core\Tree\TableConfiguration\ExtJsArrayTreeRenderer;
20 use TYPO3\CMS\Core\Tree\TableConfiguration\TableConfigurationTree;
21 use TYPO3\CMS\Core\Tree\TableConfiguration\TreeDataProviderFactory;
22 use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation;
23 use TYPO3\CMS\Core\Utility\GeneralUtility;
24
25 /**
26 * Render data as a tree.
27 *
28 * Typically rendered for config [type=select, renderMode=tree
29 */
30 class SelectTreeElement extends AbstractFormElement {
31
32 /**
33 * Render tree widget
34 *
35 * @return array As defined in initializeResultArray() of AbstractNode
36 */
37 public function render() {
38 $table = $this->data['tableName'];
39 $field = $this->data['fieldName'];
40 $row = $this->data['databaseRow'];
41 $parameterArray = $this->data['parameterArray'];
42
43 // Field configuration from TCA:
44 $config = $parameterArray['fieldConf']['config'];
45
46 $possibleSelectboxItems = $config['items'];
47
48 $selectedNodes = $parameterArray['itemFormElValue'];
49 if ($config['maxitems'] === 1 && count($selectedNodes) === 1 && empty($selectedNodes[0])) {
50 $selectedNodes = array();
51 }
52
53 $selectedNodesForApi = array();
54 foreach ($selectedNodes as $selectedNode) {
55 // @todo: this is ugly - the "old" pipe based value|label syntax is re-created here at the moment
56 foreach ($possibleSelectboxItems as $possibleSelectboxItem) {
57 if ((string)$possibleSelectboxItem[1] === (string)$selectedNode) {
58 $selectedNodesForApi[] = $selectedNode . '|' . rawurlencode($possibleSelectboxItem[0]);
59 }
60 }
61 }
62
63 $allowedUids = array();
64 foreach ($possibleSelectboxItems as $item) {
65 if ((int)$item[1] > 0) {
66 $allowedUids[] = $item[1];
67 }
68 }
69 $treeDataProvider = TreeDataProviderFactory::getDataProvider($config, $table, $field, $row);
70 $treeDataProvider->setSelectedList(implode(',', $selectedNodes));
71 $treeDataProvider->setItemWhiteList($allowedUids);
72 $treeDataProvider->initializeTreeData();
73 $treeRenderer = GeneralUtility::makeInstance(ExtJsArrayTreeRenderer::class);
74 $tree = GeneralUtility::makeInstance(TableConfigurationTree::class);
75 $tree->setDataProvider($treeDataProvider);
76 $tree->setNodeRenderer($treeRenderer);
77 $treeData = $tree->render();
78 $itemArray = array();
79
80 /**
81 * @todo: Small bug here: In the past, this was the "not processed list" of default items, but now it is
82 * @todo: a full list of elements. This needs to be fixed later, so "additional" default items are shown again.
83 if (is_array($config['items'])) {
84 foreach ($config['items'] as $additionalItem) {
85 if ($additionalItem[1] !== '--div--') {
86 $item = new \stdClass();
87 $item->uid = $additionalItem[1];
88 $item->text = $this->getLanguageService()->sL($additionalItem[0]);
89 $item->selectable = TRUE;
90 $item->leaf = TRUE;
91 $item->checked = in_array($additionalItem[1], $selectedNodes);
92 if (file_exists(PATH_typo3 . $additionalItem[3])) {
93 $item->icon = $additionalItem[3];
94 } elseif (trim($additionalItem[3]) !== '') {
95 $item->iconCls = IconUtility::getSpriteIconClasses($additionalItem[3]);
96 }
97 $itemArray[] = $item;
98 }
99 }
100 }
101 */
102
103 $itemArray[] = $treeData;
104 $treeData = json_encode($itemArray);
105 $id = md5($parameterArray['itemFormElName']);
106 if (isset($config['size']) && (int)$config['size'] > 0) {
107 $height = (int)$config['size'] * 20;
108 } else {
109 $height = 280;
110 }
111 $autoSizeMax = NULL;
112 if (isset($config['autoSizeMax']) && (int)$config['autoSizeMax'] > 0) {
113 $autoSizeMax = (int)$config['autoSizeMax'] * 20;
114 }
115 $header = FALSE;
116 $expanded = FALSE;
117 $width = 280;
118 $appearance = $config['treeConfig']['appearance'];
119 if (is_array($appearance)) {
120 $header = (bool)$appearance['showHeader'];
121 $expanded = (bool)$appearance['expandAll'];
122 if (isset($appearance['width'])) {
123 $width = (int)$appearance['width'];
124 }
125 }
126 $onChange = '';
127 if ($parameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged']) {
128 $onChange = $parameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged'];
129 }
130 // Create a JavaScript code line which will ask the user to save/update the form due to changing the element.
131 // This is used for eg. "type" fields and others configured with "requestUpdate"
132 if (
133 !empty($GLOBALS['TCA'][$table]['ctrl']['type'])
134 && $field === $GLOBALS['TCA'][$table]['ctrl']['type']
135 || !empty($GLOBALS['TCA'][$table]['ctrl']['requestUpdate'])
136 && GeneralUtility::inList(str_replace(' ', '', $GLOBALS['TCA'][$table]['ctrl']['requestUpdate']), $field)
137 ) {
138 if ($this->getBackendUserAuthentication()->jsConfirmation(JsConfirmation::TYPE_CHANGE)) {
139 $onChange .= 'if (confirm(TBE_EDITOR.labels.onChangeAlert) && ' . 'TBE_EDITOR.checkSubmit(-1)){ TBE_EDITOR.submitForm() };';
140 } else {
141 $onChange .= 'if (TBE_EDITOR.checkSubmit(-1)){ TBE_EDITOR.submitForm() };';
142 }
143 }
144 $html = '
145 <div class="typo3-tceforms-tree">
146 <input class="treeRecord" type="hidden" '
147 . $this->getValidationDataAsDataAttribute($config)
148 . ' data-formengine-input-name="' . htmlspecialchars($parameterArray['itemFormElName']) . '"'
149 . ' data-relatedfieldname="' . htmlspecialchars($parameterArray['itemFormElName']) . '"'
150 . ' name="' . htmlspecialchars($parameterArray['itemFormElName']) . '" id="treeinput' . $id . '" value="' . htmlspecialchars(implode(',', $selectedNodesForApi)) . '" />
151 </div>
152 <div id="tree_' . $id . '">
153
154 </div>';
155
156 // Wizards:
157 if (empty($config['readOnly'])) {
158 $html = $this->renderWizards(
159 array($html),
160 $config['wizards'],
161 $table,
162 $row,
163 $field,
164 $parameterArray,
165 $parameterArray['itemFormElName'],
166 BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras'])
167 );
168 }
169 $resultArray = $this->initializeResultArray();
170 $resultArray['extJSCODE'] .= LF .
171 'Ext.onReady(function() {
172 TYPO3.Components.Tree.StandardTreeItemData["' . $id . '"] = ' . $treeData . ';
173 var tree' . $id . ' = new TYPO3.Components.Tree.StandardTree({
174 id: "' . $id . '",
175 showHeader: ' . (int)$header . ',
176 onChange: "' . $onChange . '",
177 countSelectedNodes: ' . count($selectedNodes) . ',
178 width: ' . $width . ',
179 listeners: {
180 click: function(node, event) {
181 if (typeof(node.attributes.checked) == "boolean") {
182 node.attributes.checked = ! node.attributes.checked;
183 node.getUI().toggleCheck(node.attributes.checked);
184 }
185 },
186 dblclick: function(node, event) {
187 if (typeof(node.attributes.checked) == "boolean") {
188 node.attributes.checked = ! node.attributes.checked;
189 node.getUI().toggleCheck(node.attributes.checked);
190 }
191 },
192 checkchange: TYPO3.Components.Tree.TcaCheckChangeHandler,
193 collapsenode: function(node) {
194 if (node.id !== "root") {
195 top.TYPO3.Storage.Persistent.removeFromList("tcaTrees." + this.ucId, node.attributes.uid);
196 }
197 },
198 expandnode: function(node) {
199 if (node.id !== "root") {
200 top.TYPO3.Storage.Persistent.addToList("tcaTrees." + this.ucId, node.attributes.uid);
201 }
202 },
203 beforerender: function(treeCmp) {
204 // Check if that tree element is already rendered. It is appended on the first tceforms_inline call.
205 if (Ext.fly(treeCmp.getId())) {
206 return false;
207 }
208 }' . ($expanded ? ',
209 afterrender: function(treeCmp) {
210 treeCmp.expandAll();
211 }' : '') . '
212 },
213 tcaMaxItems: ' . ($config['maxitems'] ? (int)$config['maxitems'] : 99999) . ',
214 tcaSelectRecursiveAllowed: ' . ($appearance['allowRecursiveMode'] ? 'true' : 'false') . ',
215 tcaSelectRecursive: false,
216 tcaExclusiveKeys: "' . ($config['exclusiveKeys'] ? $config['exclusiveKeys'] : '') . '",
217 ucId: "' . md5(($table . '|' . $field)) . '",
218 selModel: TYPO3.Components.Tree.EmptySelectionModel,
219 disabled: ' . ($config['readOnly'] ? 'true' : 'false') . '
220 });' . LF .
221 ($autoSizeMax
222 ? 'tree' . $id . '.bodyStyle = "max-height: ' . $autoSizeMax . 'px;min-height: ' . $height . 'px;";'
223 : 'tree' . $id . '.height = ' . $height . ';'
224 ) . LF .
225 'window.setTimeout(function() {
226 tree' . $id . '.render("tree_' . $id . '");
227 }, 200);
228 });';
229 $resultArray['html'] = $html;
230
231 return $resultArray;
232 }
233
234 /**
235 * @return BackendUserAuthentication
236 */
237 protected function getBackendUserAuthentication() {
238 return $GLOBALS['BE_USER'];
239 }
240 }