TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Tree\Event;
|
||||
|
||||
use TYPO3\CMS\Backend\Tree\TreeNode;
|
||||
use TYPO3\CMS\Core\Tree\TableConfiguration\AbstractTableConfigurationTreeDataProvider;
|
||||
|
||||
/**
|
||||
* Allows to modify tree data for any database tree
|
||||
*/
|
||||
final class ModifyTreeDataEvent
|
||||
{
|
||||
public function __construct(
|
||||
private TreeNode $treeData,
|
||||
private readonly AbstractTableConfigurationTreeDataProvider $provider
|
||||
) {}
|
||||
|
||||
public function getTreeData(): TreeNode
|
||||
{
|
||||
return $this->treeData;
|
||||
}
|
||||
|
||||
public function setTreeData(TreeNode $treeData): void
|
||||
{
|
||||
$this->treeData = $treeData;
|
||||
}
|
||||
|
||||
public function getProvider(): AbstractTableConfigurationTreeDataProvider
|
||||
{
|
||||
return $this->provider;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Tree\TableConfiguration;
|
||||
|
||||
use TYPO3\CMS\Backend\Tree\AbstractTreeDataProvider;
|
||||
use TYPO3\CMS\Backend\Tree\TreeNode;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* An abstract TCA tree data provider
|
||||
*/
|
||||
abstract class AbstractTableConfigurationTreeDataProvider extends AbstractTreeDataProvider
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $expandAll = false;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $levelMaximum = 4;
|
||||
|
||||
/**
|
||||
* @var TreeNode
|
||||
*/
|
||||
protected $treeData;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $treeId;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $nonSelectableLevelList = '0';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $expandedList = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $selectedList = '';
|
||||
|
||||
/**
|
||||
* Contains all ids which may be allowed to display according to
|
||||
* beUser Rights and foreign_table_where (if type db)
|
||||
*
|
||||
* @var array $itemWhiteList
|
||||
*/
|
||||
protected $itemWhiteList = [];
|
||||
|
||||
/**
|
||||
* Contains all ids which are not allowed to be selected
|
||||
* @var mixed[]
|
||||
*/
|
||||
protected $itemUnselectableList = [];
|
||||
|
||||
/**
|
||||
* @todo: This is a hack to speed up category tree calculation. See the comments
|
||||
* in TcaCategory and AbstractItemProvider FormEngine classes.
|
||||
* @internal
|
||||
*/
|
||||
protected array $availableItems = [];
|
||||
|
||||
/**
|
||||
* @var int[]
|
||||
*/
|
||||
protected array $startingPoints = [0];
|
||||
|
||||
/**
|
||||
* Sets the id of the tree
|
||||
*
|
||||
* @param string $treeId
|
||||
*/
|
||||
public function setTreeId($treeId)
|
||||
{
|
||||
$this->treeId = $treeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the id of the tree
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTreeId()
|
||||
{
|
||||
return $this->treeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the expandAll
|
||||
*
|
||||
* @param bool $expandAll
|
||||
*/
|
||||
public function setExpandAll($expandAll)
|
||||
{
|
||||
$this->expandAll = $expandAll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the expandAll
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getExpandAll()
|
||||
{
|
||||
return $this->expandAll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the levelMaximum
|
||||
*
|
||||
* @param int $levelMaximum
|
||||
*/
|
||||
public function setLevelMaximum($levelMaximum)
|
||||
{
|
||||
$this->levelMaximum = $levelMaximum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the levelMaximum
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLevelMaximum()
|
||||
{
|
||||
return $this->levelMaximum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the expanded state of a given node
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isExpanded(TreeNode $node)
|
||||
{
|
||||
return $this->getExpandAll() || GeneralUtility::inList($this->expandedList, $node->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the tree data
|
||||
*/
|
||||
public function initializeTreeData() {}
|
||||
|
||||
/**
|
||||
* Sets the list for selected nodes
|
||||
*
|
||||
* @param string $selectedList
|
||||
*/
|
||||
public function setSelectedList($selectedList)
|
||||
{
|
||||
$this->selectedList = $selectedList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list for selected nodes
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSelectedList()
|
||||
{
|
||||
return $this->selectedList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list for non selectable tree levels
|
||||
*
|
||||
* @param string $nonSelectableLevelList
|
||||
*/
|
||||
public function setNonSelectableLevelList($nonSelectableLevelList)
|
||||
{
|
||||
$this->nonSelectableLevelList = $nonSelectableLevelList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list for non selectable tree levels
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNonSelectableLevelList()
|
||||
{
|
||||
return $this->nonSelectableLevelList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the itemWhiteList
|
||||
*/
|
||||
public function setItemWhiteList(array $itemWhiteList)
|
||||
{
|
||||
$this->itemWhiteList = $itemWhiteList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the itemWhiteList
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getItemWhiteList()
|
||||
{
|
||||
return $this->itemWhiteList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for $itemUnselectableList
|
||||
*/
|
||||
public function setItemUnselectableList(array $itemUnselectableList)
|
||||
{
|
||||
$this->itemUnselectableList = $itemUnselectableList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for $itemUnselectableList
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getItemUnselectableList()
|
||||
{
|
||||
return $this->itemUnselectableList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal See property comment
|
||||
*/
|
||||
public function setAvailableItems(array $availableItems)
|
||||
{
|
||||
$this->availableItems = $availableItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $startingPoints
|
||||
*/
|
||||
public function setStartingPoints(array $startingPoints): void
|
||||
{
|
||||
$this->startingPoints = $startingPoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
public function getStartingPoints(): array
|
||||
{
|
||||
return $this->startingPoints;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Tree\TableConfiguration;
|
||||
|
||||
use TYPO3\CMS\Backend\Tree\AbstractTree;
|
||||
use TYPO3\CMS\Backend\Tree\Renderer\AbstractTreeRenderer;
|
||||
use TYPO3\CMS\Backend\Tree\TreeNodeCollection;
|
||||
use TYPO3\CMS\Backend\Tree\TreeRepresentationNode;
|
||||
|
||||
/**
|
||||
* Renders a tca tree array for the SelectElementTree
|
||||
*/
|
||||
class ArrayTreeRenderer extends AbstractTreeRenderer
|
||||
{
|
||||
/**
|
||||
* recursion level
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $recursionLevel = 0;
|
||||
|
||||
/**
|
||||
* Renders a node recursive or just a single instance
|
||||
*
|
||||
* @param bool $recursive
|
||||
* @return array
|
||||
*/
|
||||
public function renderNode(TreeRepresentationNode $node, $recursive = true)
|
||||
{
|
||||
$nodeArray = [];
|
||||
$nodeArray[] = $this->getNodeArray($node);
|
||||
if ($recursive && $node->hasChildNodes()) {
|
||||
$this->recursionLevel++;
|
||||
$children = $this->renderNodeCollection($node->getChildNodes());
|
||||
foreach ($children as $child) {
|
||||
$nodeArray[] = $child;
|
||||
}
|
||||
$this->recursionLevel--;
|
||||
}
|
||||
return $nodeArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get node array
|
||||
*
|
||||
* @param \TYPO3\CMS\Backend\Tree\TreeRepresentationNode|DatabaseTreeNode $node
|
||||
* @return array
|
||||
*/
|
||||
protected function getNodeArray(TreeRepresentationNode $node)
|
||||
{
|
||||
$overlayIconName = '';
|
||||
if (is_object($node->getIcon())) {
|
||||
$iconName = $node->getIcon()->getIdentifier();
|
||||
if (is_object($node->getIcon()->getOverlayIcon())) {
|
||||
$overlayIconName = $node->getIcon()->getOverlayIcon()->getIdentifier();
|
||||
}
|
||||
} else {
|
||||
$iconName = $node->getIcon();
|
||||
}
|
||||
$nodeArray = [
|
||||
'identifier' => htmlspecialchars($node->getId()),
|
||||
// No need for htmlspecialchars() here as d3 is using 'textContent' property of the HTML DOM node
|
||||
'name' => $node->getLabel(),
|
||||
'icon' => $iconName,
|
||||
'overlayIcon' => $overlayIconName,
|
||||
'depth' => $this->recursionLevel,
|
||||
'hasChildren' => (bool)$node->hasChildNodes(),
|
||||
'selectable' => true,
|
||||
];
|
||||
if ($node instanceof DatabaseTreeNode) {
|
||||
$nodeArray['checked'] = (bool)$node->getSelected();
|
||||
if (!$node->getSelectable()) {
|
||||
$nodeArray['checked'] = false;
|
||||
$nodeArray['selectable'] = false;
|
||||
}
|
||||
}
|
||||
return $nodeArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a node collection recursive or just a single instance
|
||||
*
|
||||
* @param bool $recursive
|
||||
* @return array
|
||||
*/
|
||||
public function renderTree(AbstractTree $tree, $recursive = true)
|
||||
{
|
||||
$this->recursionLevel = 0;
|
||||
return $this->renderNode($tree->getRoot(), $recursive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a tree recursively or just a single instance
|
||||
*
|
||||
* @param bool $recursive
|
||||
* @return array
|
||||
*/
|
||||
public function renderNodeCollection(TreeNodeCollection $collection, $recursive = true)
|
||||
{
|
||||
$treeItems = [];
|
||||
foreach ($collection as $node) {
|
||||
$allNodes = $this->renderNode($node, $recursive);
|
||||
if ($allNodes[0]) {
|
||||
$treeItems[] = $allNodes[0];
|
||||
}
|
||||
$nodeCount = count($allNodes);
|
||||
if ($nodeCount > 1) {
|
||||
for ($i = 1; $i < $nodeCount; $i++) {
|
||||
$treeItems[] = $allNodes[$i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $treeItems;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,477 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Tree\TableConfiguration;
|
||||
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||
use TYPO3\CMS\Backend\Tree\SortedTreeNodeCollection;
|
||||
use TYPO3\CMS\Backend\Tree\TreeNode;
|
||||
use TYPO3\CMS\Backend\Tree\TreeNodeCollection;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
|
||||
use TYPO3\CMS\Core\Database\RelationHandler;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Imaging\IconSize;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchema;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
||||
use TYPO3\CMS\Core\Tree\Event\ModifyTreeDataEvent;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* TCA tree data provider
|
||||
*/
|
||||
#[Autoconfigure(public: true, shared: false)]
|
||||
class DatabaseTreeDataProvider extends AbstractTableConfigurationTreeDataProvider
|
||||
{
|
||||
public const MODE_CHILDREN = 1;
|
||||
public const MODE_PARENT = 2;
|
||||
|
||||
protected string $tableName = '';
|
||||
protected ?TcaSchema $schema = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $treeId = '';
|
||||
|
||||
protected string $labelField = '';
|
||||
|
||||
protected string $tableWhere = '';
|
||||
|
||||
/**
|
||||
* @var self::MODE_*
|
||||
*/
|
||||
protected int $lookupMode = self::MODE_CHILDREN;
|
||||
|
||||
protected string $lookupField = '';
|
||||
|
||||
protected array $idCache = [];
|
||||
|
||||
/**
|
||||
* Stores TCA-Configuration of the LookUpField in tableName
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected array $columnConfiguration;
|
||||
|
||||
/**
|
||||
* node sort values (the orderings from foreign_Table_where evaluation)
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected array $nodeSortValues = [];
|
||||
|
||||
public function __construct(protected EventDispatcherInterface $eventDispatcher) {}
|
||||
|
||||
/**
|
||||
* Sets the label field
|
||||
*/
|
||||
public function setLabelField(string $labelField): void
|
||||
{
|
||||
$this->labelField = $labelField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the label field
|
||||
*/
|
||||
public function getLabelField(): string
|
||||
{
|
||||
return $this->labelField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the table name
|
||||
*/
|
||||
public function setTableName(string $tableName): void
|
||||
{
|
||||
$this->tableName = $tableName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the table name
|
||||
*/
|
||||
public function getTableName(): string
|
||||
{
|
||||
return $this->tableName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the lookup field
|
||||
*/
|
||||
public function setLookupField(string $lookupField): void
|
||||
{
|
||||
$this->lookupField = $lookupField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the lookup field
|
||||
*/
|
||||
public function getLookupField(): string
|
||||
{
|
||||
return $this->lookupField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the lookup mode
|
||||
*
|
||||
* @param self::MODE_* $lookupMode
|
||||
*/
|
||||
public function setLookupMode(int $lookupMode): void
|
||||
{
|
||||
$this->lookupMode = $lookupMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the lookup mode
|
||||
*
|
||||
* @return self::MODE_*
|
||||
*/
|
||||
public function getLookupMode(): int
|
||||
{
|
||||
return $this->lookupMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the nodes
|
||||
*/
|
||||
public function getNodes(TreeNode $node): void {}
|
||||
|
||||
/**
|
||||
* Gets the root node
|
||||
*/
|
||||
public function getRoot(): DatabaseTreeNode
|
||||
{
|
||||
return $this->buildRepresentationForNode($this->treeData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tableWhere clause
|
||||
*/
|
||||
public function setTableWhere(string $tableWhere): void
|
||||
{
|
||||
$this->tableWhere = $tableWhere;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tableWhere clause
|
||||
*/
|
||||
public function getTableWhere(): string
|
||||
{
|
||||
return $this->tableWhere;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a complete node including children
|
||||
*/
|
||||
protected function buildRepresentationForNode(TreeNode $basicNode, ?DatabaseTreeNode $parent = null, $level = 0): DatabaseTreeNode
|
||||
{
|
||||
$node = GeneralUtility::makeInstance(DatabaseTreeNode::class);
|
||||
$row = [];
|
||||
if ($basicNode->getId() == 0) {
|
||||
$node->setSelected(false);
|
||||
$node->setLabel($this->schema?->getTitle($this->getLanguageService()->sL(...)));
|
||||
} else {
|
||||
if ($basicNode->getAdditionalData() === []) {
|
||||
$row = BackendUtility::getRecordWSOL($this->tableName, (int)$basicNode->getId(), '*', '', false) ?? [];
|
||||
} else {
|
||||
// @todo: This is part of the category tree performance hack
|
||||
$row = $basicNode->getAdditionalData();
|
||||
}
|
||||
$node->setLabel(BackendUtility::getRecordTitle($this->tableName, $row) ?: $basicNode->getId());
|
||||
$node->setSelected(GeneralUtility::inList($this->getSelectedList(), $basicNode->getId()));
|
||||
}
|
||||
$node->setId($basicNode->getId());
|
||||
$node->setSelectable(!GeneralUtility::inList($this->getNonSelectableLevelList(), (string)$level) && !in_array($basicNode->getId(), $this->getItemUnselectableList()));
|
||||
$node->setSortValue($this->nodeSortValues[$basicNode->getId()] ?? '');
|
||||
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
$node->setIcon($iconFactory->getIconForRecord($this->tableName, $row, IconSize::SMALL));
|
||||
$node->setParentNode($parent);
|
||||
if ($basicNode->hasChildNodes()) {
|
||||
$node->setHasChildren(true);
|
||||
$childNodes = GeneralUtility::makeInstance(SortedTreeNodeCollection::class);
|
||||
$tempNodes = [];
|
||||
foreach ($basicNode->getChildNodes() as $child) {
|
||||
$tempNodes[] = $this->buildRepresentationForNode($child, $node, $level + 1);
|
||||
}
|
||||
$childNodes->exchangeArray($tempNodes);
|
||||
$childNodes->asort();
|
||||
$node->setChildNodes($childNodes);
|
||||
}
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the tree data
|
||||
*/
|
||||
public function initializeTreeData(): void
|
||||
{
|
||||
$this->schema = GeneralUtility::makeInstance(TcaSchemaFactory::class)->get($this->getTableName());
|
||||
$this->nodeSortValues = array_flip($this->itemWhiteList);
|
||||
if ($this->schema->hasField($this->lookupField)) {
|
||||
$this->columnConfiguration = $this->schema->getField($this->lookupField)->getConfiguration();
|
||||
} else {
|
||||
// Use-case here is lookupField = "pid"
|
||||
$this->columnConfiguration = [];
|
||||
}
|
||||
if (isset($this->columnConfiguration['foreign_table']) && $this->columnConfiguration['foreign_table'] !== $this->getTableName()) {
|
||||
throw new \InvalidArgumentException('TCA Tree configuration is invalid: tree for different node-Tables is not implemented yet', 1290944650);
|
||||
}
|
||||
$this->treeData = GeneralUtility::makeInstance(TreeNode::class);
|
||||
$this->loadTreeData();
|
||||
$event = $this->eventDispatcher->dispatch(new ModifyTreeDataEvent($this->treeData, $this));
|
||||
$this->treeData = $event->getTreeData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the tree data (all possible children)
|
||||
*/
|
||||
protected function loadTreeData(): void
|
||||
{
|
||||
if ($this->getStartingPoints()) {
|
||||
$startingPoints = $this->getStartingPoints();
|
||||
} else {
|
||||
$startingPoints = [0];
|
||||
}
|
||||
|
||||
if (count($startingPoints) === 1) {
|
||||
// Only one starting point is available, grab it and set it as root node
|
||||
$startingPoint = current($startingPoints);
|
||||
$this->treeData->setId((string)$startingPoint);
|
||||
$this->treeData->setParentNode(null);
|
||||
|
||||
if ($this->levelMaximum >= 1) {
|
||||
$childNodes = $this->getChildrenOf($this->treeData, 1);
|
||||
if ($childNodes !== null) {
|
||||
$this->treeData->setChildNodes($childNodes);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The current tree implementation disallows multiple elements on root level, thus we have to work around
|
||||
// this with a separate TreeNodeCollection that gets attached to the root node with uid 0. This has the
|
||||
// nasty side effect we cannot avoid the root node being rendered.
|
||||
|
||||
$treeNodeCollection = GeneralUtility::makeInstance(TreeNodeCollection::class);
|
||||
foreach ($startingPoints as $startingPoint) {
|
||||
$treeData = GeneralUtility::makeInstance(TreeNode::class);
|
||||
$treeData->setId((string)$startingPoint);
|
||||
|
||||
if ($this->levelMaximum >= 1) {
|
||||
$childNodes = $this->getChildrenOf($treeData, 1);
|
||||
if ($childNodes !== null) {
|
||||
$treeData->setChildNodes($childNodes);
|
||||
}
|
||||
}
|
||||
$treeNodeCollection->append($treeData);
|
||||
}
|
||||
$this->treeData->setId('0');
|
||||
$this->treeData->setChildNodes($treeNodeCollection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets node children
|
||||
*/
|
||||
protected function getChildrenOf(TreeNode $node, int $level): ?TreeNodeCollection
|
||||
{
|
||||
$nodeData = null;
|
||||
if ($node->getId() !== 0 && $node->getId() !== '0') {
|
||||
if (is_array($this->availableItems[(int)$node->getId()] ?? false)) {
|
||||
// @todo: This is part of the category tree performance hack
|
||||
$nodeData = $this->availableItems[(int)$node->getId()];
|
||||
} else {
|
||||
$nodeData = BackendUtility::getRecord($this->tableName, $node->getId(), '*', '', false);
|
||||
}
|
||||
}
|
||||
if (empty($nodeData)) {
|
||||
$nodeData = [
|
||||
'uid' => 0,
|
||||
$this->lookupField => '',
|
||||
];
|
||||
}
|
||||
$storage = null;
|
||||
$children = $this->getRelatedRecords($nodeData);
|
||||
if (!empty($children)) {
|
||||
$storage = GeneralUtility::makeInstance(TreeNodeCollection::class);
|
||||
foreach ($children as $child) {
|
||||
$node = GeneralUtility::makeInstance(TreeNode::class, $this->availableItems[(int)$child] ?? []);
|
||||
$node->setId($child);
|
||||
if ($level < $this->levelMaximum) {
|
||||
$children = $this->getChildrenOf($node, $level + 1);
|
||||
if ($children !== null) {
|
||||
$node->setChildNodes($children);
|
||||
}
|
||||
}
|
||||
$storage->append($node);
|
||||
}
|
||||
}
|
||||
return $storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets related records depending on TCA configuration
|
||||
*/
|
||||
protected function getRelatedRecords(array $row): array
|
||||
{
|
||||
if ($this->getLookupMode() === self::MODE_PARENT) {
|
||||
$children = $this->getChildrenUidsFromParentRelation($row);
|
||||
} else {
|
||||
$children = $this->getChildrenUidsFromChildrenRelation($row);
|
||||
}
|
||||
$allowedArray = [];
|
||||
foreach ($children as $child) {
|
||||
if (!in_array($child, $this->idCache, true) && in_array($child, $this->itemWhiteList, true)) {
|
||||
$allowedArray[] = $child;
|
||||
}
|
||||
}
|
||||
$this->idCache = array_merge($this->idCache, $allowedArray);
|
||||
return $allowedArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets related records depending on TCA configuration
|
||||
*/
|
||||
protected function getChildrenUidsFromParentRelation(array $row): array
|
||||
{
|
||||
$uid = (int)$row['uid'];
|
||||
if (in_array($this->columnConfiguration['type'] ?? '', ['select', 'category', 'inline', 'file'], true)) {
|
||||
if ($this->columnConfiguration['MM'] ?? null) {
|
||||
$dbGroup = GeneralUtility::makeInstance(RelationHandler::class);
|
||||
// Dummy field for setting "look from other site"
|
||||
$this->columnConfiguration['MM_opposite_field'] = 'children';
|
||||
$dbGroup->start($row[$this->lookupField], $this->getTableName(), $this->columnConfiguration['MM'], $uid, $this->getTableName(), $this->columnConfiguration);
|
||||
$relatedUids = $dbGroup->tableArray[$this->getTableName()];
|
||||
} elseif ($this->columnConfiguration['foreign_field'] ?? null) {
|
||||
$relatedUids = $this->listFieldQuery($this->columnConfiguration['foreign_field'], $uid);
|
||||
} else {
|
||||
// Check available items
|
||||
if ($this->availableItems !== [] && $this->columnConfiguration['type'] === 'category') {
|
||||
// @todo: This is part of the category tree performance hack
|
||||
$relatedUids = [];
|
||||
foreach ($this->availableItems as $item) {
|
||||
if ($item[$this->lookupField] === $uid) {
|
||||
$relatedUids[$item['uid']] = $item['sorting'];
|
||||
}
|
||||
}
|
||||
if ($relatedUids !== []) {
|
||||
// Ensure sorting is kept
|
||||
asort($relatedUids);
|
||||
$relatedUids = array_keys($relatedUids);
|
||||
}
|
||||
} else {
|
||||
$relatedUids = $this->listFieldQuery($this->lookupField, $uid);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$relatedUids = $this->listFieldQuery($this->lookupField, $uid);
|
||||
}
|
||||
|
||||
return $relatedUids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets related children records depending on TCA configuration
|
||||
*/
|
||||
protected function getChildrenUidsFromChildrenRelation(array $row): array
|
||||
{
|
||||
$relatedUids = [];
|
||||
$uid = (int)$row['uid'];
|
||||
$value = (string)$row[$this->lookupField];
|
||||
switch ((string)$this->columnConfiguration['type']) {
|
||||
case 'inline':
|
||||
case 'file':
|
||||
// Intentional fall-through
|
||||
case 'select':
|
||||
case 'category':
|
||||
if ($this->columnConfiguration['MM'] ?? false) {
|
||||
$dbGroup = GeneralUtility::makeInstance(RelationHandler::class);
|
||||
$dbGroup->start(
|
||||
$value,
|
||||
$this->getTableName(),
|
||||
$this->columnConfiguration['MM'],
|
||||
$uid,
|
||||
$this->getTableName(),
|
||||
$this->columnConfiguration
|
||||
);
|
||||
$relatedUids = $dbGroup->tableArray[$this->getTableName()];
|
||||
} elseif ($this->columnConfiguration['foreign_field'] ?? false) {
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable($this->getTableName());
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$records = $queryBuilder->select('uid')
|
||||
->from($this->getTableName())
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
$this->columnConfiguration['foreign_field'],
|
||||
$queryBuilder->createNamedParameter($uid, Connection::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->executeQuery()
|
||||
->fetchAllAssociative();
|
||||
|
||||
if (!empty($records)) {
|
||||
$relatedUids = array_column($records, 'uid');
|
||||
}
|
||||
} else {
|
||||
$relatedUids = GeneralUtility::intExplode(',', $value, true);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$relatedUids = GeneralUtility::intExplode(',', $value, true);
|
||||
}
|
||||
return $relatedUids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the table for a field which might contain a list.
|
||||
*
|
||||
* @param string $fieldName the name of the field to be queried
|
||||
* @param int $queryId the uid to search for
|
||||
* @return int[] all uids found
|
||||
*/
|
||||
protected function listFieldQuery(string $fieldName, int $queryId): array
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable($this->getTableName());
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
|
||||
$queryBuilder->select('uid')
|
||||
->from($this->getTableName())
|
||||
->where($queryBuilder->expr()->inSet($fieldName, $queryBuilder->quote((string)$queryId)));
|
||||
|
||||
if ($queryId === 0) {
|
||||
$queryBuilder->orWhere(
|
||||
$queryBuilder->expr()->comparison(
|
||||
'CAST(' . $queryBuilder->quoteIdentifier($fieldName) . ' AS CHAR)',
|
||||
ExpressionBuilder::EQ,
|
||||
$queryBuilder->quote('')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$records = $queryBuilder->executeQuery()->fetchAllAssociative();
|
||||
return array_column($records, 'uid');
|
||||
}
|
||||
|
||||
protected function getLanguageService(): ?LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'] ?? null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Tree\TableConfiguration;
|
||||
|
||||
use TYPO3\CMS\Backend\Tree\TreeRepresentationNode;
|
||||
|
||||
/**
|
||||
* Represents a node in a TCA database setup
|
||||
*/
|
||||
class DatabaseTreeNode extends TreeRepresentationNode
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $selectable;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $selected = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasChildren = false;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $sortValue;
|
||||
|
||||
/**
|
||||
* Sets the selectable property
|
||||
*
|
||||
* @param bool $selectable
|
||||
*/
|
||||
public function setSelectable($selectable)
|
||||
{
|
||||
$this->selectable = $selectable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the selectable property
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getSelectable()
|
||||
{
|
||||
return $this->selectable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the select state
|
||||
*
|
||||
* @param bool $selected
|
||||
*/
|
||||
public function setSelected($selected)
|
||||
{
|
||||
$this->selected = $selected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the select state
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getSelected()
|
||||
{
|
||||
return $this->selected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the hasChildren property
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasChildren()
|
||||
{
|
||||
return $this->hasChildren;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the hasChildren property
|
||||
*
|
||||
* @param bool $value
|
||||
*/
|
||||
public function setHasChildren($value)
|
||||
{
|
||||
$this->hasChildren = (bool)$value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares a node to another one.
|
||||
*
|
||||
* Returns:
|
||||
* 1 if its greater than the other one
|
||||
* -1 if its smaller than the other one
|
||||
* 0 if its equal
|
||||
*
|
||||
* @param \TYPO3\CMS\Backend\Tree\TreeNode $other
|
||||
* @return int see description above
|
||||
*/
|
||||
public function compareTo($other)
|
||||
{
|
||||
if ($this->equals($other)) {
|
||||
return 0;
|
||||
}
|
||||
if ($other instanceof self) {
|
||||
return $this->sortValue > $other->getSortValue() ? 1 : -1;
|
||||
}
|
||||
return parent::compareTo($other);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the sort value
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getSortValue()
|
||||
{
|
||||
return $this->sortValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sort value
|
||||
*
|
||||
* @param mixed $sortValue
|
||||
*/
|
||||
public function setSortValue($sortValue)
|
||||
{
|
||||
$this->sortValue = $sortValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Tree\TableConfiguration;
|
||||
|
||||
use TYPO3\CMS\Backend\Tree\AbstractTree;
|
||||
|
||||
/**
|
||||
* Class for tca tree
|
||||
*/
|
||||
class TableConfigurationTree extends AbstractTree
|
||||
{
|
||||
/**
|
||||
* Returns the root node
|
||||
*
|
||||
* @return \TYPO3\CMS\Backend\Tree\TreeNode
|
||||
*/
|
||||
public function getRoot()
|
||||
{
|
||||
return $this->dataProvider->getRoot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a tree
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return $this->nodeRenderer->renderTree($this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Tree\TableConfiguration;
|
||||
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use TYPO3\CMS\Core\Schema\Capability\TcaSchemaCapability;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Builds a \TYPO3\CMS\Core\Tree\TableConfiguration\DatabaseTreeDataProvider
|
||||
* object based on some TCA configuration
|
||||
*/
|
||||
class TreeDataProviderFactory
|
||||
{
|
||||
/**
|
||||
* Gets the data provider, depending on TCA configuration
|
||||
*
|
||||
* @param array $currentValue The current database row, handing over 'uid' is enough
|
||||
* @return DatabaseTreeDataProvider
|
||||
*/
|
||||
public static function getDataProvider(array $tcaConfiguration, string $table, string $field, array $currentValue)
|
||||
{
|
||||
$dataProvider = null;
|
||||
if (!isset($tcaConfiguration['treeConfig']) || !is_array($tcaConfiguration['treeConfig'])) {
|
||||
throw new \InvalidArgumentException('TCA Tree configuration is invalid: "treeConfig" array is missing', 1288215890);
|
||||
}
|
||||
|
||||
if (!empty($tcaConfiguration['treeConfig']['dataProvider'])) {
|
||||
// This is a hack since TYPO3 v10 we use this to inject the EventDispatcher in the first argument
|
||||
// For TYPO3 Core, but this is only possible if the dataProvider is extending from the DatabaseTreeDataProvider
|
||||
// but did NOT use a custom constructor. This way, the original constructor receives the EventDispatcher properly
|
||||
// as first argument. It is encouraged to use a custom constructor that also receives the EventDispatcher
|
||||
// separately.
|
||||
$reflectionClass = new \ReflectionClass($tcaConfiguration['treeConfig']['dataProvider']);
|
||||
if ($reflectionClass->getConstructor()->getDeclaringClass()->getName() === DatabaseTreeDataProvider::class) {
|
||||
$dataProvider = GeneralUtility::makeInstance(
|
||||
$tcaConfiguration['treeConfig']['dataProvider'],
|
||||
GeneralUtility::makeInstance(EventDispatcherInterface::class)
|
||||
);
|
||||
} else {
|
||||
$dataProvider = GeneralUtility::makeInstance(
|
||||
$tcaConfiguration['treeConfig']['dataProvider'],
|
||||
$tcaConfiguration,
|
||||
$table,
|
||||
$field,
|
||||
$currentValue,
|
||||
GeneralUtility::makeInstance(EventDispatcherInterface::class)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (($tcaConfiguration['type'] ?? '') !== 'folder') {
|
||||
if ($dataProvider === null) {
|
||||
$dataProvider = GeneralUtility::makeInstance(DatabaseTreeDataProvider::class);
|
||||
}
|
||||
if (isset($tcaConfiguration['foreign_table'])) {
|
||||
$tableName = $tcaConfiguration['foreign_table'];
|
||||
$dataProvider->setTableName($tableName);
|
||||
if ($tableName == $table) {
|
||||
// The uid of the currently opened row cannot be selected in a table relation to "self"
|
||||
$unselectableUids = [$currentValue['uid']];
|
||||
$dataProvider->setItemUnselectableList($unselectableUids);
|
||||
}
|
||||
} else {
|
||||
throw new \InvalidArgumentException('TCA Tree configuration is invalid: "foreign_table" not set', 1288215888);
|
||||
}
|
||||
if (isset($tcaConfiguration['foreign_label'])) {
|
||||
$dataProvider->setLabelField($tcaConfiguration['foreign_label']);
|
||||
} else {
|
||||
$schemaFactory = GeneralUtility::makeInstance(TcaSchemaFactory::class);
|
||||
if ($schemaFactory->has($tableName)) {
|
||||
$labelField = $schemaFactory->get($tableName)->getCapability(TcaSchemaCapability::Label);
|
||||
$dataProvider->setLabelField($labelField->getPrimaryFieldName() ?? '');
|
||||
}
|
||||
}
|
||||
$dataProvider->setTreeId(md5($table . '|' . $field));
|
||||
|
||||
$treeConfiguration = $tcaConfiguration['treeConfig'];
|
||||
if (isset($treeConfiguration['startingPoints'])) {
|
||||
$dataProvider->setStartingPoints(array_unique(GeneralUtility::intExplode(',', (string)$treeConfiguration['startingPoints'])));
|
||||
}
|
||||
if (isset($treeConfiguration['appearance']['expandAll'])) {
|
||||
$dataProvider->setExpandAll((bool)$treeConfiguration['appearance']['expandAll']);
|
||||
}
|
||||
if (isset($treeConfiguration['appearance']['maxLevels'])) {
|
||||
$dataProvider->setLevelMaximum((int)$treeConfiguration['appearance']['maxLevels']);
|
||||
}
|
||||
if (isset($treeConfiguration['appearance']['nonSelectableLevels'])) {
|
||||
$dataProvider->setNonSelectableLevelList($treeConfiguration['appearance']['nonSelectableLevels']);
|
||||
} elseif (isset($treeConfiguration['startingPoints'])) {
|
||||
// If there are more than 1 starting points, disable the first level. See description in DatabaseTreeProvider::loadTreeData()
|
||||
$dataProvider->setNonSelectableLevelList(substr_count($treeConfiguration['startingPoints'], ',') > 0 ? '0' : '');
|
||||
}
|
||||
if (isset($treeConfiguration['childrenField'])) {
|
||||
$dataProvider->setLookupMode(DatabaseTreeDataProvider::MODE_CHILDREN);
|
||||
$dataProvider->setLookupField($treeConfiguration['childrenField']);
|
||||
} elseif (isset($treeConfiguration['parentField'])) {
|
||||
$dataProvider->setLookupMode(DatabaseTreeDataProvider::MODE_PARENT);
|
||||
$dataProvider->setLookupField($treeConfiguration['parentField']);
|
||||
} else {
|
||||
throw new \InvalidArgumentException('TCA Tree configuration is invalid: neither "childrenField" nor "parentField" is set', 1288215889);
|
||||
}
|
||||
} elseif ($dataProvider === null) {
|
||||
throw new \InvalidArgumentException('TCA Tree configuration is invalid: tree for "type=folder" not implemented yet', 1288215892);
|
||||
}
|
||||
return $dataProvider;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user