[BUGFIX] SelectTreeElement modify expandstate after tree was loaded
[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 {
39 $table = $this->data['tableName'];
40 $field = $this->data['fieldName'];
41 $row = $this->data['databaseRow'];
42 $parameterArray = $this->data['parameterArray'];
43
44 // Field configuration from TCA:
45 $config = $parameterArray['fieldConf']['config'];
46
47 $possibleSelectboxItems = $config['items'];
48
49 $selectedNodes = $parameterArray['itemFormElValue'];
50
51 $selectedNodesForApi = array();
52 foreach ($selectedNodes as $selectedNode) {
53 // @todo: this is ugly - the "old" pipe based value|label syntax is re-created here at the moment
54 foreach ($possibleSelectboxItems as $possibleSelectboxItem) {
55 if ((string)$possibleSelectboxItem[1] === (string)$selectedNode) {
56 $selectedNodesForApi[] = $selectedNode . '|' . rawurlencode($possibleSelectboxItem[0]);
57 }
58 }
59 }
60
61 $allowedUids = array();
62 foreach ($possibleSelectboxItems as $item) {
63 if ((int)$item[1] > 0) {
64 $allowedUids[] = $item[1];
65 }
66 }
67 $treeDataProvider = TreeDataProviderFactory::getDataProvider($config, $table, $field, $row);
68 $treeDataProvider->setSelectedList(implode(',', $selectedNodes));
69 $treeDataProvider->setItemWhiteList($allowedUids);
70 $treeDataProvider->initializeTreeData();
71 $treeRenderer = GeneralUtility::makeInstance(ExtJsArrayTreeRenderer::class);
72 $tree = GeneralUtility::makeInstance(TableConfigurationTree::class);
73 $tree->setDataProvider($treeDataProvider);
74 $tree->setNodeRenderer($treeRenderer);
75 $treeData = $tree->render();
76 $itemArray = array();
77
78 /**
79 * @todo: Small bug here: In the past, this was the "not processed list" of default items, but now it is
80 * @todo: a full list of elements. This needs to be fixed later, so "additional" default items are shown again.
81 if (is_array($config['items'])) {
82 foreach ($config['items'] as $additionalItem) {
83 if ($additionalItem[1] !== '--div--') {
84 $item = new \stdClass();
85 $item->uid = $additionalItem[1];
86 $item->text = $this->getLanguageService()->sL($additionalItem[0]);
87 $item->selectable = TRUE;
88 $item->leaf = TRUE;
89 $item->checked = in_array($additionalItem[1], $selectedNodes);
90 if (file_exists(PATH_typo3 . $additionalItem[3])) {
91 $item->icon = $additionalItem[3];
92 } elseif (trim($additionalItem[3]) !== '') {
93 $item->iconCls = IconUtility::getSpriteIconClasses($additionalItem[3]);
94 }
95 $itemArray[] = $item;
96 }
97 }
98 }
99 */
100
101 $itemArray[] = $treeData;
102 $id = md5($parameterArray['itemFormElName']);
103 if (isset($config['size']) && (int)$config['size'] > 0) {
104 $height = (int)$config['size'] * 20;
105 } else {
106 $height = 280;
107 }
108 $autoSizeMax = null;
109 if (isset($config['autoSizeMax']) && (int)$config['autoSizeMax'] > 0) {
110 $autoSizeMax = (int)$config['autoSizeMax'] * 20;
111 }
112 $header = false;
113 $expanded = false;
114 $width = 280;
115 $appearance = $config['treeConfig']['appearance'];
116 if (is_array($appearance)) {
117 $header = (bool)$appearance['showHeader'];
118 $expanded = (bool)$appearance['expandAll'];
119 if (isset($appearance['width'])) {
120 $width = (int)$appearance['width'];
121 }
122 }
123 $onChange = '';
124 if ($parameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged']) {
125 $onChange = $parameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged'];
126 }
127 // Create a JavaScript code line which will ask the user to save/update the form due to changing the element.
128 // This is used for eg. "type" fields and others configured with "requestUpdate"
129 if (
130 !empty($GLOBALS['TCA'][$table]['ctrl']['type'])
131 && $field === $GLOBALS['TCA'][$table]['ctrl']['type']
132 || !empty($GLOBALS['TCA'][$table]['ctrl']['requestUpdate'])
133 && GeneralUtility::inList(str_replace(' ', '', $GLOBALS['TCA'][$table]['ctrl']['requestUpdate']), $field)
134 ) {
135 if ($this->getBackendUserAuthentication()->jsConfirmation(JsConfirmation::TYPE_CHANGE)) {
136 $onChange = 'top.TYPO3.Modal.confirm(TBE_EDITOR.labels.refreshRequired.title, TBE_EDITOR.labels.refreshRequired.content).on("button.clicked", function(e) { if (e.target.name == "ok" && TBE_EDITOR.checkSubmit(-1)) { TBE_EDITOR.submitForm() } top.TYPO3.Modal.dismiss(); });';
137 } else {
138 $onChange .= 'if (TBE_EDITOR.checkSubmit(-1)){ TBE_EDITOR.submitForm() };';
139 }
140 }
141 $html = '
142 <div class="typo3-tceforms-tree">
143 <input class="treeRecord" type="hidden" '
144 . $this->getValidationDataAsDataAttribute($config)
145 . ' data-formengine-input-name="' . htmlspecialchars($parameterArray['itemFormElName']) . '"'
146 . ' data-relatedfieldname="' . htmlspecialchars($parameterArray['itemFormElName']) . '"'
147 . ' name="' . htmlspecialchars($parameterArray['itemFormElName']) . '" id="treeinput' . $id . '" value="' . htmlspecialchars(implode(',', $selectedNodesForApi)) . '" />
148 </div>
149 <div id="tree_' . $id . '">
150
151 </div>';
152
153 // Wizards:
154 if (empty($config['readOnly'])) {
155 $html = $this->renderWizards(
156 array($html),
157 $config['wizards'],
158 $table,
159 $row,
160 $field,
161 $parameterArray,
162 $parameterArray['itemFormElName'],
163 BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras'])
164 );
165 }
166 $resultArray = $this->initializeResultArray();
167 $resultArray['extJSCODE'] .= LF .
168 'Ext.onReady(function() {
169 TYPO3.Components.Tree.StandardTreeItemData["' . $id . '"] = ' . json_encode($itemArray) . ';
170 var tree' . $id . ' = new TYPO3.Components.Tree.StandardTree({
171 id: "' . $id . '",
172 showHeader: ' . (int)$header . ',
173 onChange: ' . GeneralUtility::quoteJSvalue($onChange) . ',
174 countSelectedNodes: ' . count($selectedNodes) . ',
175 width: ' . (int)$width . ',
176 rendering: false,
177 listeners: {
178 click: function(node, event) {
179 if (typeof(node.attributes.checked) == "boolean") {
180 node.attributes.checked = ! node.attributes.checked;
181 node.getUI().toggleCheck(node.attributes.checked);
182 }
183 },
184 dblclick: function(node, event) {
185 if (typeof(node.attributes.checked) == "boolean") {
186 node.attributes.checked = ! node.attributes.checked;
187 node.getUI().toggleCheck(node.attributes.checked);
188 }
189 },
190 checkchange: TYPO3.Components.Tree.TcaCheckChangeHandler,
191 collapsenode: function(node) {
192 if (node.id !== "root" && !this.rendering) {
193 top.TYPO3.Storage.Persistent.removeFromList("tcaTrees." + this.ucId, node.attributes.uid);
194 }
195 },
196 expandnode: function(node) {
197 if (node.id !== "root" && !this.rendering) {
198 top.TYPO3.Storage.Persistent.addToList("tcaTrees." + this.ucId, node.attributes.uid);
199 }
200 },
201 beforerender: function(treeCmp) {
202 this.rendering = true;
203 // Check if that tree element is already rendered. It is appended on the first tceforms_inline call.
204 if (Ext.fly(treeCmp.getId())) {
205 return false;
206 }
207 },
208 afterrender: function(treeCmp) {
209 ' . ($expanded ? 'treeCmp.expandAll();' : '') . '
210 this.rendering = false;
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 {
239 return $GLOBALS['BE_USER'];
240 }
241 }