[BUGFIX] dbal: Cast field to CHAR for FIND_IN_SET()
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Tree / TableConfiguration / DatabaseTreeDataProvider.php
1 <?php
2 namespace TYPO3\CMS\Core\Tree\TableConfiguration;
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\Utility\GeneralUtility;
19
20 /**
21 * TCA tree data provider
22 */
23 class DatabaseTreeDataProvider extends AbstractTableConfigurationTreeDataProvider {
24
25 const SIGNAL_PostProcessTreeData = 'PostProcessTreeData';
26 const MODE_CHILDREN = 1;
27 const MODE_PARENT = 2;
28
29 /**
30 * @var string
31 */
32 protected $tableName = '';
33
34 /**
35 * @var string
36 */
37 protected $treeId = '';
38
39 /**
40 * @var string
41 */
42 protected $labelField = '';
43
44 /**
45 * @var string
46 */
47 protected $tableWhere = '';
48
49 /**
50 * @var int
51 */
52 protected $lookupMode = self::MODE_CHILDREN;
53
54 /**
55 * @var string
56 */
57 protected $lookupField = '';
58
59 /**
60 * @var int
61 */
62 protected $rootUid = 0;
63
64 /**
65 * @var array
66 */
67 protected $idCache = array();
68
69 /**
70 * Stores TCA-Configuration of the LookUpField in tableName
71 *
72 * @var array
73 */
74 protected $columnConfiguration;
75
76 /**
77 * node sort values (the orderings from foreign_Table_where evaluation)
78 *
79 * @var array
80 */
81 protected $nodeSortValues = array();
82
83 /**
84 * @var array TCEforms compiled TSConfig array
85 */
86 protected $generatedTSConfig = array();
87
88 /**
89 * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
90 */
91 protected $signalSlotDispatcher;
92
93 /**
94 * Sets the label field
95 *
96 * @param string $labelField
97 * @return void
98 */
99 public function setLabelField($labelField) {
100 $this->labelField = $labelField;
101 }
102
103 /**
104 * Gets the label field
105 *
106 * @return string
107 */
108 public function getLabelField() {
109 return $this->labelField;
110 }
111
112 /**
113 * Sets the table name
114 *
115 * @param string $tableName
116 * @return void
117 */
118 public function setTableName($tableName) {
119 $this->tableName = $tableName;
120 }
121
122 /**
123 * Gets the table name
124 *
125 * @return string
126 */
127 public function getTableName() {
128 return $this->tableName;
129 }
130
131 /**
132 * Sets the lookup field
133 *
134 * @param string $lookupField
135 * @return void
136 */
137 public function setLookupField($lookupField) {
138 $this->lookupField = $lookupField;
139 }
140
141 /**
142 * Gets the lookup field
143 *
144 * @return string
145 */
146 public function getLookupField() {
147 return $this->lookupField;
148 }
149
150 /**
151 * Sets the lookup mode
152 *
153 * @param int $lookupMode
154 * @return void
155 */
156 public function setLookupMode($lookupMode) {
157 $this->lookupMode = $lookupMode;
158 }
159
160 /**
161 * Gets the lookup mode
162 *
163 * @return int
164 */
165 public function getLookupMode() {
166 return $this->lookupMode;
167 }
168
169 /**
170 * Gets the nodes
171 *
172 * @param \TYPO3\CMS\Backend\Tree\TreeNode $node
173 * @return \TYPO3\CMS\Backend\Tree\TreeNodeCollection
174 */
175 public function getNodes(\TYPO3\CMS\Backend\Tree\TreeNode $node) {
176
177 }
178
179 /**
180 * Gets the root node
181 *
182 * @return \TYPO3\CMS\Core\Tree\TableConfiguration\DatabaseTreeNode
183 */
184 public function getRoot() {
185 return $this->buildRepresentationForNode($this->treeData);
186 }
187
188 /**
189 * Sets the root uid
190 *
191 * @param int $rootUid
192 * @return void
193 */
194 public function setRootUid($rootUid) {
195 $this->rootUid = $rootUid;
196 }
197
198 /**
199 * Gets the root uid
200 *
201 * @return int
202 */
203 public function getRootUid() {
204 return $this->rootUid;
205 }
206
207 /**
208 * Sets the tableWhere clause
209 *
210 * @param string $tableWhere
211 * @return void
212 */
213 public function setTableWhere($tableWhere) {
214 $this->tableWhere = $tableWhere;
215 }
216
217 /**
218 * Gets the tableWhere clause
219 *
220 * @return string
221 */
222 public function getTableWhere() {
223 return $this->tableWhere;
224 }
225
226 /**
227 * Builds a complete node including childs
228 *
229 * @param \TYPO3\CMS\Backend\Tree\TreeNode $basicNode
230 * @param NULL|\TYPO3\CMS\Core\Tree\TableConfiguration\DatabaseTreeNode $parent
231 * @param int $level
232 * @return \TYPO3\CMS\Core\Tree\TableConfiguration\DatabaseTreeNode Node object
233 */
234 protected function buildRepresentationForNode(\TYPO3\CMS\Backend\Tree\TreeNode $basicNode, DatabaseTreeNode $parent = NULL, $level = 0) {
235 /** @var $node \TYPO3\CMS\Core\Tree\TableConfiguration\DatabaseTreeNode */
236 $node = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Tree\TableConfiguration\DatabaseTreeNode::class);
237 $row = array();
238 if ($basicNode->getId() == 0) {
239 $node->setSelected(FALSE);
240 $node->setExpanded(TRUE);
241 $node->setLabel($GLOBALS['LANG']->sL($GLOBALS['TCA'][$this->tableName]['ctrl']['title']));
242 } else {
243 $row = BackendUtility::getRecordWSOL($this->tableName, $basicNode->getId(), '*', '', FALSE);
244 $node->setLabel(BackendUtility::getRecordTitle($this->tableName, $row) ?: $basicNode->getId());
245 $node->setSelected(GeneralUtility::inList($this->getSelectedList(), $basicNode->getId()));
246 $node->setExpanded($this->isExpanded($basicNode));
247 }
248 $node->setId($basicNode->getId());
249 $node->setSelectable(!GeneralUtility::inList($this->getNonSelectableLevelList(), $level) && !in_array($basicNode->getId(), $this->getItemUnselectableList()));
250 $node->setSortValue($this->nodeSortValues[$basicNode->getId()]);
251 $node->setIcon(\TYPO3\CMS\Backend\Utility\IconUtility::mapRecordTypeToSpriteIconClass($this->tableName, $row));
252 $node->setParentNode($parent);
253 if ($basicNode->hasChildNodes()) {
254 $node->setHasChildren(TRUE);
255 /** @var $childNodes \TYPO3\CMS\Backend\Tree\SortedTreeNodeCollection */
256 $childNodes = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Tree\SortedTreeNodeCollection::class);
257 foreach ($basicNode->getChildNodes() as $child) {
258 $childNodes->append($this->buildRepresentationForNode($child, $node, $level + 1));
259 }
260 $node->setChildNodes($childNodes);
261 }
262 return $node;
263 }
264
265 /**
266 * Init the tree data
267 *
268 * @return void
269 */
270 public function initializeTreeData() {
271 parent::initializeTreeData();
272 $this->nodeSortValues = array_flip($this->itemWhiteList);
273 $this->columnConfiguration = $GLOBALS['TCA'][$this->getTableName()]['columns'][$this->getLookupField()]['config'];
274 if (isset($this->columnConfiguration['foreign_table']) && $this->columnConfiguration['foreign_table'] != $this->getTableName()) {
275 throw new \InvalidArgumentException('TCA Tree configuration is invalid: tree for different node-Tables is not implemented yet', 1290944650);
276 }
277 $this->treeData = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Tree\TreeNode::class);
278 $this->loadTreeData();
279 $this->emitPostProcessTreeDataSignal();
280 }
281
282 /**
283 * Loads the tree data (all possible children)
284 *
285 * @return void
286 */
287 protected function loadTreeData() {
288 $this->treeData->setId($this->getRootUid());
289 $this->treeData->setParentNode(NULL);
290 if ($this->levelMaximum >= 1) {
291 $childNodes = $this->getChildrenOf($this->treeData, 1);
292 if ($childNodes !== NULL) {
293 $this->treeData->setChildNodes($childNodes);
294 }
295 }
296 }
297
298 /**
299 * Gets node children
300 *
301 * @param \TYPO3\CMS\Backend\Tree\TreeNode $node
302 * @param int $level
303 * @return NULL|\TYPO3\CMS\Backend\Tree\TreeNodeCollection
304 */
305 protected function getChildrenOf(\TYPO3\CMS\Backend\Tree\TreeNode $node, $level) {
306 $nodeData = NULL;
307 if ($node->getId() !== 0) {
308 $nodeData = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('*', $this->tableName, 'uid=' . $node->getId());
309 }
310 if ($nodeData == NULL) {
311 $nodeData = array(
312 'uid' => 0,
313 $this->getLookupField() => ''
314 );
315 }
316 $storage = NULL;
317 $children = $this->getRelatedRecords($nodeData);
318 if (!empty($children)) {
319 /** @var $storage \TYPO3\CMS\Backend\Tree\TreeNodeCollection */
320 $storage = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Tree\TreeNodeCollection::class);
321 foreach ($children as $child) {
322 $node = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Tree\TreeNode::class);
323 $node->setId($child);
324 if ($level < $this->levelMaximum) {
325 $children = $this->getChildrenOf($node, $level + 1);
326 if ($children !== NULL) {
327 $node->setChildNodes($children);
328 }
329 }
330 $storage->append($node);
331 }
332 }
333 return $storage;
334 }
335
336 /**
337 * Gets related records depending on TCA configuration
338 *
339 * @param array $row
340 * @return array
341 */
342 protected function getRelatedRecords(array $row) {
343 if ($this->getLookupMode() == DatabaseTreeDataProvider::MODE_PARENT) {
344 $children = $this->getChildrenUidsFromParentRelation($row);
345 } else {
346 $children = $this->getChildrenUidsFromChildrenRelation($row);
347 }
348 $allowedArray = array();
349 foreach ($children as $child) {
350 if (!in_array($child, $this->idCache) && in_array($child, $this->itemWhiteList)) {
351 $allowedArray[] = $child;
352 }
353 }
354 $this->idCache = array_merge($this->idCache, $allowedArray);
355 return $allowedArray;
356 }
357
358 /**
359 * Gets related records depending on TCA configuration
360 *
361 * @param array $row
362 * @return array
363 */
364 protected function getChildrenUidsFromParentRelation(array $row) {
365 $uid = $row['uid'];
366 switch ((string)$this->columnConfiguration['type']) {
367 case 'inline':
368
369 case 'select':
370 if ($this->columnConfiguration['MM']) {
371 /** @var $dbGroup \TYPO3\CMS\Core\Database\RelationHandler */
372 $dbGroup = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\RelationHandler::class);
373 // Dummy field for setting "look from other site"
374 $this->columnConfiguration['MM_oppositeField'] = 'children';
375 $dbGroup->start($row[$this->getLookupField()], $this->getTableName(), $this->columnConfiguration['MM'], $uid, $this->getTableName(), $this->columnConfiguration);
376 $relatedUids = $dbGroup->tableArray[$this->getTableName()];
377 } elseif ($this->columnConfiguration['foreign_field']) {
378 $relatedUids = $this->listFieldQuery($this->columnConfiguration['foreign_field'], $uid);
379 } else {
380 $relatedUids = $this->listFieldQuery($this->getLookupField(), $uid);
381 }
382 break;
383 default:
384 $relatedUids = $this->listFieldQuery($this->getLookupField(), $uid);
385 }
386 return $relatedUids;
387 }
388
389 /**
390 * Gets related children records depending on TCA configuration
391 *
392 * @param array $row
393 * @return array
394 */
395 protected function getChildrenUidsFromChildrenRelation(array $row) {
396 $relatedUids = array();
397 $uid = $row['uid'];
398 $value = $row[$this->getLookupField()];
399 switch ((string)$this->columnConfiguration['type']) {
400 case 'inline':
401
402 case 'select':
403 if ($this->columnConfiguration['MM']) {
404 /** @var $dbGroup \TYPO3\CMS\Core\Database\RelationHandler */
405 $dbGroup = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\RelationHandler::class);
406 $dbGroup->start($value, $this->getTableName(), $this->columnConfiguration['MM'], $uid, $this->getTableName(), $this->columnConfiguration);
407 $relatedUids = $dbGroup->tableArray[$this->getTableName()];
408 } elseif ($this->columnConfiguration['foreign_field']) {
409 $records = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid', $this->getTableName(), $this->columnConfiguration['foreign_field'] . '=' . (int)$uid);
410 foreach ($records as $record) {
411 $relatedUids[] = $record['uid'];
412 }
413 } else {
414 $relatedUids = GeneralUtility::intExplode(',', $value, TRUE);
415 }
416 break;
417 default:
418 $relatedUids = GeneralUtility::intExplode(',', $value, TRUE);
419 }
420 return $relatedUids;
421 }
422
423 /**
424 * Queries the table for an field which might contain a list.
425 *
426 * @param string $fieldName the name of the field to be queried
427 * @param int $queryId the uid to search for
428 * @return int[] all uids found
429 */
430 protected function listFieldQuery($fieldName, $queryId) {
431 $records = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid', $this->getTableName(), $GLOBALS['TYPO3_DB']->listQuery($fieldName, (int)$queryId, $this->getTableName()) . ((int)$queryId === 0 ? ' OR CAST(' . $fieldName . ' AS CHAR) = \'\'' : ''));
432 $uidArray = array();
433 foreach ($records as $record) {
434 $uidArray[] = $record['uid'];
435 }
436 return $uidArray;
437 }
438
439 /**
440 * Emits the post processing tree data signal.
441 *
442 * @return void
443 */
444 protected function emitPostProcessTreeDataSignal() {
445 $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Core\Tree\TableConfiguration\DatabaseTreeDataProvider::class,
446 self::SIGNAL_PostProcessTreeData,
447 array($this, $this->treeData)
448 );
449 $this->emitDeprecatedPostProcessTreeDataSignal();
450 }
451
452 /**
453 * A wrong signal name was introduced with https://review.typo3.org/#/c/34855/
454 * This function handles the old signal name and logs a deprecation warning.
455 *
456 * @return void
457 * @deprecated since TYPO3 CMS 7, will be removed in TYPO3 CMS 8
458 */
459 protected function emitDeprecatedPostProcessTreeDataSignal() {
460 $deprecatedSlots = $this->getSignalSlotDispatcher()->getSlots(
461 'TYPO3\\CMS\\Core\\Tree\\TableConfiguration\\TableConfiguration\\DatabaseTreeDataProvider',
462 self::SIGNAL_PostProcessTreeData
463 );
464 if (!empty($deprecatedSlots)) {
465 foreach ($deprecatedSlots as $slotInformation) {
466 $slotClassNameOrObject = $slotInformation['object'] ? get_class($slotInformation['object']) : $slotInformation['class'];
467 GeneralUtility::deprecationLog(
468 'Signal "TYPO3\\CMS\\Core\\Tree\\TableConfiguration\\TableConfiguration\\DatabaseTreeDataProvider" ' .
469 'is deprecated but used by "' . $slotClassNameOrObject . '". ' .
470 'Please update signal name to "' . __CLASS__ . '".'
471 );
472 }
473 $this->getSignalSlotDispatcher()->dispatch(
474 'TYPO3\\CMS\\Core\\Tree\\TableConfiguration\\TableConfiguration\\DatabaseTreeDataProvider',
475 self::SIGNAL_PostProcessTreeData,
476 array($this, $this->treeData)
477 );
478 }
479 }
480
481 /**
482 * Get the SignalSlot dispatcher
483 *
484 * @return \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
485 */
486 protected function getSignalSlotDispatcher() {
487 if (!isset($this->signalSlotDispatcher)) {
488 $this->signalSlotDispatcher = $this->getObjectManager()->get(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class);
489 }
490 return $this->signalSlotDispatcher;
491 }
492
493 /**
494 * Get the ObjectManager
495 *
496 * @return \TYPO3\CMS\Extbase\Object\ObjectManager
497 */
498 protected function getObjectManager() {
499 return GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
500 }
501
502 }