[BUGFIX] Fix selected node count in select tree
[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" name="' . htmlspecialchars($parameterArray['itemFormElName']) . '" id="treeinput' . $id . '" value="' . htmlspecialchars(implode(',', $selectedNodesForApi)) . '" />
147 </div>
148 <div id="tree_' . $id . '">
149
150 </div>';
151
152 // Wizards:
153 if (empty($config['readOnly'])) {
154 $html = $this->renderWizards(
155 array($html),
156 $config['wizards'],
157 $table,
158 $row,
159 $field,
160 $parameterArray,
161 $parameterArray['itemFormElName'],
162 BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras'])
163 );
164 }
165 $resultArray = $this->initializeResultArray();
166 $resultArray['extJSCODE'] .= LF .
167 'Ext.onReady(function() {
168 TYPO3.Components.Tree.StandardTreeItemData["' . $id . '"] = ' . $treeData . ';
169 var tree' . $id . ' = new TYPO3.Components.Tree.StandardTree({
170 id: "' . $id . '",
171 showHeader: ' . (int)$header . ',
172 onChange: "' . $onChange . '",
173 countSelectedNodes: ' . count($selectedNodes) . ',
174 width: ' . $width . ',
175 listeners: {
176 click: function(node, event) {
177 if (typeof(node.attributes.checked) == "boolean") {
178 node.attributes.checked = ! node.attributes.checked;
179 node.getUI().toggleCheck(node.attributes.checked);
180 }
181 },
182 dblclick: function(node, event) {
183 if (typeof(node.attributes.checked) == "boolean") {
184 node.attributes.checked = ! node.attributes.checked;
185 node.getUI().toggleCheck(node.attributes.checked);
186 }
187 },
188 checkchange: TYPO3.Components.Tree.TcaCheckChangeHandler,
189 collapsenode: function(node) {
190 if (node.id !== "root") {
191 top.TYPO3.Storage.Persistent.removeFromList("tcaTrees." + this.ucId, node.attributes.uid);
192 }
193 },
194 expandnode: function(node) {
195 if (node.id !== "root") {
196 top.TYPO3.Storage.Persistent.addToList("tcaTrees." + this.ucId, node.attributes.uid);
197 }
198 },
199 beforerender: function(treeCmp) {
200 // Check if that tree element is already rendered. It is appended on the first tceforms_inline call.
201 if (Ext.fly(treeCmp.getId())) {
202 return false;
203 }
204 }' . ($expanded ? ',
205 afterrender: function(treeCmp) {
206 treeCmp.expandAll();
207 }' : '') . '
208 },
209 tcaMaxItems: ' . ($config['maxitems'] ? (int)$config['maxitems'] : 99999) . ',
210 tcaSelectRecursiveAllowed: ' . ($appearance['allowRecursiveMode'] ? 'true' : 'false') . ',
211 tcaSelectRecursive: false,
212 tcaExclusiveKeys: "' . ($config['exclusiveKeys'] ? $config['exclusiveKeys'] : '') . '",
213 ucId: "' . md5(($table . '|' . $field)) . '",
214 selModel: TYPO3.Components.Tree.EmptySelectionModel,
215 disabled: ' . ($config['readOnly'] ? 'true' : 'false') . '
216 });' . LF .
217 ($autoSizeMax
218 ? 'tree' . $id . '.bodyStyle = "max-height: ' . $autoSizeMax . 'px;min-height: ' . $height . 'px;";'
219 : 'tree' . $id . '.height = ' . $height . ';'
220 ) . LF .
221 'window.setTimeout(function() {
222 tree' . $id . '.render("tree_' . $id . '");
223 }, 200);
224 });';
225 $resultArray['html'] = $html;
226
227 return $resultArray;
228 }
229
230 /**
231 * @return BackendUserAuthentication
232 */
233 protected function getBackendUserAuthentication() {
234 return $GLOBALS['BE_USER'];
235 }
236 }