first commit
This commit is contained in:
commit
1218dd1517
12716 files changed
+989233
No files matched your search
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Tree;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Abstract Tree
|
||||
*/
|
||||
abstract class AbstractTree
|
||||
{
|
||||
/**
|
||||
* Data Provider
|
||||
*
|
||||
* @var \TYPO3\CMS\Backend\Tree\AbstractTreeDataProvider
|
||||
*/
|
||||
protected $dataProvider;
|
||||
|
||||
/**
|
||||
* Tree Node Decorator
|
||||
*
|
||||
* @var \TYPO3\CMS\Backend\Tree\Renderer\AbstractTreeRenderer
|
||||
*/
|
||||
protected $nodeRenderer;
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Backend\Tree\AbstractTreeDataProvider $dataProvider
|
||||
*/
|
||||
public function setDataProvider(\TYPO3\CMS\Backend\Tree\AbstractTreeDataProvider $dataProvider)
|
||||
{
|
||||
$this->dataProvider = $dataProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \TYPO3\CMS\Backend\Tree\AbstractTreeDataProvider
|
||||
*/
|
||||
public function getDataProvider()
|
||||
{
|
||||
return $this->dataProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Backend\Tree\Renderer\AbstractTreeRenderer $nodeRenderer
|
||||
*/
|
||||
public function setNodeRenderer(\TYPO3\CMS\Backend\Tree\Renderer\AbstractTreeRenderer $nodeRenderer)
|
||||
{
|
||||
$this->nodeRenderer = $nodeRenderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \TYPO3\CMS\Backend\Tree\Renderer\AbstractTreeRenderer
|
||||
*/
|
||||
public function getNodeRenderer()
|
||||
{
|
||||
return $this->nodeRenderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root node
|
||||
*
|
||||
* @return \TYPO3\CMS\Backend\Tree\TreeNode
|
||||
*/
|
||||
abstract public function getRoot();
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Tree;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Abstract Tree Data Provider
|
||||
*/
|
||||
abstract class AbstractTreeDataProvider
|
||||
{
|
||||
/**
|
||||
* Root Node
|
||||
*
|
||||
* @var \TYPO3\CMS\Backend\Tree\TreeNode
|
||||
*/
|
||||
protected $rootNode;
|
||||
|
||||
/**
|
||||
* Returns the root node
|
||||
*
|
||||
* @return \TYPO3\CMS\Backend\Tree\TreeNode
|
||||
*/
|
||||
abstract public function getRoot();
|
||||
|
||||
/**
|
||||
* Fetches the subnodes of the given node
|
||||
*
|
||||
* @param \TYPO3\CMS\Backend\Tree\TreeNode $node
|
||||
* @return \TYPO3\CMS\Backend\Tree\TreeNodeCollection
|
||||
*/
|
||||
abstract public function getNodes(\TYPO3\CMS\Backend\Tree\TreeNode $node);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Tree;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface that defines the comparison of nodes
|
||||
*/
|
||||
interface ComparableNodeInterface
|
||||
{
|
||||
/**
|
||||
* Compare Node against another one
|
||||
*
|
||||
* Returns:
|
||||
* 1 if the current node is greater than the $other,
|
||||
* -1 if $other is greater than the current node and
|
||||
* 0 if the nodes are equal
|
||||
*
|
||||
* <strong>Example</strong>
|
||||
* <pre>
|
||||
* if ($this->sortValue > $other->sortValue) {
|
||||
* return 1;
|
||||
* } elseif ($this->sortValue < $other->sortValue) {
|
||||
* return -1;
|
||||
* } else {
|
||||
* return 0;
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param \TYPO3\CMS\Backend\Tree\TreeNode $other
|
||||
* @return int see description
|
||||
*/
|
||||
public function compareTo($other);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Tree\Renderer;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Abstract Renderer
|
||||
*/
|
||||
abstract class AbstractTreeRenderer
|
||||
{
|
||||
/**
|
||||
* Renders a node recursive or just a single instance
|
||||
*
|
||||
* @param \TYPO3\CMS\Backend\Tree\TreeRepresentationNode $node
|
||||
* @param bool $recursive
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function renderNode(\TYPO3\CMS\Backend\Tree\TreeRepresentationNode $node, $recursive = true);
|
||||
|
||||
/**
|
||||
* Renders a node collection recursive or just a single instance
|
||||
*
|
||||
* @param \TYPO3\CMS\Backend\Tree\TreeNodeCollection $collection
|
||||
* @param bool $recursive
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function renderNodeCollection(\TYPO3\CMS\Backend\Tree\TreeNodeCollection $collection, $recursive = true);
|
||||
|
||||
/**
|
||||
* Renders an tree recursive or just a single instance
|
||||
*
|
||||
* @param \TYPO3\CMS\Backend\Tree\AbstractTree $tree
|
||||
* @param bool $recursive
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function renderTree(\TYPO3\CMS\Backend\Tree\AbstractTree $tree, $recursive = true);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Tree\Renderer;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Renderer for unordered lists
|
||||
*/
|
||||
class UnorderedListTreeRenderer extends \TYPO3\CMS\Backend\Tree\Renderer\AbstractTreeRenderer
|
||||
{
|
||||
/**
|
||||
* recursion level
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $recursionLevel = 0;
|
||||
|
||||
/**
|
||||
* Renders a node recursive or just a single instance
|
||||
*
|
||||
* @param \TYPO3\CMS\Backend\Tree\TreeRepresentationNode $node
|
||||
* @param bool $recursive
|
||||
* @return string
|
||||
*/
|
||||
public function renderNode(\TYPO3\CMS\Backend\Tree\TreeRepresentationNode $node, $recursive = true)
|
||||
{
|
||||
$code = '<li><span class="' . htmlspecialchars($node->getIcon()) . '"> </span>' . htmlspecialchars($node->getLabel());
|
||||
if ($recursive && $node->getChildNodes() !== null) {
|
||||
$this->recursionLevel++;
|
||||
$code .= $this->renderNodeCollection($node->getChildNodes());
|
||||
$this->recursionLevel--;
|
||||
}
|
||||
$code .= '</li>';
|
||||
return $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a node collection recursive or just a single instance
|
||||
*
|
||||
* @param \TYPO3\CMS\Backend\Tree\AbstractTree $tree
|
||||
* @param bool $recursive
|
||||
* @return string
|
||||
*/
|
||||
public function renderTree(\TYPO3\CMS\Backend\Tree\AbstractTree $tree, $recursive = true)
|
||||
{
|
||||
$this->recursionLevel = 0;
|
||||
$code = '<ul class="level' . $this->recursionLevel . '" style="margin-left:10px">';
|
||||
$code .= $this->renderNode($tree->getRoot(), $recursive);
|
||||
$code .= '</ul>';
|
||||
return $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders an tree recursive or just a single instance
|
||||
*
|
||||
* @param \TYPO3\CMS\Backend\Tree\TreeNodeCollection $collection
|
||||
* @param bool $recursive
|
||||
* @return string
|
||||
*/
|
||||
public function renderNodeCollection(\TYPO3\CMS\Backend\Tree\TreeNodeCollection $collection, $recursive = true)
|
||||
{
|
||||
$code = '<ul class="level' . $this->recursionLevel . '" style="margin-left:10px">';
|
||||
foreach ($collection as $node) {
|
||||
$code .= $this->renderNode($node, $recursive);
|
||||
}
|
||||
$code .= '</ul>';
|
||||
return $code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,737 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Tree\Repository;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\QueryHelper;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction;
|
||||
use TYPO3\CMS\Core\DataHandling\PlainDataResolver;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Versioning\VersionState;
|
||||
|
||||
/**
|
||||
* Fetches ALL pages in the page tree, possibly overlaid with the workspace
|
||||
* in a sorted way.
|
||||
*
|
||||
* This works agnostic of the Backend User, allows to be used in FE as well in the future.
|
||||
*
|
||||
* @internal this class is not public API yet, as it needs to be proven stable enough first.
|
||||
*/
|
||||
class PageTreeRepository
|
||||
{
|
||||
/**
|
||||
* Fields to be queried from the database
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $fields = [
|
||||
'uid',
|
||||
'pid',
|
||||
'sorting',
|
||||
'starttime',
|
||||
'endtime',
|
||||
'hidden',
|
||||
'fe_group',
|
||||
'title',
|
||||
'nav_title',
|
||||
'nav_hide',
|
||||
'alias',
|
||||
'php_tree_stop',
|
||||
'doktype',
|
||||
'is_siteroot',
|
||||
'module',
|
||||
'extendToSubpages',
|
||||
'content_from_pid',
|
||||
't3ver_oid',
|
||||
't3ver_id',
|
||||
't3ver_wsid',
|
||||
't3ver_label',
|
||||
't3ver_state',
|
||||
't3ver_stage',
|
||||
't3ver_tstamp',
|
||||
't3ver_move_id',
|
||||
'perms_userid',
|
||||
'perms_user',
|
||||
'perms_groupid',
|
||||
'perms_group',
|
||||
'perms_everybody',
|
||||
'mount_pid',
|
||||
'shortcut',
|
||||
'shortcut_mode',
|
||||
'mount_pid_ol',
|
||||
'url',
|
||||
'sys_language_uid',
|
||||
'l10n_parent',
|
||||
];
|
||||
|
||||
/**
|
||||
* The workspace ID to operate on
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $currentWorkspace = 0;
|
||||
|
||||
/**
|
||||
* Full page tree when selected without permissions applied.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fullPageTree = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $additionalQueryRestrictions = [];
|
||||
|
||||
/**
|
||||
* @param int $workspaceId the workspace ID to be checked for.
|
||||
* @param array $additionalFieldsToQuery an array with more fields that should be accessed.
|
||||
* @param array $additionalQueryRestrictions an array with more restrictions to add
|
||||
*/
|
||||
public function __construct(int $workspaceId = 0, array $additionalFieldsToQuery = [], array $additionalQueryRestrictions = [])
|
||||
{
|
||||
$this->currentWorkspace = $workspaceId;
|
||||
if (!empty($additionalFieldsToQuery)) {
|
||||
$this->fields = array_merge($this->fields, $additionalFieldsToQuery);
|
||||
}
|
||||
|
||||
if (!empty($additionalQueryRestrictions)) {
|
||||
$this->additionalQueryRestrictions = $additionalQueryRestrictions;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main entry point for this repository, to fetch the tree data for a page.
|
||||
* Basically the page record, plus all child pages and their child pages recursively, stored within "_children" item.
|
||||
*
|
||||
* @param int $entryPoint the page ID to fetch the tree for
|
||||
* @param callable $callback a callback to be used to check for permissions and filter out pages not to be included.
|
||||
* @param array $dbMounts
|
||||
* @param bool $resolveUserPermissions
|
||||
* @return array
|
||||
*/
|
||||
public function getTree(
|
||||
int $entryPoint,
|
||||
callable $callback = null,
|
||||
array $dbMounts = [],
|
||||
$resolveUserPermissions = false
|
||||
): array {
|
||||
$this->fetchAllPages($dbMounts, $resolveUserPermissions);
|
||||
if ($entryPoint === 0) {
|
||||
$tree = $this->fullPageTree;
|
||||
} else {
|
||||
$tree = $this->findInPageTree($entryPoint, $this->fullPageTree);
|
||||
}
|
||||
if (!empty($tree) && $callback !== null) {
|
||||
$this->applyCallbackToChildren($tree, $callback);
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes items from a tree based on a callback, usually used for permission checks
|
||||
*
|
||||
* @param array $tree
|
||||
* @param callable $callback
|
||||
*/
|
||||
protected function applyCallbackToChildren(array &$tree, callable $callback)
|
||||
{
|
||||
if (!isset($tree['_children'])) {
|
||||
return;
|
||||
}
|
||||
foreach ($tree['_children'] as $k => &$childPage) {
|
||||
if (!call_user_func_array($callback, [$childPage])) {
|
||||
unset($tree['_children'][$k]);
|
||||
continue;
|
||||
}
|
||||
$this->applyCallbackToChildren($childPage, $callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the page tree based on a given page record and a given depth
|
||||
*
|
||||
* @param array $pageTree The page record of the top level page you want to get the page tree of
|
||||
* @param int $depth Number of levels to fetch
|
||||
* @return array An array with page records and their children
|
||||
*/
|
||||
public function getTreeLevels(array $pageTree, int $depth): array
|
||||
{
|
||||
$parentPageIds = [$pageTree['uid']];
|
||||
$groupedAndSortedPagesByPid = [];
|
||||
for ($i = 0; $i < $depth; $i++) {
|
||||
if (empty($parentPageIds)) {
|
||||
break;
|
||||
}
|
||||
$pageRecords = $this->getChildPageRecords($parentPageIds);
|
||||
|
||||
$groupedAndSortedPagesByPid = $this->groupAndSortPages($pageRecords, $groupedAndSortedPagesByPid);
|
||||
|
||||
$parentPageIds = array_column($pageRecords, 'uid');
|
||||
}
|
||||
$this->addChildrenToPage($pageTree, $groupedAndSortedPagesByPid);
|
||||
return $pageTree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the page records based on the given parent page ids
|
||||
*
|
||||
* @param array $parentPageIds
|
||||
* @return array
|
||||
*/
|
||||
protected function getChildPageRecords(array $parentPageIds): array
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('pages');
|
||||
$queryBuilder->getRestrictions()
|
||||
->removeAll()
|
||||
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
|
||||
->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->currentWorkspace));
|
||||
|
||||
if (!empty($this->additionalQueryRestrictions)) {
|
||||
foreach ($this->additionalQueryRestrictions as $additionalQueryRestriction) {
|
||||
$queryBuilder->getRestrictions()->add($additionalQueryRestriction);
|
||||
}
|
||||
}
|
||||
|
||||
$pageRecords = $queryBuilder
|
||||
->select(...$this->fields)
|
||||
->from('pages')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('sys_language_uid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)),
|
||||
$queryBuilder->expr()->in('pid', $queryBuilder->createNamedParameter($parentPageIds, Connection::PARAM_INT_ARRAY))
|
||||
)
|
||||
->andWhere(
|
||||
QueryHelper::stripLogicalOperatorPrefix($GLOBALS['BE_USER']->getPagePermsClause(Permission::PAGE_SHOW))
|
||||
)
|
||||
->execute()
|
||||
->fetchAll();
|
||||
|
||||
// This is necessary to resolve all IDs in a workspace
|
||||
if ($this->currentWorkspace !== 0 && !empty($pageRecords)) {
|
||||
$livePagePids = [];
|
||||
$livePageIds = [];
|
||||
$movePlaceholderData = [];
|
||||
foreach ($pageRecords as $pageRecord) {
|
||||
$livePageIds[] = (int)$pageRecord['uid'];
|
||||
$livePagePids[(int)$pageRecord['uid']] = (int)$pageRecord['pid'];
|
||||
if ((int)$pageRecord['t3ver_state'] === VersionState::MOVE_PLACEHOLDER) {
|
||||
$movePlaceholderData[$pageRecord['t3ver_move_id']] = [
|
||||
'pid' => (int)$pageRecord['pid'],
|
||||
'sorting' => (int)$pageRecord['sorting']
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve placeholders of workspace versions
|
||||
$resolver = GeneralUtility::makeInstance(
|
||||
PlainDataResolver::class,
|
||||
'pages',
|
||||
$livePageIds
|
||||
);
|
||||
$resolver->setWorkspaceId($this->currentWorkspace);
|
||||
$resolver->setKeepDeletePlaceholder(false);
|
||||
$resolver->setKeepMovePlaceholder(false);
|
||||
$resolver->setKeepLiveIds(false);
|
||||
$recordIds = $resolver->get();
|
||||
|
||||
if (!empty($recordIds)) {
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$pageRecords = $queryBuilder
|
||||
->select(...$this->fields)
|
||||
->from('pages')
|
||||
->where(
|
||||
$queryBuilder->expr()->in('uid', $queryBuilder->createNamedParameter($recordIds, Connection::PARAM_INT_ARRAY))
|
||||
)
|
||||
->execute()
|
||||
->fetchAll();
|
||||
|
||||
foreach ($pageRecords as &$pageRecord) {
|
||||
if ((int)$pageRecord['t3ver_state'] === VersionState::MOVE_PLACEHOLDER) {
|
||||
$liveRecord = BackendUtility::getRecord('pages', $pageRecord['t3ver_move_id']);
|
||||
$pageRecord['uid'] = (int)$liveRecord['uid'];
|
||||
$pageRecord['t3ver_oid'] = (int)$pageRecord['t3ver_move_id'];
|
||||
$pageRecord['title'] = $liveRecord['title'];
|
||||
} elseif ((int)$pageRecord['t3ver_state'] === VersionState::MOVE_POINTER && !empty($movePlaceholderData[$pageRecord['t3ver_oid']])) {
|
||||
$pageRecord['uid'] = $pageRecord['t3ver_oid'];
|
||||
$pageRecord['sorting'] = (int)$movePlaceholderData[$pageRecord['t3ver_oid']]['sorting'];
|
||||
$pageRecord['t3ver_state'] = VersionState::MOVE_PLACEHOLDER;
|
||||
$pageRecord['pid'] = (int)$movePlaceholderData[$pageRecord['t3ver_oid']]['pid'];
|
||||
} elseif ((int)$pageRecord['t3ver_oid'] > 0) {
|
||||
$liveRecord = BackendUtility::getRecord('pages', $pageRecord['t3ver_oid']);
|
||||
$pageRecord['sorting'] = (int)$liveRecord['sorting'];
|
||||
$pageRecord['uid'] = (int)$liveRecord['uid'];
|
||||
$pageRecord['pid'] = (int)$liveRecord['pid'];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$pageRecords = [];
|
||||
}
|
||||
}
|
||||
foreach ($pageRecords as &$pageRecord) {
|
||||
$pageRecord['uid'] = (int)$pageRecord['uid'];
|
||||
}
|
||||
|
||||
return $pageRecords;
|
||||
}
|
||||
|
||||
public function hasChildren(int $pid): bool
|
||||
{
|
||||
$pageRecords = $this->getChildPageRecords([$pid]);
|
||||
return !empty($pageRecords);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all non-deleted pages, regardless of permissions. That's why it's internal.
|
||||
*
|
||||
* @param array $dbMounts
|
||||
* @param bool $resolveUserPermissions
|
||||
* @return array the full page tree of the whole installation
|
||||
*/
|
||||
protected function fetchAllPages(array $dbMounts, bool $resolveUserPermissions = false): array
|
||||
{
|
||||
if (!empty($this->fullPageTree)) {
|
||||
return $this->fullPageTree;
|
||||
}
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('pages');
|
||||
$queryBuilder->getRestrictions()
|
||||
->removeAll()
|
||||
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
|
||||
->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->currentWorkspace));
|
||||
|
||||
if (!empty($this->additionalQueryRestrictions)) {
|
||||
foreach ($this->additionalQueryRestrictions as $additionalQueryRestriction) {
|
||||
$queryBuilder->getRestrictions()->add($additionalQueryRestriction);
|
||||
}
|
||||
}
|
||||
|
||||
$query = $queryBuilder
|
||||
->select(...$this->fields)
|
||||
->from('pages')
|
||||
->where(
|
||||
// Only show records in default language
|
||||
$queryBuilder->expr()->eq('sys_language_uid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
|
||||
);
|
||||
|
||||
if ($resolveUserPermissions) {
|
||||
$query->andWhere(
|
||||
QueryHelper::stripLogicalOperatorPrefix($GLOBALS['BE_USER']->getPagePermsClause(Permission::PAGE_SHOW))
|
||||
);
|
||||
}
|
||||
|
||||
$pageRecords = $query->execute()->fetchAll();
|
||||
|
||||
$ids = array_column($pageRecords, 'uid');
|
||||
foreach ($dbMounts as $mount) {
|
||||
$entryPointRootLine = BackendUtility::BEgetRootLine($mount, '', false, $this->fields);
|
||||
foreach ($entryPointRootLine as $page) {
|
||||
$pageId = (int)$page['uid'];
|
||||
if (in_array($pageId, $ids) || $pageId === 0) {
|
||||
continue;
|
||||
}
|
||||
$pageRecords[] = $page;
|
||||
$ids[] = $pageId;
|
||||
}
|
||||
}
|
||||
|
||||
$livePagePids = [];
|
||||
$movePlaceholderData = [];
|
||||
// This is necessary to resolve all IDs in a workspace
|
||||
if ($this->currentWorkspace !== 0 && !empty($pageRecords)) {
|
||||
$livePageIds = [];
|
||||
foreach ($pageRecords as $pageRecord) {
|
||||
$livePageIds[] = (int)$pageRecord['uid'];
|
||||
$livePagePids[(int)$pageRecord['uid']] = (int)$pageRecord['pid'];
|
||||
if ((int)$pageRecord['t3ver_state'] === VersionState::MOVE_PLACEHOLDER) {
|
||||
$movePlaceholderData[$pageRecord['t3ver_move_id']] = [
|
||||
'pid' => (int)$pageRecord['pid'],
|
||||
'sorting' => (int)$pageRecord['sorting']
|
||||
];
|
||||
}
|
||||
}
|
||||
// Resolve placeholders of workspace versions
|
||||
$resolver = GeneralUtility::makeInstance(
|
||||
PlainDataResolver::class,
|
||||
'pages',
|
||||
$livePageIds
|
||||
);
|
||||
$resolver->setWorkspaceId($this->currentWorkspace);
|
||||
$resolver->setKeepDeletePlaceholder(false);
|
||||
$resolver->setKeepMovePlaceholder(false);
|
||||
$resolver->setKeepLiveIds(false);
|
||||
$recordIds = $resolver->get();
|
||||
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$pageRecords = $queryBuilder
|
||||
->select(...$this->fields)
|
||||
->from('pages')
|
||||
->where(
|
||||
$queryBuilder->expr()->in('uid', $recordIds)
|
||||
)
|
||||
->execute()
|
||||
->fetchAll();
|
||||
}
|
||||
|
||||
// Now set up sorting, nesting (tree-structure) for all pages based on pid+sorting fields
|
||||
$groupedAndSortedPagesByPid = [];
|
||||
foreach ($pageRecords as $pageRecord) {
|
||||
$parentPageId = (int)$pageRecord['pid'];
|
||||
// In case this is a record from a workspace
|
||||
// The uid+pid of the live-version record is fetched
|
||||
// This is done in order to avoid fetching records again (e.g. via BackendUtility::workspaceOL()
|
||||
if ($parentPageId === -1) {
|
||||
// When a move pointer is found, the pid+sorting of the MOVE_PLACEHOLDER should be used (this is the
|
||||
// workspace record holding this information), also the t3ver_state is set to the MOVE_PLACEHOLDER
|
||||
// because the record is then added
|
||||
if ((int)$pageRecord['t3ver_state'] === VersionState::MOVE_POINTER && !empty($movePlaceholderData[$pageRecord['t3ver_oid']])) {
|
||||
$parentPageId = (int)$movePlaceholderData[$pageRecord['t3ver_oid']]['pid'];
|
||||
$pageRecord['sorting'] = (int)$movePlaceholderData[$pageRecord['t3ver_oid']]['sorting'];
|
||||
$pageRecord['t3ver_state'] = VersionState::MOVE_PLACEHOLDER;
|
||||
} else {
|
||||
// Just a record in a workspace (not moved etc)
|
||||
$parentPageId = (int)$livePagePids[$pageRecord['t3ver_oid']];
|
||||
}
|
||||
// this is necessary so the links to the modules are still pointing to the live IDs
|
||||
$pageRecord['uid'] = (int)$pageRecord['t3ver_oid'];
|
||||
$pageRecord['pid'] = $parentPageId;
|
||||
}
|
||||
|
||||
$sorting = (int)$pageRecord['sorting'];
|
||||
while (isset($groupedAndSortedPagesByPid[$parentPageId][$sorting])) {
|
||||
$sorting++;
|
||||
}
|
||||
$groupedAndSortedPagesByPid[$parentPageId][$sorting] = $pageRecord;
|
||||
}
|
||||
|
||||
$this->fullPageTree = [
|
||||
'uid' => 0,
|
||||
'title' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ?: 'TYPO3'
|
||||
];
|
||||
$this->addChildrenToPage($this->fullPageTree, $groupedAndSortedPagesByPid);
|
||||
return $this->fullPageTree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the property "_children" to a page record with the child pages
|
||||
*
|
||||
* @param array $page
|
||||
* @param array[] $groupedAndSortedPagesByPid
|
||||
*/
|
||||
protected function addChildrenToPage(array &$page, array &$groupedAndSortedPagesByPid)
|
||||
{
|
||||
$page['_children'] = $groupedAndSortedPagesByPid[(int)$page['uid']] ?? [];
|
||||
ksort($page['_children']);
|
||||
foreach ($page['_children'] as &$child) {
|
||||
$this->addChildrenToPage($child, $groupedAndSortedPagesByPid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Looking for a page by traversing the tree
|
||||
*
|
||||
* @param int $pageId the page ID to search for
|
||||
* @param array $pages the page tree to look for the page
|
||||
* @return array Array of the tree data, empty array if nothing was found
|
||||
*/
|
||||
protected function findInPageTree(int $pageId, array $pages): array
|
||||
{
|
||||
foreach ($pages['_children'] as $childPage) {
|
||||
if ((int)$childPage['uid'] === $pageId) {
|
||||
return $childPage;
|
||||
}
|
||||
$result = $this->findInPageTree($pageId, $childPage);
|
||||
if (!empty($result)) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the page tree based on the given search filter
|
||||
*
|
||||
* @param string $searchFilter
|
||||
* @param array $allowedMountPointPageIds
|
||||
* @param string $additionalWhereClause
|
||||
* @return array
|
||||
*/
|
||||
public function fetchFilteredTree(string $searchFilter, array $allowedMountPointPageIds, string $additionalWhereClause): array
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('pages');
|
||||
$queryBuilder->getRestrictions()
|
||||
->removeAll()
|
||||
->add(GeneralUtility::makeInstance(DeletedRestriction::class));
|
||||
|
||||
if (!empty($this->additionalQueryRestrictions)) {
|
||||
foreach ($this->additionalQueryRestrictions as $additionalQueryRestriction) {
|
||||
$queryBuilder->getRestrictions()->add($additionalQueryRestriction);
|
||||
}
|
||||
}
|
||||
|
||||
$expressionBuilder = $queryBuilder->expr();
|
||||
|
||||
if ($this->currentWorkspace === 0) {
|
||||
// Only include ws_id=0
|
||||
$workspaceIdExpression = $expressionBuilder->eq('t3ver_wsid', 0);
|
||||
} else {
|
||||
// Include live records PLUS records from the given workspace
|
||||
$workspaceIdExpression = $expressionBuilder->in(
|
||||
't3ver_wsid',
|
||||
[0, $this->currentWorkspace]
|
||||
);
|
||||
}
|
||||
|
||||
$queryBuilder = $queryBuilder
|
||||
->select(...$this->fields)
|
||||
->from('pages')
|
||||
->where(
|
||||
// Only show records in default language
|
||||
$expressionBuilder->eq('sys_language_uid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)),
|
||||
$workspaceIdExpression,
|
||||
QueryHelper::stripLogicalOperatorPrefix($additionalWhereClause)
|
||||
);
|
||||
|
||||
$searchParts = $expressionBuilder->orX();
|
||||
if (is_numeric($searchFilter) && $searchFilter > 0) {
|
||||
$searchParts->add(
|
||||
$expressionBuilder->eq('uid', $queryBuilder->createNamedParameter($searchFilter, \PDO::PARAM_INT))
|
||||
);
|
||||
}
|
||||
$searchFilter = '%' . $queryBuilder->escapeLikeWildcards($searchFilter) . '%';
|
||||
|
||||
$searchWhereAlias = $expressionBuilder->orX(
|
||||
$expressionBuilder->like(
|
||||
'nav_title',
|
||||
$queryBuilder->createNamedParameter($searchFilter, \PDO::PARAM_STR)
|
||||
),
|
||||
$expressionBuilder->like(
|
||||
'title',
|
||||
$queryBuilder->createNamedParameter($searchFilter, \PDO::PARAM_STR)
|
||||
)
|
||||
);
|
||||
$searchParts->add($searchWhereAlias);
|
||||
|
||||
$queryBuilder->andWhere($searchParts);
|
||||
$pageRecords = $queryBuilder
|
||||
->execute()
|
||||
->fetchAll();
|
||||
|
||||
$livePagePids = [];
|
||||
if ($this->currentWorkspace !== 0 && !empty($pageRecords)) {
|
||||
$livePageIds = [];
|
||||
foreach ($pageRecords as $pageRecord) {
|
||||
$livePageIds[] = (int)$pageRecord['uid'];
|
||||
$livePagePids[(int)$pageRecord['uid']] = (int)$pageRecord['pid'];
|
||||
if ((int)$pageRecord['t3ver_state'] === VersionState::MOVE_POINTER) {
|
||||
$movePlaceholder = BackendUtility::getMovePlaceholder('pages', $pageRecord['t3ver_oid'], '*', $this->currentWorkspace);
|
||||
$movePlaceholderData[$pageRecord['t3ver_oid']] = [
|
||||
'pid' => (int)$movePlaceholder['pid'],
|
||||
'sorting' => (int)$movePlaceholder['sorting']
|
||||
];
|
||||
}
|
||||
if ((int)$pageRecord['t3ver_state'] === VersionState::MOVE_PLACEHOLDER) {
|
||||
$movePlaceholderData[$pageRecord['t3ver_move_id']] = [
|
||||
'pid' => (int)$pageRecord['pid'],
|
||||
'sorting' => (int)$pageRecord['sorting']
|
||||
];
|
||||
}
|
||||
}
|
||||
// Resolve placeholders of workspace versions
|
||||
$resolver = GeneralUtility::makeInstance(
|
||||
PlainDataResolver::class,
|
||||
'pages',
|
||||
$livePageIds
|
||||
);
|
||||
$resolver->setWorkspaceId($this->currentWorkspace);
|
||||
$resolver->setKeepDeletePlaceholder(false);
|
||||
$resolver->setKeepMovePlaceholder(false);
|
||||
$resolver->setKeepLiveIds(false);
|
||||
$recordIds = $resolver->get();
|
||||
|
||||
$pageRecords = [];
|
||||
if (!empty($recordIds)) {
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$queryBuilder
|
||||
->select(...$this->fields)
|
||||
->from('pages')
|
||||
->where(
|
||||
$queryBuilder->expr()->in('uid', $queryBuilder->createNamedParameter($recordIds, Connection::PARAM_INT_ARRAY))
|
||||
);
|
||||
$queryBuilder->andWhere($searchParts);
|
||||
$pageRecords = $queryBuilder
|
||||
->execute()
|
||||
->fetchAll();
|
||||
}
|
||||
}
|
||||
|
||||
$pages = [];
|
||||
foreach ($pageRecords as $pageRecord) {
|
||||
$parentPageId = (int)$pageRecord['pid'];
|
||||
// In case this is a record from a workspace
|
||||
// The uid+pid of the live-version record is fetched
|
||||
// This is done in order to avoid fetching records again (e.g. via BackendUtility::workspaceOL()
|
||||
if ($parentPageId === -1) {
|
||||
// This probably should also remove the live version
|
||||
if ((int)$pageRecord['t3ver_state'] === VersionState::DELETE_PLACEHOLDER) {
|
||||
continue;
|
||||
}
|
||||
// When a move pointer is found, the pid+sorting of the MOVE_PLACEHOLDER should be used (this is the
|
||||
// workspace record holding this information), also the t3ver_state is set to the MOVE_PLACEHOLDER
|
||||
// because the record is then added
|
||||
if ((int)$pageRecord['t3ver_state'] === VersionState::MOVE_POINTER && !empty($movePlaceholderData[$pageRecord['t3ver_oid']])) {
|
||||
$parentPageId = (int)$movePlaceholderData[$pageRecord['t3ver_oid']]['pid'];
|
||||
$pageRecord['sorting'] = (int)$movePlaceholderData[$pageRecord['t3ver_oid']]['sorting'];
|
||||
$pageRecord['t3ver_state'] = VersionState::MOVE_PLACEHOLDER;
|
||||
} else {
|
||||
// Just a record in a workspace (not moved etc)
|
||||
if (isset($livePagePids[$pageRecord['t3ver_oid']])) {
|
||||
$parentPageId = (int)$livePagePids[$pageRecord['t3ver_oid']];
|
||||
} else {
|
||||
$liveRecord = BackendUtility::getRecord('pages', $pageRecord['t3ver_oid'], 'uid,pid');
|
||||
$parentPageId = (int)$liveRecord['pid'];
|
||||
}
|
||||
}
|
||||
// this is necessary so the links to the modules are still pointing to the live IDs
|
||||
$pageRecord['uid'] = (int)$pageRecord['t3ver_oid'];
|
||||
$pageRecord['pid'] = $parentPageId;
|
||||
}
|
||||
$pages[(int)$pageRecord['uid']] = $pageRecord;
|
||||
}
|
||||
unset($pageRecords);
|
||||
|
||||
$pages = $this->filterPagesOnMountPoints($pages, $allowedMountPointPageIds);
|
||||
|
||||
$groupedAndSortedPagesByPid = $this->groupAndSortPages($pages);
|
||||
|
||||
$this->fullPageTree = [
|
||||
'uid' => 0,
|
||||
'title' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ?: 'TYPO3'
|
||||
];
|
||||
$this->addChildrenToPage($this->fullPageTree, $groupedAndSortedPagesByPid);
|
||||
|
||||
return $this->fullPageTree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter all records outside of the allowed mount points
|
||||
*
|
||||
* @param array $pages
|
||||
* @param array $mountPoints
|
||||
* @return array
|
||||
*/
|
||||
protected function filterPagesOnMountPoints(array $pages, array $mountPoints): array
|
||||
{
|
||||
foreach ($pages as $key => $pageRecord) {
|
||||
$rootline = BackendUtility::BEgetRootLine(
|
||||
$pageRecord['uid'],
|
||||
'',
|
||||
$this->currentWorkspace !== 0,
|
||||
$this->fields
|
||||
);
|
||||
$rootline = array_reverse($rootline);
|
||||
if (!in_array(0, $mountPoints, true)) {
|
||||
$isInsideMountPoints = false;
|
||||
foreach ($rootline as $rootlineElement) {
|
||||
if (in_array((int)$rootlineElement['uid'], $mountPoints, true)) {
|
||||
$isInsideMountPoints = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$isInsideMountPoints) {
|
||||
unset($pages[$key]);
|
||||
//skip records outside of the allowed mount points
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$inFilteredRootline = false;
|
||||
$amountOfRootlineElements = count($rootline);
|
||||
for ($i = 0; $i < $amountOfRootlineElements; ++$i) {
|
||||
$rootlineElement = $rootline[$i];
|
||||
$rootlineElement['uid'] = (int)$rootlineElement['uid'];
|
||||
$isInWebMount = false;
|
||||
if ($rootlineElement['uid'] > 0) {
|
||||
$isInWebMount = (int)$this->getBackendUser()->isInWebMount($rootlineElement);
|
||||
}
|
||||
|
||||
if (!$isInWebMount
|
||||
|| ($rootlineElement['uid'] === (int)$mountPoints[0]
|
||||
&& $rootlineElement['uid'] !== $isInWebMount)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if ($this->getBackendUser()->isAdmin() || ($rootlineElement['uid'] === $isInWebMount && in_array($rootlineElement['uid'], $mountPoints, true))) {
|
||||
$inFilteredRootline = true;
|
||||
}
|
||||
if (!$inFilteredRootline) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($pages[$rootlineElement['uid']])) {
|
||||
$pages[$rootlineElement['uid']] = $rootlineElement;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Make sure the mountpoints show up in page tree even when parent pages are not accessible pages
|
||||
foreach ($mountPoints as $mountPoint) {
|
||||
if ($mountPoint !== 0) {
|
||||
if (!array_key_exists($mountPoint, $pages)) {
|
||||
$pages[$mountPoint] = BackendUtility::getRecord('pages', $mountPoint);
|
||||
$pages[$mountPoint]['uid'] = (int)$pages[$mountPoint]['uid'];
|
||||
}
|
||||
$pages[$mountPoint]['pid'] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return $pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group pages by parent page and sort pages based on sorting property
|
||||
*
|
||||
* @param array $pages
|
||||
* @param array $groupedAndSortedPagesByPid
|
||||
* @return array
|
||||
*/
|
||||
protected function groupAndSortPages(array $pages, $groupedAndSortedPagesByPid = []): array
|
||||
{
|
||||
foreach ($pages as $key => $pageRecord) {
|
||||
$parentPageId = (int)$pageRecord['pid'];
|
||||
$sorting = (int)$pageRecord['sorting'];
|
||||
while (isset($groupedAndSortedPagesByPid[$parentPageId][$sorting])) {
|
||||
$sorting++;
|
||||
}
|
||||
$groupedAndSortedPagesByPid[$parentPageId][$sorting] = $pageRecord;
|
||||
}
|
||||
|
||||
return $groupedAndSortedPagesByPid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Tree;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sorted Tree Node Collection
|
||||
*
|
||||
* Note: This collection works only with integers as offset keys and not
|
||||
* with much datasets. You have been warned!
|
||||
*/
|
||||
class SortedTreeNodeCollection extends \TYPO3\CMS\Backend\Tree\TreeNodeCollection
|
||||
{
|
||||
/**
|
||||
* Checks if a specific node is inside the collection
|
||||
*
|
||||
* @param \TYPO3\CMS\Backend\Tree\TreeNode $node
|
||||
* @return bool
|
||||
*/
|
||||
public function contains(\TYPO3\CMS\Backend\Tree\TreeNode $node)
|
||||
{
|
||||
return $this->offsetOf($node) !== -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the offset key of given node
|
||||
*
|
||||
* @param \TYPO3\CMS\Backend\Tree\TreeNode $node
|
||||
* @return int
|
||||
*/
|
||||
protected function offsetOf(\TYPO3\CMS\Backend\Tree\TreeNode $node)
|
||||
{
|
||||
return $this->binarySearch($node, 0, $this->count() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binary search that returns the offset of a given node
|
||||
*
|
||||
* @param \TYPO3\CMS\Backend\Tree\TreeNode $node
|
||||
* @param int $start
|
||||
* @param int $end
|
||||
* @return int
|
||||
*/
|
||||
protected function binarySearch(\TYPO3\CMS\Backend\Tree\TreeNode $node, $start, $end)
|
||||
{
|
||||
if (!$start && $end - $start >= 2 || $end - $start > 2) {
|
||||
$divider = ceil(($end - $start) / 2);
|
||||
if ($this->offsetGet($divider)->equals($node)) {
|
||||
return $divider;
|
||||
}
|
||||
if ($this->offsetGet($divider)->compareTo($node) > 0) {
|
||||
return $this->binarySearch($node, $start, $divider - 1);
|
||||
}
|
||||
return $this->binarySearch($node, $divider + 1, $end);
|
||||
}
|
||||
if ($this->offsetGet($start)->equals($node)) {
|
||||
return $start;
|
||||
}
|
||||
if ($this->offsetGet($end)->equals($node)) {
|
||||
return $end;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the array by reordering the keys
|
||||
*/
|
||||
protected function normalize()
|
||||
{
|
||||
$nodes = [];
|
||||
foreach ($this as $node) {
|
||||
$nodes[] = $node;
|
||||
}
|
||||
$this->exchangeArray($nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a node to the internal list in a sorted approach
|
||||
*
|
||||
* @param \TYPO3\CMS\Backend\Tree\TreeNode $node
|
||||
*/
|
||||
public function append($node)
|
||||
{
|
||||
parent::append($node);
|
||||
$this->asort();
|
||||
$this->normalize();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Tree;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Tree Node
|
||||
*/
|
||||
class TreeNode implements \TYPO3\CMS\Backend\Tree\ComparableNodeInterface, \Serializable
|
||||
{
|
||||
/**
|
||||
* Node Identifier
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $id = '';
|
||||
|
||||
/**
|
||||
* Parent Node
|
||||
*
|
||||
* @var \TYPO3\CMS\Backend\Tree\TreeNode
|
||||
*/
|
||||
protected $parentNode;
|
||||
|
||||
/**
|
||||
* Child Nodes
|
||||
*
|
||||
* @var \TYPO3\CMS\Backend\Tree\TreeNodeCollection
|
||||
*/
|
||||
protected $childNodes;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* You can move an initial data array to initialize the instance and further objects.
|
||||
* This is useful for the deserialization.
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(array $data = [])
|
||||
{
|
||||
if (!empty($data)) {
|
||||
$this->dataFromArray($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the child nodes collection
|
||||
*
|
||||
* @param \TYPO3\CMS\Backend\Tree\TreeNodeCollection $childNodes
|
||||
*/
|
||||
public function setChildNodes(\TYPO3\CMS\Backend\Tree\TreeNodeCollection $childNodes)
|
||||
{
|
||||
$this->childNodes = $childNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes child nodes collection
|
||||
*/
|
||||
public function removeChildNodes()
|
||||
{
|
||||
if ($this->childNodes !== null) {
|
||||
unset($this->childNodes);
|
||||
$this->childNodes = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns child nodes collection
|
||||
*
|
||||
* @return \TYPO3\CMS\Backend\Tree\TreeNodeCollection
|
||||
*/
|
||||
public function getChildNodes()
|
||||
{
|
||||
return $this->childNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns TRUE if the node has child nodes attached
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasChildNodes()
|
||||
{
|
||||
if ($this->childNodes !== null) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the identifier
|
||||
*
|
||||
* @param string $id
|
||||
*/
|
||||
public function setId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identifier
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the parent node
|
||||
*
|
||||
* @param \TYPO3\CMS\Backend\Tree\TreeNode|null $parentNode
|
||||
*/
|
||||
public function setParentNode(\TYPO3\CMS\Backend\Tree\TreeNode $parentNode = null)
|
||||
{
|
||||
$this->parentNode = $parentNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent node
|
||||
*
|
||||
* @return \TYPO3\CMS\Backend\Tree\TreeNode
|
||||
*/
|
||||
public function getParentNode()
|
||||
{
|
||||
return $this->parentNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares a node if it's identical to another node by the id property.
|
||||
*
|
||||
* @param \TYPO3\CMS\Backend\Tree\TreeNode $other
|
||||
* @return bool
|
||||
*/
|
||||
public function equals(\TYPO3\CMS\Backend\Tree\TreeNode $other)
|
||||
{
|
||||
return $this->id == $other->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
return $this->id > $other->getId() ? 1 : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the node in an array representation that can be used for serialization
|
||||
*
|
||||
* @param bool $addChildNodes
|
||||
* @return array
|
||||
*/
|
||||
public function toArray($addChildNodes = true)
|
||||
{
|
||||
$arrayRepresentation = [
|
||||
'serializeClassName' => static::class,
|
||||
'id' => $this->id
|
||||
];
|
||||
if ($this->parentNode !== null) {
|
||||
$arrayRepresentation['parentNode'] = $this->parentNode->toArray(false);
|
||||
} else {
|
||||
$arrayRepresentation['parentNode'] = '';
|
||||
}
|
||||
if ($this->hasChildNodes() && $addChildNodes) {
|
||||
$arrayRepresentation['childNodes'] = $this->childNodes->toArray();
|
||||
} else {
|
||||
$arrayRepresentation['childNodes'] = '';
|
||||
}
|
||||
return $arrayRepresentation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets data of the node by a given data array
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
public function dataFromArray($data)
|
||||
{
|
||||
$this->setId($data['id']);
|
||||
if (isset($data['parentNode']) && $data['parentNode'] !== '') {
|
||||
$this->setParentNode(GeneralUtility::makeInstance($data['parentNode']['serializeClassName'], $data['parentNode']));
|
||||
}
|
||||
if (isset($data['childNodes']) && $data['childNodes'] !== '') {
|
||||
$this->setChildNodes(GeneralUtility::makeInstance($data['childNodes']['serializeClassName'], $data['childNodes']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the serialized instance
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function serialize()
|
||||
{
|
||||
return serialize($this->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the current node with the given serialized informations
|
||||
*
|
||||
* @throws \TYPO3\CMS\Core\Exception if the deserialized object type is not identical to the current one
|
||||
* @param string $serializedString
|
||||
*/
|
||||
public function unserialize($serializedString)
|
||||
{
|
||||
$arrayRepresentation = unserialize($serializedString);
|
||||
if ($arrayRepresentation['serializeClassName'] !== static::class) {
|
||||
throw new \TYPO3\CMS\Core\Exception('Deserialized object type is not identical!', 1294586646);
|
||||
}
|
||||
$this->dataFromArray($arrayRepresentation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Tree;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tree Node Collection
|
||||
*/
|
||||
class TreeNodeCollection extends \ArrayObject
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* You can move an initial data array to initialize the instance and further objects.
|
||||
* This is useful for the deserialization.
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(array $data = [])
|
||||
{
|
||||
parent::__construct();
|
||||
if (!empty($data)) {
|
||||
$this->dataFromArray($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the internal nodes array
|
||||
*/
|
||||
public function asort()
|
||||
{
|
||||
$this->uasort([$this, 'nodeCompare']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares a node with another one
|
||||
*
|
||||
* @see \TYPO3\CMS\Backend\Tree\TreeNode::compareTo
|
||||
* @internal
|
||||
* @param TreeNode $node
|
||||
* @param TreeNode $otherNode
|
||||
* @return int
|
||||
*/
|
||||
public function nodeCompare(\TYPO3\CMS\Backend\Tree\TreeNode $node, \TYPO3\CMS\Backend\Tree\TreeNode $otherNode)
|
||||
{
|
||||
return $node->compareTo($otherNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the serialized instance
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function serialize()
|
||||
{
|
||||
return serialize($this->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the current object with a serialized instance
|
||||
*
|
||||
* @param string $serializedString
|
||||
* @throws \TYPO3\CMS\Core\Exception if the deserialized is not identical to the current class
|
||||
*/
|
||||
public function unserialize($serializedString)
|
||||
{
|
||||
$arrayRepresentation = unserialize($serializedString);
|
||||
if ($arrayRepresentation['serializeClassName'] !== static::class) {
|
||||
throw new \TYPO3\CMS\Core\Exception('Deserialized object type is not identical!', 1294586647);
|
||||
}
|
||||
$this->dataFromArray($arrayRepresentation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the collection in an array representation for e.g. serialization
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
$arrayRepresentation = [
|
||||
'serializeClassName' => static::class
|
||||
];
|
||||
$iterator = $this->getIterator();
|
||||
while ($iterator->valid()) {
|
||||
$arrayRepresentation[] = $iterator->current()->toArray();
|
||||
$iterator->next();
|
||||
}
|
||||
return $arrayRepresentation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the data of the node collection by a given array
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
public function dataFromArray($data)
|
||||
{
|
||||
unset($data['serializeClassName']);
|
||||
foreach ($data as $index => $nodeArray) {
|
||||
$node = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance($nodeArray['serializeClassName'], $nodeArray);
|
||||
$this->offsetSet($index, $node);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Tree;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
|
||||
/**
|
||||
* Representation Tree Node
|
||||
*/
|
||||
class TreeRepresentationNode extends \TYPO3\CMS\Backend\Tree\TreeNode
|
||||
{
|
||||
/**
|
||||
* Node Label
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label = '';
|
||||
|
||||
/**
|
||||
* Node Type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = '';
|
||||
|
||||
/**
|
||||
* Node CSS Class
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $class = '';
|
||||
|
||||
/**
|
||||
* Node Icon
|
||||
*
|
||||
* @var string | Icon
|
||||
*/
|
||||
protected $icon = '';
|
||||
|
||||
/**
|
||||
* Callback function that is called e.g after a click on the label
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $callbackAction = '';
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
*/
|
||||
public function setClass($class)
|
||||
{
|
||||
$this->class = $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getClass()
|
||||
{
|
||||
return $this->class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|Icon $icon
|
||||
*/
|
||||
public function setIcon($icon)
|
||||
{
|
||||
$this->icon = $icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|Icon
|
||||
*/
|
||||
public function getIcon()
|
||||
{
|
||||
return $this->icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $label
|
||||
*/
|
||||
public function setLabel($label)
|
||||
{
|
||||
$this->label = $label;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLabel()
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
*/
|
||||
public function setType($type)
|
||||
{
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the callback action
|
||||
*
|
||||
* @param string $callbackAction
|
||||
*/
|
||||
public function setCallbackAction($callbackAction)
|
||||
{
|
||||
$this->callbackAction = $callbackAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the callback action
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCallbackAction()
|
||||
{
|
||||
return $this->callbackAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the node in an array representation that can be used for serialization
|
||||
*
|
||||
* @param bool $addChildNodes
|
||||
* @return array
|
||||
*/
|
||||
public function toArray($addChildNodes = true)
|
||||
{
|
||||
$arrayRepresentation = parent::toArray();
|
||||
$arrayRepresentation = array_merge($arrayRepresentation, [
|
||||
'label' => $this->label,
|
||||
'type' => $this->type,
|
||||
'class' => $this->class,
|
||||
'icon' => $this->icon,
|
||||
'callbackAction' => $this->callbackAction
|
||||
]);
|
||||
return $arrayRepresentation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets data of the node by a given data array
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
public function dataFromArray($data)
|
||||
{
|
||||
parent::dataFromArray($data);
|
||||
$this->setLabel($data['label']);
|
||||
$this->setType($data['type']);
|
||||
$this->setClass($data['class']);
|
||||
$this->setIcon($data['icon']);
|
||||
$this->setCallbackAction($data['callbackAction']);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Tree\View;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
|
||||
use TYPO3\CMS\Core\Site\PseudoSiteFinder;
|
||||
use TYPO3\CMS\Core\Site\SiteFinder;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Generate a page-tree, browsable.
|
||||
*/
|
||||
class BrowseTreeView extends AbstractTreeView
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $fieldArray = [
|
||||
'uid',
|
||||
'pid',
|
||||
'title',
|
||||
'doktype',
|
||||
'nav_title',
|
||||
'mount_pid',
|
||||
'php_tree_stop',
|
||||
't3ver_id',
|
||||
't3ver_state',
|
||||
'hidden',
|
||||
'starttime',
|
||||
'endtime',
|
||||
'fe_group',
|
||||
'module',
|
||||
'extendToSubpages',
|
||||
'nav_hide',
|
||||
't3ver_wsid',
|
||||
't3ver_move_id',
|
||||
'is_siteroot'
|
||||
];
|
||||
|
||||
/**
|
||||
* override to use this treeName
|
||||
* @var string
|
||||
*/
|
||||
public $treeName = 'browsePages';
|
||||
|
||||
/**
|
||||
* override to use this table
|
||||
* @var string
|
||||
*/
|
||||
public $table = 'pages';
|
||||
|
||||
/**
|
||||
* override to use this domIdPrefix
|
||||
* @var string
|
||||
*/
|
||||
public $domIdPrefix = 'pages';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $ext_showNavTitle = false;
|
||||
|
||||
/**
|
||||
* Initialize, setting what is necessary for browsing pages.
|
||||
* Using the current user.
|
||||
*
|
||||
* @param string $clause Additional clause for selecting pages.
|
||||
* @param string $orderByFields record ORDER BY field
|
||||
*/
|
||||
public function init($clause = '', $orderByFields = '')
|
||||
{
|
||||
$backendUser = $this->getBackendUser();
|
||||
// This will hide records from display - it has nothing to do with user rights!!
|
||||
$clauseExcludePidList = '';
|
||||
$pidList = $backendUser->getTSConfig()['options.']['hideRecords.']['pages'] ?? '';
|
||||
if (!empty($pidList)) {
|
||||
if ($pidList = implode(',', GeneralUtility::intExplode(',', $pidList))) {
|
||||
$clauseExcludePidList = ' AND pages.uid NOT IN (' . $pidList . ')';
|
||||
}
|
||||
}
|
||||
// This is very important for making trees of pages: Filtering out deleted pages, pages with no access to and sorting them correctly:
|
||||
parent::init(' AND deleted=0 AND sys_language_uid=0 AND ' . $backendUser->getPagePermsClause(Permission::PAGE_SHOW) . ' ' . $clause . $clauseExcludePidList, 'sorting');
|
||||
$this->title = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'];
|
||||
$this->MOUNTS = $backendUser->returnWebmounts();
|
||||
if ($pidList) {
|
||||
// Remove mountpoint if explicitly set in options.hideRecords.pages (see above)
|
||||
$hideList = explode(',', $pidList);
|
||||
$this->MOUNTS = array_diff($this->MOUNTS, $hideList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates title attribute content for pages.
|
||||
* Uses API function in \TYPO3\CMS\Backend\Utility\BackendUtility which will retrieve lots of useful information for pages.
|
||||
*
|
||||
* @param array $row The table row.
|
||||
* @return string
|
||||
*/
|
||||
public function getTitleAttrib($row)
|
||||
{
|
||||
return BackendUtility::titleAttribForPages($row, '1=1 ' . $this->clause, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapping the image tag, $icon, for the row, $row (except for mount points)
|
||||
*
|
||||
* @param string $icon The image tag for the icon
|
||||
* @param array $row The row for the current element
|
||||
* @return string The processed icon input value.
|
||||
* @internal
|
||||
*/
|
||||
public function wrapIcon($icon, $row)
|
||||
{
|
||||
// Wrap icon in click-menu link.
|
||||
$theIcon = '';
|
||||
if (!$this->ext_IconMode) {
|
||||
$theIcon = BackendUtility::wrapClickMenuOnIcon($icon, $this->treeName, $this->getId($row), 0);
|
||||
} elseif ($this->ext_IconMode === 'titlelink') {
|
||||
$aOnClick = 'return jumpTo(' . GeneralUtility::quoteJSvalue($this->getJumpToParam($row)) . ',this,'
|
||||
. GeneralUtility::quoteJSvalue($this->domIdPrefix . $this->getId($row)) . ',' . $this->bank . ');';
|
||||
$theIcon = '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $icon . '</a>';
|
||||
}
|
||||
return $theIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the title for the input record. If blank, a "no title" label (localized) will be returned.
|
||||
* Do NOT htmlspecialchar the string from this function - has already been done.
|
||||
*
|
||||
* @param array $row The input row array (where the key "title" is used for the title)
|
||||
* @param int $titleLen Title length (30)
|
||||
* @return string The title.
|
||||
*/
|
||||
public function getTitleStr($row, $titleLen = 30)
|
||||
{
|
||||
if ($this->ext_showNavTitle && isset($row['nav_title']) && trim($row['nav_title']) !== '') {
|
||||
$title = parent::getTitleStr(['title' => $row['nav_title']], $titleLen);
|
||||
} else {
|
||||
$title = parent::getTitleStr($row, $titleLen);
|
||||
}
|
||||
if (!empty($row['is_siteroot'])
|
||||
&& $this->getBackendUser()->getTSConfig()['options.']['pageTree.']['showDomainNameWithTitle'] ?? false
|
||||
) {
|
||||
$pageId = (int)$row['uid'];
|
||||
$siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
|
||||
try {
|
||||
$site = $siteFinder->getSiteByRootPageId($pageId);
|
||||
$title .= ' [' . (string)$site->getBase() . ']';
|
||||
} catch (SiteNotFoundException $e) {
|
||||
// No site found, let's see if it is a legacy-pseudo-site
|
||||
$pseudoSiteFinder = GeneralUtility::makeInstance(PseudoSiteFinder::class);
|
||||
|
||||
try {
|
||||
$site = $pseudoSiteFinder->getSiteByRootPageId($pageId);
|
||||
$title .= ' [' . trim((string)$site->getBase(), '/') . ']';
|
||||
} catch (SiteNotFoundException $e) {
|
||||
// No pseudo-site found either
|
||||
}
|
||||
}
|
||||
}
|
||||
return $title;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Tree\View;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Local position map class when creating new Content Elements
|
||||
*
|
||||
* @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class ContentCreationPagePositionMap extends PagePositionMap
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $dontPrintPageInsertIcons = 1;
|
||||
|
||||
/**
|
||||
* Wrapping the title of the record - here we just return it.
|
||||
*
|
||||
* @param string $str The title value.
|
||||
* @param array $row The record row.
|
||||
* @return string Wrapped title string.
|
||||
*/
|
||||
public function wrapRecordTitle($str, $row)
|
||||
{
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create on-click event value.
|
||||
*
|
||||
* @param array $row The record.
|
||||
* @param string $vv Column position value.
|
||||
* @param int $moveUid Move uid
|
||||
* @param int $pid PID value.
|
||||
* @param int $sys_lang System language
|
||||
* @return string
|
||||
*/
|
||||
public function onClickInsertRecord($row, $vv, $moveUid, $pid, $sys_lang = 0)
|
||||
{
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
$location = (string)$uriBuilder->buildUriFromRoute('record_edit', [
|
||||
'edit[tt_content][' . (is_array($row) ? -$row['uid'] : $pid) . ']' => 'new',
|
||||
'defVals[tt_content][colPos]' => $vv,
|
||||
'defVals[tt_content][sys_language_uid]' => $sys_lang,
|
||||
'returnUrl' => GeneralUtility::_GP('returnUrl')
|
||||
]);
|
||||
return $this->clientContext . '.location.href=' . GeneralUtility::quoteJSvalue($location) . '+document.editForm.defValues.value; return false;';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Tree\View;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Position map class for content elements within the page module
|
||||
*
|
||||
* @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class ContentLayoutPagePositionMap extends PagePositionMap
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $dontPrintPageInsertIcons = 1;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $l_insertNewRecordHere = 'newContentElement';
|
||||
|
||||
/**
|
||||
* Wrapping the title of the record.
|
||||
*
|
||||
* @param string $str The title value.
|
||||
* @param array $row The record row.
|
||||
* @return string Wrapped title string.
|
||||
*/
|
||||
public function wrapRecordTitle($str, $row)
|
||||
{
|
||||
$aOnClick = 'jumpToUrl(' . GeneralUtility::quoteJSvalue($this->linkToCurrentModule(['edit_record' => 'tt_content:' . $row['uid']])) . ');return false;';
|
||||
return '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $str . '</a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapping the column header
|
||||
*
|
||||
* @param string $str Header value
|
||||
* @param string $vv Column info.
|
||||
* @return string
|
||||
* @see printRecordMap()
|
||||
*/
|
||||
public function wrapColumnHeader($str, $vv)
|
||||
{
|
||||
$aOnClick = 'jumpToUrl(' . GeneralUtility::quoteJSvalue($this->linkToCurrentModule(['edit_record' => '_EDIT_COL:' . $vv])) . ');return false;';
|
||||
return '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $str . '</a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create on-click event value.
|
||||
*
|
||||
* @param array $row The record.
|
||||
* @param string $vv Column position value.
|
||||
* @param int $moveUid Move uid
|
||||
* @param int $pid PID value.
|
||||
* @param int $sys_lang System language
|
||||
* @return string
|
||||
*/
|
||||
public function onClickInsertRecord($row, $vv, $moveUid, $pid, $sys_lang = 0)
|
||||
{
|
||||
if (is_array($row)) {
|
||||
$linkInformation = 'tt_content:new/-' . $row['uid'] . '/' . $row['colPos'];
|
||||
} else {
|
||||
$linkInformation = 'tt_content:new/' . $pid . '/' . $vv;
|
||||
}
|
||||
$location = $this->linkToCurrentModule(['edit_record' => $linkInformation]);
|
||||
return 'jumpToUrl(' . GeneralUtility::quoteJSvalue($location) . ');return false;';
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapping the record header (from getRecordHeader())
|
||||
*
|
||||
* @param string $str HTML content
|
||||
* @param array $row Record array.
|
||||
* @return string HTML content
|
||||
*/
|
||||
public function wrapRecordHeader($str, $row)
|
||||
{
|
||||
if ($row['uid'] == $this->moveUid) {
|
||||
/** @var IconFactory $iconFactory */
|
||||
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
return $iconFactory->getIcon('status-status-current', Icon::SIZE_SMALL)->render() . $str;
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns URL to the current script.
|
||||
* In particular the "popView" and "new_unique_uid" Get vars are unset.
|
||||
*
|
||||
* @param array $params Parameters array, merged with global GET vars.
|
||||
* @return string URL
|
||||
*/
|
||||
protected function linkToCurrentModule($params)
|
||||
{
|
||||
unset($params['popView']);
|
||||
unset($params['new_unique_uid']);
|
||||
return GeneralUtility::linkThisScript($params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Tree\View;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Position map class for moving content elements.
|
||||
*
|
||||
* @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class ContentMovingPagePositionMap extends PagePositionMap
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $dontPrintPageInsertIcons = 1;
|
||||
|
||||
/**
|
||||
* Page tree implementation class name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $pageTreeClassName = PageTreeView::class;
|
||||
|
||||
/**
|
||||
* Wrapping page title.
|
||||
*
|
||||
* @param string $str Page title.
|
||||
* @param array $rec Page record (?)
|
||||
* @return string Wrapped title.
|
||||
*/
|
||||
public function linkPageTitle($str, $rec)
|
||||
{
|
||||
$url = \TYPO3\CMS\Core\Utility\GeneralUtility::linkThisScript(['uid' => (int)$rec['uid'], 'moveUid' => $this->moveUid]);
|
||||
return '<a href="' . htmlspecialchars($url) . '">' . $str . '</a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapping the title of the record.
|
||||
*
|
||||
* @param string $str The title value.
|
||||
* @param array $row The record row.
|
||||
* @return string Wrapped title string.
|
||||
*/
|
||||
public function wrapRecordTitle($str, $row)
|
||||
{
|
||||
if ($this->moveUid == $row['uid']) {
|
||||
$str = '<strong>' . $str . '</strong>';
|
||||
}
|
||||
return parent::wrapRecordTitle($str, $row);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Tree\View;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Resource\Folder;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\HttpUtility;
|
||||
use TYPO3\CMS\Recordlist\Tree\View\LinkParameterProviderInterface;
|
||||
|
||||
/**
|
||||
* Base extension class which generates the folder tree.
|
||||
* Used directly by the RTE.
|
||||
* also used for the linkpicker on files
|
||||
*
|
||||
* Browsable folder tree, used in Element Browser and RTE (for which it will be extended)
|
||||
* previously located inside typo3/class.browse_links.php
|
||||
*/
|
||||
class ElementBrowserFolderTreeView extends FolderTreeView
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $ext_IconMode = 'titlelink';
|
||||
|
||||
/**
|
||||
* @var LinkParameterProviderInterface
|
||||
*/
|
||||
protected $linkParameterProvider;
|
||||
|
||||
/**
|
||||
* @param LinkParameterProviderInterface $linkParameterProvider
|
||||
*/
|
||||
public function setLinkParameterProvider(LinkParameterProviderInterface $linkParameterProvider)
|
||||
{
|
||||
$this->linkParameterProvider = $linkParameterProvider;
|
||||
$this->thisScript = $linkParameterProvider->getScriptUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapping the folder icon
|
||||
*
|
||||
* @param string $icon The image tag for the icon
|
||||
* @param Folder $folderObject The row for the current element
|
||||
*
|
||||
* @return string The processed icon input value.
|
||||
* @internal
|
||||
*/
|
||||
public function wrapIcon($icon, $folderObject)
|
||||
{
|
||||
// Add title attribute to input icon tag
|
||||
$theFolderIcon = '';
|
||||
|
||||
// Wrap icon in link (in ElementBrowser only the "titlelink" is used).
|
||||
if ($this->ext_IconMode === 'titlelink') {
|
||||
$parameters = HttpUtility::buildQueryString(
|
||||
$this->linkParameterProvider->getUrlParameters(['identifier' => $folderObject->getCombinedIdentifier()])
|
||||
);
|
||||
$aOnClick = 'return jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . $parameters) . ');';
|
||||
$theFolderIcon = '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $icon . '</a>';
|
||||
}
|
||||
|
||||
return $theFolderIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapping the title in a link, if applicable.
|
||||
*
|
||||
* @param string $title Title, ready for output.
|
||||
* @param Folder $folderObject The record
|
||||
* @param int $bank Bank pointer (which mount point number)
|
||||
* @return string Wrapping title string.
|
||||
*/
|
||||
public function wrapTitle($title, $folderObject, $bank = 0)
|
||||
{
|
||||
$parameters = HttpUtility::buildQueryString(
|
||||
$this->linkParameterProvider->getUrlParameters(['identifier' => $folderObject->getCombinedIdentifier()])
|
||||
);
|
||||
return '<a href="#" onclick="return jumpToUrl(' . htmlspecialchars(GeneralUtility::quoteJSvalue($this->getThisScript() . $parameters)) . ');">' . $title . '</a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns TRUE if the input "record" contains a folder which can be linked.
|
||||
*
|
||||
* @param Folder $folderObject Object with information about the folder element. Contains keys like title, uid, path, _title
|
||||
* @return bool TRUE
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function ext_isLinkable(Folder $folderObject)
|
||||
{
|
||||
trigger_error('This method is obsolete and will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $cmd
|
||||
* @param bool $isOpen
|
||||
* @return string
|
||||
*/
|
||||
protected function renderPMIconAndLink($cmd, $isOpen)
|
||||
{
|
||||
if (static::class === __CLASS__) {
|
||||
return $this->PMiconATagWrap('', $cmd, !$isOpen);
|
||||
}
|
||||
return parent::renderPMIconAndLink($cmd, $isOpen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the plus/minus icon in a link
|
||||
*
|
||||
* @param string $icon HTML string to wrap, probably an image tag.
|
||||
* @param string $cmd Command for 'PM' get var
|
||||
* @param bool|string $bMark If set, the link will have an anchor point (=$bMark) and a name attribute (=$bMark)
|
||||
* @param bool $isOpen check if the item has children
|
||||
* @return string Link-wrapped input string
|
||||
* @internal
|
||||
*/
|
||||
public function PM_ATagWrap($icon, $cmd, $bMark = '', $isOpen = false)
|
||||
{
|
||||
$anchor = $bMark ? '#' . $bMark : '';
|
||||
$name = $bMark ? ' name=' . $bMark : '';
|
||||
$urlParameters = $this->linkParameterProvider->getUrlParameters([]);
|
||||
$urlParameters['PM'] = $cmd;
|
||||
$aOnClick = 'return jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . HttpUtility::buildQueryString($urlParameters)) . ',' . GeneralUtility::quoteJSvalue($anchor) . ');';
|
||||
return '<a href="#"' . htmlspecialchars($name) . ' onclick="' . htmlspecialchars($aOnClick) . '">' . $icon . '</a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the plus/minus icon in a link
|
||||
*
|
||||
* @param string $icon HTML string to wrap, probably an image tag.
|
||||
* @param string $cmd Command for 'PM' get var
|
||||
* @param bool $isExpand Whether to be expanded
|
||||
* @return string Link-wrapped input string
|
||||
* @internal
|
||||
*/
|
||||
public function PMiconATagWrap($icon, $cmd, $isExpand = true)
|
||||
{
|
||||
if (empty($this->scope)) {
|
||||
$this->scope = [
|
||||
'class' => static::class,
|
||||
'script' => $this->thisScript,
|
||||
'browser' => $this->linkParameterProvider->getUrlParameters([]),
|
||||
];
|
||||
}
|
||||
|
||||
return parent::PMiconATagWrap($icon, $cmd, $isExpand);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Tree\View;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\LinkHandling\LinkService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\HttpUtility;
|
||||
use TYPO3\CMS\Frontend\Page\PageRepository;
|
||||
use TYPO3\CMS\Recordlist\Tree\View\LinkParameterProviderInterface;
|
||||
|
||||
/**
|
||||
* Class which generates the selectable page tree
|
||||
*
|
||||
* Browsable tree, used in PagePositionMaps (move elements), the Link Wizard and the Database Browser (for which it will be extended)
|
||||
*/
|
||||
class ElementBrowserPageTreeView extends BrowseTreeView
|
||||
{
|
||||
/**
|
||||
* whether the page ID should be shown next to the title, activate through
|
||||
* userTSconfig (options.pageTree.showPageIdWithTitle)
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $ext_showPageId = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $ext_pArrPages = true;
|
||||
|
||||
/**
|
||||
* @var LinkParameterProviderInterface
|
||||
*/
|
||||
protected $linkParameterProvider;
|
||||
|
||||
/**
|
||||
* Constructor. Just calling init()
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->init();
|
||||
$this->clause = ' AND doktype <> ' . PageRepository::DOKTYPE_RECYCLER . $this->clause;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LinkParameterProviderInterface $linkParameterProvider
|
||||
*/
|
||||
public function setLinkParameterProvider(LinkParameterProviderInterface $linkParameterProvider)
|
||||
{
|
||||
$this->linkParameterProvider = $linkParameterProvider;
|
||||
$this->thisScript = $linkParameterProvider->getScriptUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapping the title in a link, if applicable.
|
||||
*
|
||||
* @param string $title Title, (must be ready for output, that means it must be htmlspecialchars()'ed).
|
||||
* @param array $v The record
|
||||
* @param bool $ext_pArrPages (ignored)
|
||||
* @return string Wrapping title string.
|
||||
*/
|
||||
public function wrapTitle($title, $v, $ext_pArrPages = false)
|
||||
{
|
||||
if ($this->ext_isLinkable($v['doktype'], $v['uid'])) {
|
||||
$url = GeneralUtility::makeInstance(LinkService::class)->asString(['type' => LinkService::TYPE_PAGE, 'pageuid' => (int)$v['uid']]);
|
||||
return '<span class="list-tree-title"><a href="' . htmlspecialchars($url) . '" class="t3js-pageLink">' . $title . '</a></span>';
|
||||
}
|
||||
return '<span class="list-tree-title text-muted">' . $title . '</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the page navigation tree in HTML
|
||||
*
|
||||
* @param array|string $treeArr Tree array
|
||||
* @return string HTML output.
|
||||
*/
|
||||
public function printTree($treeArr = '')
|
||||
{
|
||||
$titleLen = (int)$GLOBALS['BE_USER']->uc['titleLen'];
|
||||
if (!is_array($treeArr)) {
|
||||
$treeArr = $this->tree;
|
||||
}
|
||||
$out = '';
|
||||
// We need to count the opened <ul>'s every time we dig into another level,
|
||||
// so we know how many we have to close when all children are done rendering
|
||||
$closeDepth = [];
|
||||
foreach ($treeArr as $treeItem) {
|
||||
if ($treeItem['isMountPointPath']) {
|
||||
$out .= '<li class="list-tree-path">' . $treeItem['title'] . '</li>';
|
||||
continue;
|
||||
}
|
||||
|
||||
$classAttr = $treeItem['row']['_CSSCLASS'];
|
||||
if ($treeItem['isFirst']) {
|
||||
$out .= '<ul class="list-tree">';
|
||||
}
|
||||
|
||||
// Add CSS classes to the list item
|
||||
if ($treeItem['hasSub']) {
|
||||
$classAttr .= ' list-tree-control-open';
|
||||
}
|
||||
|
||||
$selected = '';
|
||||
if ($this->linkParameterProvider->isCurrentlySelectedItem(['pid' => (int)$treeItem['row']['uid']])) {
|
||||
$selected = ' bg-success';
|
||||
$classAttr .= ' active';
|
||||
}
|
||||
$urlParameters = $this->linkParameterProvider->getUrlParameters(['pid' => (int)$treeItem['row']['uid']]);
|
||||
$aOnClick = 'return jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . HttpUtility::buildQueryString($urlParameters)) . ');';
|
||||
$cEbullet = $this->ext_isLinkable($treeItem['row']['doktype'], $treeItem['row']['uid'])
|
||||
? '<a href="#" class="list-tree-show" onclick="' . htmlspecialchars($aOnClick) . '"><i class="fa fa-caret-square-o-right"></i></a>'
|
||||
: '';
|
||||
$out .= '
|
||||
<li' . ($classAttr ? ' class="' . trim($classAttr) . '"' : '') . '>
|
||||
<span class="list-tree-group' . $selected . '">
|
||||
' . $cEbullet . $treeItem['HTML'] . $this->wrapTitle($this->getTitleStr($treeItem['row'], $titleLen), $treeItem['row'], $this->ext_pArrPages) . '
|
||||
</span>
|
||||
';
|
||||
if (!$treeItem['hasSub']) {
|
||||
$out .= '</li>';
|
||||
}
|
||||
|
||||
// We have to remember if this is the last one
|
||||
// on level X so the last child on level X+1 closes the <ul>-tag
|
||||
if ($treeItem['isLast']) {
|
||||
$closeDepth[$treeItem['invertedDepth']] = 1;
|
||||
}
|
||||
// If this is the last one and does not have subitems, we need to close
|
||||
// the tree as long as the upper levels have last items too
|
||||
if ($treeItem['isLast'] && !$treeItem['hasSub']) {
|
||||
for ($i = $treeItem['invertedDepth']; $closeDepth[$i] == 1; $i++) {
|
||||
$closeDepth[$i] = 0;
|
||||
$out .= '</ul></li>';
|
||||
}
|
||||
}
|
||||
}
|
||||
return '<ul class="list-tree list-tree-root">' . $out . '</ul>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns TRUE if a doktype can be linked.
|
||||
*
|
||||
* @param int $doktype Doktype value to test
|
||||
* @param int $uid uid to test.
|
||||
* @return bool
|
||||
*/
|
||||
public function ext_isLinkable($doktype, $uid)
|
||||
{
|
||||
return $uid && $doktype < PageRepository::DOKTYPE_SPACER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the plus/minus icon in a link
|
||||
*
|
||||
* @param string $icon HTML string to wrap, probably an image tag.
|
||||
* @param string $cmd Command for 'PM' get var
|
||||
* @param string $bMark If set, the link will have an anchor point (=$bMark) and a name attribute (=$bMark)
|
||||
* @param bool $isOpen
|
||||
* @return string Link-wrapped input string
|
||||
*/
|
||||
public function PM_ATagWrap($icon, $cmd, $bMark = '', $isOpen = false)
|
||||
{
|
||||
$anchor = $bMark ? '#' . $bMark : '';
|
||||
$name = $bMark ? ' name=' . $bMark : '';
|
||||
$urlParameters = $this->linkParameterProvider->getUrlParameters([]);
|
||||
$urlParameters['PM'] = $cmd;
|
||||
$aOnClick = 'return jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . HttpUtility::buildQueryString($urlParameters)) . ',' . GeneralUtility::quoteJSvalue($anchor) . ');';
|
||||
return '<a class="list-tree-control ' . ($isOpen ? 'list-tree-control-open' : 'list-tree-control-closed')
|
||||
. '" href="#"' . htmlspecialchars($name) . ' onclick="' . htmlspecialchars($aOnClick) . '"><i class="fa"></i></a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapping the image tag, $icon, for the row, $row
|
||||
*
|
||||
* @param string $icon The image tag for the icon
|
||||
* @param array $row The row for the current element
|
||||
* @return string The processed icon input value.
|
||||
*/
|
||||
public function wrapIcon($icon, $row)
|
||||
{
|
||||
if ($this->ext_showPageId) {
|
||||
$icon .= '[' . $row['uid'] . '] ';
|
||||
}
|
||||
return $icon;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,717 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Tree\View;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageService;
|
||||
use TYPO3\CMS\Core\Resource\Folder;
|
||||
use TYPO3\CMS\Core\Resource\FolderInterface;
|
||||
use TYPO3\CMS\Core\Resource\InaccessibleFolder;
|
||||
use TYPO3\CMS\Core\Resource\ResourceStorage;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Generate a folder tree,
|
||||
* specially made for browsing folders in the File module
|
||||
*
|
||||
* @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class FolderTreeView extends AbstractTreeView
|
||||
{
|
||||
/**
|
||||
* The users' file Storages
|
||||
*
|
||||
* @var ResourceStorage[]
|
||||
*/
|
||||
protected $storages;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $storageHashNumbers;
|
||||
|
||||
/**
|
||||
* Indicates, whether the AJAX call was successful,
|
||||
* i.e. the requested page has been found
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $ajaxStatus = false;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $scope;
|
||||
|
||||
/**
|
||||
* @var IconFactory
|
||||
*/
|
||||
protected $iconFactory;
|
||||
|
||||
/**
|
||||
* override to not use a title attribute
|
||||
* @var string
|
||||
*/
|
||||
public $titleAttrib = '';
|
||||
|
||||
/**
|
||||
* override to use this treeName
|
||||
* does not need to be set in __construct()
|
||||
* @var string
|
||||
*/
|
||||
public $treeName = 'folder';
|
||||
|
||||
/**
|
||||
* override to use this domIdPrefix
|
||||
* @var string
|
||||
*/
|
||||
public $domIdPrefix = 'folder';
|
||||
|
||||
/**
|
||||
* Constructor function of the class
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->init();
|
||||
$this->storages = $this->BE_USER->getFileStorages();
|
||||
$this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the plus/minus icon for the browsable tree.
|
||||
*
|
||||
* @param Folder $folderObject Entry folder object
|
||||
* @param int $subFolderCounter The current entry number
|
||||
* @param int $totalSubFolders The total number of entries. If equal to $a, a "bottom" element is returned.
|
||||
* @param int $nextCount The number of sub-elements to the current element.
|
||||
* @param bool $isExpanded The element was expanded to render subelements if this flag is set.
|
||||
*
|
||||
* @return string Image tag with the plus/minus icon.
|
||||
* @internal
|
||||
* @see \TYPO3\CMS\Backend\Tree\View\PageTreeView::PMicon()
|
||||
*/
|
||||
public function PMicon($folderObject, $subFolderCounter, $totalSubFolders, $nextCount, $isExpanded)
|
||||
{
|
||||
$icon = '';
|
||||
if ($nextCount) {
|
||||
$cmd = $this->generateExpandCollapseParameter($this->bank, !$isExpanded, $folderObject);
|
||||
$icon = $this->PMiconATagWrap($icon, $cmd, !$isExpanded);
|
||||
}
|
||||
return $icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the plus/minus icon in a link
|
||||
*
|
||||
* @param string $icon HTML string to wrap, probably an image tag.
|
||||
* @param string $cmd Command for 'PM' get var
|
||||
* @param bool $isExpand Whether to be expanded
|
||||
* @return string Link-wrapped input string
|
||||
* @internal
|
||||
*/
|
||||
public function PMiconATagWrap($icon, $cmd, $isExpand = true)
|
||||
{
|
||||
if (empty($this->scope)) {
|
||||
$this->scope = [
|
||||
'class' => static::class,
|
||||
'script' => $this->thisScript,
|
||||
];
|
||||
}
|
||||
|
||||
if ($this->thisScript) {
|
||||
// Activates dynamic AJAX based tree
|
||||
$scopeData = json_encode($this->scope);
|
||||
$scopeHash = GeneralUtility::hmac($scopeData);
|
||||
$js = htmlspecialchars('Tree.load(' . GeneralUtility::quoteJSvalue($cmd) . ', ' . (int)$isExpand . ', this, ' . GeneralUtility::quoteJSvalue($scopeData) . ', ' . GeneralUtility::quoteJSvalue($scopeHash) . ');');
|
||||
return '<a class="list-tree-control' . (!$isExpand ? ' list-tree-control-open' : ' list-tree-control-closed') . '" onclick="' . $js . '"><i class="fa"></i></a>';
|
||||
}
|
||||
return $icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $cmd
|
||||
* @param bool $isOpen
|
||||
* @return string
|
||||
*/
|
||||
protected function renderPMIconAndLink($cmd, $isOpen)
|
||||
{
|
||||
$link = $this->thisScript ? ' href="' . htmlspecialchars($this->getThisScript() . 'PM=' . $cmd) . '"' : '';
|
||||
return '<a class="list-tree-control list-tree-control-' . ($isOpen ? 'open' : 'closed') . '"' . $link . '><i class="fa"></i></a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapping the folder icon
|
||||
*
|
||||
* @param string $icon The image tag for the icon
|
||||
* @param Folder $folderObject The row for the current element
|
||||
*
|
||||
* @return string The processed icon input value.
|
||||
* @internal
|
||||
*/
|
||||
public function wrapIcon($icon, $folderObject)
|
||||
{
|
||||
// Add title attribute to input icon tag
|
||||
$theFolderIcon = '';
|
||||
// Wrap icon in click-menu link.
|
||||
if (!$this->ext_IconMode) {
|
||||
// Check storage access to wrap with click menu
|
||||
if (!$folderObject instanceof InaccessibleFolder) {
|
||||
$tableName = $this->getTableNameForClickMenu($folderObject);
|
||||
$theFolderIcon = BackendUtility::wrapClickMenuOnIcon($icon, $tableName, $folderObject->getCombinedIdentifier(), 'tree');
|
||||
}
|
||||
} elseif ($this->ext_IconMode === 'titlelink') {
|
||||
$aOnClick = 'return jumpTo(' . GeneralUtility::quoteJSvalue($this->getJumpToParam($folderObject)) . ',this,' . GeneralUtility::quoteJSvalue($this->domIdPrefix . $this->getId($folderObject)) . ',' . $this->bank . ');';
|
||||
$theFolderIcon = '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $icon . '</a>';
|
||||
}
|
||||
return $theFolderIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapping $title in a-tags.
|
||||
*
|
||||
* @param string $title Title string
|
||||
* @param Folder $folderObject the folder record
|
||||
* @param int $bank Bank pointer (which mount point number)
|
||||
*
|
||||
* @return string
|
||||
* @internal
|
||||
*/
|
||||
public function wrapTitle($title, $folderObject, $bank = 0)
|
||||
{
|
||||
// Check storage access to wrap with click menu
|
||||
if ($folderObject instanceof InaccessibleFolder) {
|
||||
return $title;
|
||||
}
|
||||
$aOnClick = 'return jumpTo(' . GeneralUtility::quoteJSvalue($this->getJumpToParam($folderObject)) . ', this, ' . GeneralUtility::quoteJSvalue($this->domIdPrefix . $this->getId($folderObject)) . ', ' . $bank . ');';
|
||||
$tableName = $this->getTableNameForClickMenu($folderObject);
|
||||
$clickMenuParts = BackendUtility::wrapClickMenuOnIcon('', $tableName, $folderObject->getCombinedIdentifier(), 'tree', '', '', true);
|
||||
|
||||
return '<a href="#" title="' . htmlspecialchars(strip_tags($title)) . '" onclick="' . htmlspecialchars($aOnClick) . '" ' . GeneralUtility::implodeAttributes($clickMenuParts) . '>' . $title . '</a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the id from the record - for folders, this is an md5 hash.
|
||||
*
|
||||
* @param Folder $folderObject The folder object
|
||||
*
|
||||
* @return int The "uid" field value.
|
||||
*/
|
||||
public function getId($folderObject)
|
||||
{
|
||||
return GeneralUtility::md5int($folderObject->getCombinedIdentifier());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns jump-url parameter value.
|
||||
*
|
||||
* @param Folder $folderObject The folder object
|
||||
*
|
||||
* @return string The jump-url parameter.
|
||||
*/
|
||||
public function getJumpToParam($folderObject)
|
||||
{
|
||||
return rawurlencode($folderObject->getCombinedIdentifier());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the title for the input record. If blank, a "no title" label (localized) will be returned.
|
||||
* '_title' is used for setting an alternative title for folders.
|
||||
*
|
||||
* @param array $row The input row array (where the key "_title" is used for the title)
|
||||
* @param int $titleLen Title length (30)
|
||||
* @return string The title
|
||||
*/
|
||||
public function getTitleStr($row, $titleLen = 30)
|
||||
{
|
||||
return $row['_title'] ?? parent::getTitleStr($row, $titleLen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value for the image "title" attribute
|
||||
*
|
||||
* @param Folder $folderObject The folder to be used
|
||||
*
|
||||
* @return string The attribute value (is htmlspecialchared() already)
|
||||
*/
|
||||
public function getTitleAttrib($folderObject)
|
||||
{
|
||||
return htmlspecialchars($folderObject->getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Will create and return the HTML code for a browsable tree of folders.
|
||||
* Is based on the mounts found in the internal array ->MOUNTS (set in the constructor)
|
||||
*
|
||||
* @return string HTML code for the browsable tree
|
||||
*/
|
||||
public function getBrowsableTree()
|
||||
{
|
||||
// Get stored tree structure AND updating it if needed according to incoming PM GET var.
|
||||
$this->initializePositionSaving();
|
||||
// Init done:
|
||||
$treeItems = [];
|
||||
// Traverse mounts:
|
||||
foreach ($this->storages as $storageObject) {
|
||||
$this->getBrowseableTreeForStorage($storageObject);
|
||||
// Add tree:
|
||||
$treeItems = array_merge($treeItems, $this->tree);
|
||||
}
|
||||
return $this->printTree($treeItems);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a tree for one storage
|
||||
*
|
||||
* @param ResourceStorage $storageObject
|
||||
*/
|
||||
public function getBrowseableTreeForStorage(ResourceStorage $storageObject)
|
||||
{
|
||||
// If there are filemounts, show each, otherwise just the rootlevel folder
|
||||
$fileMounts = $storageObject->getFileMounts();
|
||||
$rootLevelFolders = [];
|
||||
if (!empty($fileMounts)) {
|
||||
foreach ($fileMounts as $fileMountInfo) {
|
||||
$rootLevelFolders[] = [
|
||||
'folder' => $fileMountInfo['folder'],
|
||||
'name' => $fileMountInfo['title']
|
||||
];
|
||||
}
|
||||
} elseif ($this->BE_USER->isAdmin()) {
|
||||
$rootLevelFolders[] = [
|
||||
'folder' => $storageObject->getRootLevelFolder(),
|
||||
'name' => $storageObject->getName()
|
||||
];
|
||||
}
|
||||
// Clean the tree
|
||||
$this->reset();
|
||||
// Go through all "root level folders" of this tree (can be the rootlevel folder or any file mount points)
|
||||
foreach ($rootLevelFolders as $rootLevelFolderInfo) {
|
||||
/** @var Folder $rootLevelFolder */
|
||||
$rootLevelFolder = $rootLevelFolderInfo['folder'];
|
||||
$rootLevelFolderName = $rootLevelFolderInfo['name'];
|
||||
$folderHashSpecUID = GeneralUtility::md5int($rootLevelFolder->getCombinedIdentifier());
|
||||
$this->specUIDmap[$folderHashSpecUID] = $rootLevelFolder->getCombinedIdentifier();
|
||||
// Hash key
|
||||
$storageHashNumber = $this->getShortHashNumberForStorage($storageObject, $rootLevelFolder);
|
||||
// Set first:
|
||||
$this->bank = $storageHashNumber;
|
||||
$isOpen = $this->stored[$storageHashNumber][$folderHashSpecUID] || $this->expandFirst;
|
||||
// Set PM icon:
|
||||
$cmd = $this->generateExpandCollapseParameter($this->bank, !$isOpen, $rootLevelFolder);
|
||||
// Only show and link icon if storage is browseable
|
||||
if (!$storageObject->isBrowsable() || $this->getNumberOfSubfolders($rootLevelFolder) === 0) {
|
||||
$firstHtml = '';
|
||||
} else {
|
||||
$firstHtml = $this->renderPMIconAndLink($cmd, $isOpen);
|
||||
}
|
||||
// Mark a storage which is not online, as offline
|
||||
// maybe someday there will be a special icon for this
|
||||
if ($storageObject->isOnline() === false) {
|
||||
$rootLevelFolderName .= ' (' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_file.xlf:sys_file_storage.isOffline') . ')';
|
||||
}
|
||||
// Preparing rootRec for the mount
|
||||
$icon = $this->iconFactory->getIconForResource($rootLevelFolder, Icon::SIZE_SMALL, null, ['mount-root' => true]);
|
||||
$firstHtml .= $this->wrapIcon($icon, $rootLevelFolder);
|
||||
$row = [
|
||||
'uid' => $folderHashSpecUID,
|
||||
'title' => $rootLevelFolderName,
|
||||
'path' => $rootLevelFolder->getCombinedIdentifier(),
|
||||
'folder' => $rootLevelFolder
|
||||
];
|
||||
// Add the storage root to ->tree
|
||||
$this->tree[] = [
|
||||
'HTML' => $firstHtml,
|
||||
'row' => $row,
|
||||
'bank' => $this->bank,
|
||||
// hasSub is TRUE when the root of the storage is expanded
|
||||
'hasSub' => $isOpen && $storageObject->isBrowsable(),
|
||||
'invertedDepth' => 1000,
|
||||
];
|
||||
// If the mount is expanded, go down:
|
||||
if ($isOpen && $storageObject->isBrowsable()) {
|
||||
// Set depth:
|
||||
$this->getFolderTree($rootLevelFolder, 999);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the data for the tree
|
||||
*
|
||||
* @param Folder $folderObject the folderobject
|
||||
* @param int $depth Max depth (recursivity limit)
|
||||
* @param string $type HTML-code prefix for recursive calls.
|
||||
*
|
||||
* @return int The count of items on the level
|
||||
* @see getBrowsableTree()
|
||||
*/
|
||||
public function getFolderTree(Folder $folderObject, $depth = 999, $type = '')
|
||||
{
|
||||
$depth = (int)$depth;
|
||||
|
||||
// This generates the directory tree
|
||||
/* array of \TYPO3\CMS\Core\Resource\Folder */
|
||||
if ($folderObject instanceof InaccessibleFolder) {
|
||||
$subFolders = [];
|
||||
} else {
|
||||
$subFolders = $folderObject->getSubfolders();
|
||||
$subFolders = \TYPO3\CMS\Core\Resource\Utility\ListUtility::resolveSpecialFolderNames($subFolders);
|
||||
uksort($subFolders, 'strnatcasecmp');
|
||||
}
|
||||
|
||||
$totalSubFolders = count($subFolders);
|
||||
$HTML = '';
|
||||
$subFolderCounter = 0;
|
||||
$treeKey = '';
|
||||
/** @var Folder $subFolder */
|
||||
foreach ($subFolders as $subFolderName => $subFolder) {
|
||||
$subFolderCounter++;
|
||||
// Reserve space.
|
||||
$this->tree[] = [];
|
||||
// Get the key for this space
|
||||
end($this->tree);
|
||||
$isLocked = $subFolder instanceof InaccessibleFolder;
|
||||
$treeKey = key($this->tree);
|
||||
$specUID = GeneralUtility::md5int($subFolder->getCombinedIdentifier());
|
||||
$this->specUIDmap[$specUID] = $subFolder->getCombinedIdentifier();
|
||||
$row = [
|
||||
'uid' => $specUID,
|
||||
'path' => $subFolder->getCombinedIdentifier(),
|
||||
'title' => $subFolderName,
|
||||
'folder' => $subFolder
|
||||
];
|
||||
// Make a recursive call to the next level
|
||||
if (!$isLocked && $depth > 1 && $this->expandNext($specUID)) {
|
||||
$nextCount = $this->getFolderTree($subFolder, $depth - 1, $type);
|
||||
// Set "did expand" flag
|
||||
$isOpen = 1;
|
||||
} else {
|
||||
$nextCount = $isLocked ? 0 : $this->getNumberOfSubfolders($subFolder);
|
||||
// Clear "did expand" flag
|
||||
$isOpen = 0;
|
||||
}
|
||||
// Set HTML-icons, if any:
|
||||
if ($this->makeHTML) {
|
||||
$HTML = $this->PMicon($subFolder, $subFolderCounter, $totalSubFolders, $nextCount, $isOpen);
|
||||
$type = '';
|
||||
|
||||
$role = $subFolder->getRole();
|
||||
if ($role !== FolderInterface::ROLE_DEFAULT) {
|
||||
$row['_title'] = '<strong>' . $subFolderName . '</strong>';
|
||||
}
|
||||
$icon = '<span title="' . htmlspecialchars($subFolderName) . '">'
|
||||
. $this->iconFactory->getIconForResource($subFolder, Icon::SIZE_SMALL, null, ['folder-open' => (bool)$isOpen])
|
||||
. '</span>';
|
||||
$HTML .= $this->wrapIcon($icon, $subFolder);
|
||||
}
|
||||
// Finally, add the row/HTML content to the ->tree array in the reserved key.
|
||||
$this->tree[$treeKey] = [
|
||||
'row' => $row,
|
||||
'HTML' => $HTML,
|
||||
'hasSub' => $nextCount && $this->expandNext($specUID),
|
||||
'isFirst' => $subFolderCounter == 1,
|
||||
'isLast' => false,
|
||||
'invertedDepth' => $depth,
|
||||
'bank' => $this->bank
|
||||
];
|
||||
}
|
||||
if ($subFolderCounter > 0) {
|
||||
$this->tree[$treeKey]['isLast'] = true;
|
||||
}
|
||||
return $totalSubFolders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the HTML code for displaying the structure found inside the ->tree array
|
||||
*
|
||||
* @param array|string $treeItems "tree-array" - if blank string, the internal ->tree array is used.
|
||||
* @return string The HTML code for the tree
|
||||
*/
|
||||
public function printTree($treeItems = '')
|
||||
{
|
||||
$doExpand = false;
|
||||
$doCollapse = false;
|
||||
$ajaxOutput = '';
|
||||
$titleLength = (int)$this->BE_USER->uc['titleLen'];
|
||||
if (!is_array($treeItems)) {
|
||||
$treeItems = $this->tree;
|
||||
}
|
||||
|
||||
if (empty($treeItems)) {
|
||||
$message = GeneralUtility::makeInstance(
|
||||
FlashMessage::class,
|
||||
$this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang.xlf:foldertreeview.noFolders.message'),
|
||||
$this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang.xlf:foldertreeview.noFolders.title'),
|
||||
FlashMessage::INFO
|
||||
);
|
||||
/** @var \TYPO3\CMS\Core\Messaging\FlashMessageService $flashMessageService */
|
||||
$flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
|
||||
/** @var \TYPO3\CMS\Core\Messaging\FlashMessageQueue $defaultFlashMessageQueue */
|
||||
$defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
|
||||
$defaultFlashMessageQueue->enqueue($message);
|
||||
return $defaultFlashMessageQueue->renderFlashMessages();
|
||||
}
|
||||
|
||||
$expandedFolderHash = '';
|
||||
$invertedDepthOfAjaxRequestedItem = 0;
|
||||
$out = '<ul class="list-tree list-tree-root">';
|
||||
// Evaluate AJAX request
|
||||
if (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_AJAX) {
|
||||
list(, $expandCollapseCommand, $expandedFolderHash, ) = $this->evaluateExpandCollapseParameter();
|
||||
if ($expandCollapseCommand == 1) {
|
||||
$doExpand = true;
|
||||
} else {
|
||||
$doCollapse = true;
|
||||
}
|
||||
}
|
||||
// We need to count the opened <ul>'s every time we dig into another level,
|
||||
// so we know how many we have to close when all children are done rendering
|
||||
$closeDepth = [];
|
||||
foreach ($treeItems as $treeItem) {
|
||||
/** @var Folder $folderObject */
|
||||
$folderObject = $treeItem['row']['folder'];
|
||||
$classAttr = $treeItem['row']['_CSSCLASS'] ?? '';
|
||||
$folderIdentifier = $folderObject->getCombinedIdentifier();
|
||||
// this is set if the AJAX request has just opened this folder (via the PM command)
|
||||
$isExpandedFolderIdentifier = $expandedFolderHash == GeneralUtility::md5int($folderIdentifier);
|
||||
$idAttr = htmlspecialchars($this->domIdPrefix . $this->getId($folderObject) . '_' . $treeItem['bank']);
|
||||
$itemHTML = '';
|
||||
// If this item is the start of a new level,
|
||||
// then a new level <ul> is needed, but not in ajax mode
|
||||
if (!empty($treeItem['isFirst']) && !$doCollapse && !($doExpand && $isExpandedFolderIdentifier)) {
|
||||
$itemHTML = '<ul class="list-tree">';
|
||||
}
|
||||
// Add CSS classes to the list item
|
||||
if (!empty($treeItem['hasSub'])) {
|
||||
$classAttr .= ' list-tree-control-open';
|
||||
}
|
||||
$itemHTML .= '
|
||||
<li id="' . $idAttr . '" ' . ($classAttr ? ' class="' . trim($classAttr) . '"' : '') . '><span class="list-tree-group">' . $treeItem['HTML'] . $this->wrapTitle($this->getTitleStr($treeItem['row'], $titleLength), $folderObject, $treeItem['bank']) . '</span>';
|
||||
if (empty($treeItem['hasSub'])) {
|
||||
$itemHTML .= '</li>';
|
||||
}
|
||||
// We have to remember if this is the last one
|
||||
// on level X so the last child on level X+1 closes the <ul>-tag
|
||||
if (!empty($treeItem['isLast']) && !($doExpand && $isExpandedFolderIdentifier)) {
|
||||
$closeDepth[$treeItem['invertedDepth']] = 1;
|
||||
}
|
||||
// If this is the last one and does not have subitems, we need to close
|
||||
// the tree as long as the upper levels have last items too
|
||||
if (!empty($treeItem['isLast']) && empty($treeItem['hasSub']) && !$doCollapse && !($doExpand && $isExpandedFolderIdentifier)) {
|
||||
for ($i = $treeItem['invertedDepth']; !empty($closeDepth[$i]); $i++) {
|
||||
$closeDepth[$i] = 0;
|
||||
$itemHTML .= '</ul></li>';
|
||||
}
|
||||
}
|
||||
// Ajax request: collapse
|
||||
if ($doCollapse && $isExpandedFolderIdentifier) {
|
||||
$this->ajaxStatus = true;
|
||||
return $itemHTML;
|
||||
}
|
||||
// Ajax request: expand
|
||||
if ($doExpand && $isExpandedFolderIdentifier) {
|
||||
$ajaxOutput .= $itemHTML;
|
||||
$invertedDepthOfAjaxRequestedItem = $treeItem['invertedDepth'];
|
||||
} elseif ($invertedDepthOfAjaxRequestedItem) {
|
||||
if ($treeItem['invertedDepth'] && ($treeItem['invertedDepth'] < $invertedDepthOfAjaxRequestedItem)) {
|
||||
$ajaxOutput .= $itemHTML;
|
||||
} else {
|
||||
$this->ajaxStatus = true;
|
||||
return $ajaxOutput;
|
||||
}
|
||||
}
|
||||
$out .= $itemHTML;
|
||||
}
|
||||
// If this is an AJAX request, output directly
|
||||
if ($ajaxOutput) {
|
||||
$this->ajaxStatus = true;
|
||||
return $ajaxOutput;
|
||||
}
|
||||
// Finally close the first ul
|
||||
$out .= '</ul>';
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns table name for click menu
|
||||
*
|
||||
* @param Folder $folderObject
|
||||
* @return string
|
||||
*/
|
||||
protected function getTableNameForClickMenu(Folder $folderObject)
|
||||
{
|
||||
if (strpos($folderObject->getRole(), FolderInterface::ROLE_MOUNT) !== false) {
|
||||
$tableName = 'sys_filemounts';
|
||||
} elseif ($folderObject->getIdentifier() === $folderObject->getStorage()->getRootLevelFolder()->getIdentifier()) {
|
||||
$tableName = 'sys_file_storage';
|
||||
} else {
|
||||
$tableName = 'sys_file';
|
||||
}
|
||||
return $tableName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of directories in a file path.
|
||||
*
|
||||
* @param Folder $folderObject File path.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getNumberOfSubfolders(Folder $folderObject)
|
||||
{
|
||||
$subFolders = $folderObject->getSubfolders();
|
||||
return count($subFolders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stored tree structure AND updating it if needed according to incoming PM GET var.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function initializePositionSaving()
|
||||
{
|
||||
// Get stored tree structure:
|
||||
$this->stored = unserialize($this->BE_USER->uc['browseTrees'][$this->treeName], ['allowed_classes' => false]);
|
||||
$this->getShortHashNumberForStorage();
|
||||
// PM action:
|
||||
// (If an plus/minus icon has been clicked,
|
||||
// the PM GET var is sent and we must update the stored positions in the tree):
|
||||
// 0: mount key, 1: set/clear boolean, 2: item ID (cannot contain "_"), 3: treeName
|
||||
list($storageHashNumber, $doExpand, $numericFolderHash, $treeName) = $this->evaluateExpandCollapseParameter();
|
||||
if ($treeName && $treeName == $this->treeName) {
|
||||
if (in_array($storageHashNumber, $this->storageHashNumbers)) {
|
||||
if ($doExpand == 1) {
|
||||
// Set
|
||||
$this->stored[$storageHashNumber][$numericFolderHash] = 1;
|
||||
} else {
|
||||
// Clear
|
||||
unset($this->stored[$storageHashNumber][$numericFolderHash]);
|
||||
}
|
||||
$this->savePosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to map md5-hash to shorter number
|
||||
*
|
||||
* @param ResourceStorage $storageObject
|
||||
* @param Folder $startingPointFolder
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function getShortHashNumberForStorage(ResourceStorage $storageObject = null, Folder $startingPointFolder = null)
|
||||
{
|
||||
if (!$this->storageHashNumbers) {
|
||||
$this->storageHashNumbers = [];
|
||||
foreach ($this->storages as $storageUid => $storage) {
|
||||
$fileMounts = $storage->getFileMounts();
|
||||
if (!empty($fileMounts)) {
|
||||
foreach ($fileMounts as $fileMount) {
|
||||
$nkey = hexdec(substr(GeneralUtility::md5int($fileMount['folder']->getCombinedIdentifier()), 0, 4));
|
||||
$this->storageHashNumbers[$storageUid . $fileMount['folder']->getCombinedIdentifier()] = $nkey;
|
||||
}
|
||||
} else {
|
||||
$folder = $storage->getRootLevelFolder();
|
||||
$nkey = hexdec(substr(GeneralUtility::md5int($folder->getCombinedIdentifier()), 0, 4));
|
||||
$this->storageHashNumbers[$storageUid . $folder->getCombinedIdentifier()] = $nkey;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($storageObject) {
|
||||
if ($startingPointFolder) {
|
||||
return $this->storageHashNumbers[$storageObject->getUid() . $startingPointFolder->getCombinedIdentifier()];
|
||||
}
|
||||
return $this->storageHashNumbers[$storageObject->getUid()];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the values from the Expand/Collapse Parameter (&PM)
|
||||
* previously known as "PM" (plus/minus)
|
||||
* PM action:
|
||||
* (If an plus/minus icon has been clicked,
|
||||
* the PM GET var is sent and we must update the stored positions in the tree):
|
||||
* 0: mount key, 1: set/clear boolean, 2: item ID (cannot contain "_"), 3: treeName
|
||||
*
|
||||
* @param string $PM The "plus/minus" command
|
||||
* @return array
|
||||
*/
|
||||
protected function evaluateExpandCollapseParameter($PM = null)
|
||||
{
|
||||
if ($PM === null) {
|
||||
$PM = GeneralUtility::_GP('PM');
|
||||
// IE takes anchor as parameter
|
||||
if (($PMpos = strpos($PM, '#')) !== false) {
|
||||
$PM = substr($PM, 0, $PMpos);
|
||||
}
|
||||
}
|
||||
// Take the first three parameters
|
||||
list($mountKey, $doExpand, $folderIdentifier) = array_pad(explode('_', $PM, 3), 3, null);
|
||||
// In case the folder identifier contains "_", we just need to get the fourth/last parameter
|
||||
list($folderIdentifier, $treeName) = array_pad(GeneralUtility::revExplode('_', $folderIdentifier, 2), 2, null);
|
||||
return [
|
||||
$mountKey,
|
||||
$doExpand,
|
||||
$folderIdentifier,
|
||||
$treeName
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the "PM" string to sent to expand/collapse items
|
||||
*
|
||||
* @param string $mountKey The mount key / storage UID
|
||||
* @param bool $doExpand Whether to expand/collapse
|
||||
* @param Folder $folderObject The folder object
|
||||
* @param string $treeName The name of the tree
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function generateExpandCollapseParameter($mountKey = null, $doExpand = false, Folder $folderObject = null, $treeName = null)
|
||||
{
|
||||
$parts = [
|
||||
$mountKey ?? $this->bank,
|
||||
$doExpand == 1 ? 1 : 0,
|
||||
$folderObject !== null ? GeneralUtility::md5int($folderObject->getCombinedIdentifier()) : '',
|
||||
$treeName ?? $this->treeName
|
||||
];
|
||||
return implode('_', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the AJAX status.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getAjaxStatus()
|
||||
{
|
||||
return $this->ajaxStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Tree\View;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Extension for the tree class that generates the tree of pages in the page-wizard mode
|
||||
*
|
||||
* @see \TYPO3\CMS\Backend\Controller\NewRecordController
|
||||
* @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class NewRecordPageTreeView extends PageTreeView
|
||||
{
|
||||
/**
|
||||
* Determines whether to expand a branch or not.
|
||||
* Here the branch is expanded if the current id matches the global id for the listing/new
|
||||
*
|
||||
* @param int $id The ID (page id) of the element
|
||||
* @return bool Returns TRUE if the IDs matches
|
||||
*/
|
||||
public function expandNext($id)
|
||||
{
|
||||
return $id == $GLOBALS['SOBE']->id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Tree\View;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Position map class for moving pages,
|
||||
* @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class PageMovingPagePositionMap extends PagePositionMap
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $l_insertNewPageHere = 'movePageToHere';
|
||||
|
||||
/**
|
||||
* Page tree implementation class name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $pageTreeClassName = PageTreeView::class;
|
||||
|
||||
/**
|
||||
* Creates the onclick event for the insert-icons.
|
||||
*
|
||||
* @param int $pid The pid.
|
||||
* @param int $newPagePID New page id.
|
||||
* @return string Onclick attribute content
|
||||
*/
|
||||
public function onClickEvent($pid, $newPagePID)
|
||||
{
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
return 'window.location.href=' . GeneralUtility::quoteJSvalue((string)$uriBuilder->buildUriFromRoute('tce_db', [
|
||||
'cmd[pages][' . $this->moveUid . '][' . $this->moveOrCopy . ']' => $pid,
|
||||
'redirect' => $this->R_URI,
|
||||
])) . ';return false;';
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapping page title.
|
||||
*
|
||||
* @param string $str Page title.
|
||||
* @param array $rec Page record (?)
|
||||
* @return string Wrapped title.
|
||||
*/
|
||||
public function linkPageTitle($str, $rec)
|
||||
{
|
||||
$url = GeneralUtility::linkThisScript(['uid' => (int)$rec['uid'], 'moveUid' => $this->moveUid]);
|
||||
return '<a href="' . htmlspecialchars($url) . '">' . $str . '</a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap $t_code in bold IF the $dat uid matches $id
|
||||
*
|
||||
* @param string $t_code Title string
|
||||
* @param array $dat Information array with record array inside.
|
||||
* @param int $id The current id.
|
||||
* @return string The title string.
|
||||
*/
|
||||
public function boldTitle($t_code, $dat, $id)
|
||||
{
|
||||
return parent::boldTitle($t_code, $dat, $this->moveUid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,663 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Tree\View;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Compatibility\PublicPropertyDeprecationTrait;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Position map class - generating a page tree / content element list which links for inserting (copy/move) of records.
|
||||
* Used for pages / tt_content element wizards of various kinds.
|
||||
* @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class PagePositionMap
|
||||
{
|
||||
use PublicPropertyDeprecationTrait;
|
||||
|
||||
/**
|
||||
* Properties which have been moved to protected status from public
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $deprecatedPublicProperties = [
|
||||
'getModConfigCache' => 'Using $getModConfigCache of class PagePositionMap is discouraged. This property will be removed in TYPO3 v10.0.',
|
||||
'modConfigStr' => 'Using $$modConfigStr of class PagePositionMap is discouraged. This property will be removed in TYPO3 v10.0.',
|
||||
];
|
||||
|
||||
// EXTERNAL, static:
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $moveOrCopy = 'move';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $dontPrintPageInsertIcons = 0;
|
||||
|
||||
// How deep the position page tree will go.
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $depth = 2;
|
||||
|
||||
// Can be set to the sys_language uid to select content elements for.
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $cur_sys_language;
|
||||
|
||||
// INTERNAL, dynamic:
|
||||
// Request uri
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $R_URI = '';
|
||||
|
||||
// Element id.
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $elUid = '';
|
||||
|
||||
// tt_content element uid to move.
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $moveUid = '';
|
||||
|
||||
// Caching arrays:
|
||||
/**
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
protected $getModConfigCache = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $checkNewPageCache = [];
|
||||
|
||||
// Label keys:
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $l_insertNewPageHere = 'insertNewPageHere';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $l_insertNewRecordHere = 'insertNewRecordHere';
|
||||
|
||||
/**
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
protected $modConfigStr = 'mod.web_list.newPageWiz';
|
||||
|
||||
/**
|
||||
* Page tree implementation class name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $pageTreeClassName = ElementBrowserPageTreeView::class;
|
||||
|
||||
/**
|
||||
* @var IconFactory
|
||||
*/
|
||||
protected $iconFactory;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $clientContext;
|
||||
|
||||
/**
|
||||
* Constructor allowing to set pageTreeImplementation
|
||||
*
|
||||
* @param string $pageTreeClassName
|
||||
* @param string $clientContext JavaScript context of view client (either 'window' or 'list_frame')
|
||||
*/
|
||||
public function __construct(string $pageTreeClassName = null, string $clientContext = 'window')
|
||||
{
|
||||
if ($pageTreeClassName !== null) {
|
||||
$this->pageTreeClassName = $pageTreeClassName;
|
||||
}
|
||||
$this->clientContext = $clientContext;
|
||||
$this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
}
|
||||
|
||||
/*************************************
|
||||
*
|
||||
* Page position map:
|
||||
*
|
||||
**************************************/
|
||||
/**
|
||||
* Creates a "position tree" based on the page tree.
|
||||
*
|
||||
* @param int $id Current page id
|
||||
* @param array $pageinfo Current page record.
|
||||
* @param string $perms_clause Page selection permission clause.
|
||||
* @param string $R_URI Current REQUEST_URI
|
||||
* @return string HTML code for the tree.
|
||||
*/
|
||||
public function positionTree($id, $pageinfo, $perms_clause, $R_URI)
|
||||
{
|
||||
// Make page tree object:
|
||||
/** @var \TYPO3\CMS\Backend\Tree\View\PageTreeView $pageTree */
|
||||
$pageTree = GeneralUtility::makeInstance($this->pageTreeClassName);
|
||||
$pageTree->init(' AND ' . $perms_clause);
|
||||
$pageTree->addField('pid');
|
||||
// Initialize variables:
|
||||
$this->R_URI = $R_URI;
|
||||
$this->elUid = $id;
|
||||
// Create page tree, in $this->depth levels.
|
||||
$pageTree->getTree($pageinfo['pid'], $this->depth);
|
||||
// Initialize variables:
|
||||
$saveLatestUid = [];
|
||||
$latestInvDepth = $this->depth;
|
||||
// Traverse the tree:
|
||||
$lines = [];
|
||||
foreach ($pageTree->tree as $cc => $dat) {
|
||||
if ($latestInvDepth > $dat['invertedDepth']) {
|
||||
$margin = 'style="margin-left: ' . ($dat['invertedDepth'] * 16 + 9) . 'px;"';
|
||||
$lines[] = '<ul class="list-tree" ' . $margin . '>';
|
||||
}
|
||||
// Make link + parameters.
|
||||
$latestInvDepth = $dat['invertedDepth'];
|
||||
$saveLatestUid[$latestInvDepth] = $dat;
|
||||
if (isset($pageTree->tree[$cc - 1])) {
|
||||
$prev_dat = $pageTree->tree[$cc - 1];
|
||||
// If current page, subpage?
|
||||
if ($prev_dat['row']['uid'] == $id) {
|
||||
// 1) It must be allowed to create a new page and 2) If there are subpages there is no need to render a subpage icon here - it'll be done over the subpages...
|
||||
if (!$this->dontPrintPageInsertIcons && $this->checkNewPageInPid($id) && !($prev_dat['invertedDepth'] > $pageTree->tree[$cc]['invertedDepth'])) {
|
||||
end($lines);
|
||||
$margin = 'style="margin-left: ' . (($dat['invertedDepth'] - 1) * 16 + 9) . 'px;"';
|
||||
$lines[] = '<ul class="list-tree" ' . $margin . '><li><span class="text-nowrap"><a href="#" onclick="' . htmlspecialchars($this->onClickEvent($id, $id)) . '"><i class="t3-icon fa fa-long-arrow-left" title="' . $this->insertlabel() . '"></i></a></span></li></ul>';
|
||||
}
|
||||
}
|
||||
// If going down
|
||||
if ($prev_dat['invertedDepth'] > $pageTree->tree[$cc]['invertedDepth']) {
|
||||
$prevPid = $pageTree->tree[$cc]['row']['pid'];
|
||||
} elseif ($prev_dat['invertedDepth'] < $pageTree->tree[$cc]['invertedDepth']) {
|
||||
// If going up
|
||||
// First of all the previous level should have an icon:
|
||||
if (!$this->dontPrintPageInsertIcons && $this->checkNewPageInPid($prev_dat['row']['pid'])) {
|
||||
$prevPid = -$prev_dat['row']['uid'];
|
||||
end($lines);
|
||||
$lines[] = '<li><span class="text-nowrap"><a href="#" onclick="' . htmlspecialchars($this->onClickEvent($prevPid, $prev_dat['row']['pid'])) . '"><i class="t3-icon fa fa-long-arrow-left" title="' . $this->insertlabel() . '"></i></a></span></li>';
|
||||
}
|
||||
// Then set the current prevPid
|
||||
$prevPid = -$prev_dat['row']['pid'];
|
||||
if ($prevPid !== $dat['row']['pid']) {
|
||||
$lines[] = '</ul>';
|
||||
}
|
||||
} else {
|
||||
// In on the same level
|
||||
$prevPid = -$prev_dat['row']['uid'];
|
||||
}
|
||||
} else {
|
||||
// First in the tree
|
||||
$prevPid = $dat['row']['pid'];
|
||||
}
|
||||
// print arrow on the same level
|
||||
if (!$this->dontPrintPageInsertIcons && $this->checkNewPageInPid($dat['row']['pid'])) {
|
||||
$lines[] = '<span class="text-nowrap"><a href="#" onclick="' . htmlspecialchars($this->onClickEvent($prevPid, $dat['row']['pid'])) . '"><i class="t3-icon fa fa-long-arrow-left" title="' . $this->insertlabel() . '"></i></a></span>';
|
||||
}
|
||||
// The line with the icon and title:
|
||||
$toolTip = BackendUtility::getRecordToolTip($dat['row'], 'pages');
|
||||
$icon = '<span ' . $toolTip . '>' . $this->iconFactory->getIconForRecord('pages', $dat['row'], Icon::SIZE_SMALL)->render() . '</span>';
|
||||
|
||||
$lines[] = '<span class="text-nowrap">' . $icon . $this->linkPageTitle($this->boldTitle(htmlspecialchars(GeneralUtility::fixed_lgd_cs($dat['row']['title'], $this->getBackendUser()->uc['titleLen'])), $dat, $id), $dat['row']) . '</span>';
|
||||
}
|
||||
// If the current page was the last in the tree:
|
||||
$prev_dat = end($pageTree->tree);
|
||||
if ($prev_dat['row']['uid'] == $id) {
|
||||
if (!$this->dontPrintPageInsertIcons && $this->checkNewPageInPid($id)) {
|
||||
$lines[] = '<ul class="list-tree" style="margin-left: 25px"><li><span class="text-nowrap"><a href="#" onclick="' . htmlspecialchars($this->onClickEvent($id, $id)) . '"><i class="t3-icon fa fa-long-arrow-left" title="' . $this->insertlabel() . '"></i></a></span></li></ul>';
|
||||
}
|
||||
}
|
||||
for ($a = $latestInvDepth; $a <= $this->depth; $a++) {
|
||||
$dat = $saveLatestUid[$a];
|
||||
$prevPid = -$dat['row']['uid'];
|
||||
if (!$this->dontPrintPageInsertIcons && $this->checkNewPageInPid($dat['row']['pid'])) {
|
||||
if ($latestInvDepth < $dat['invertedDepth']) {
|
||||
$lines[] = '</ul>';
|
||||
}
|
||||
$lines[] = '<span class="text-nowrap"><a href="#" onclick="' . htmlspecialchars($this->onClickEvent($prevPid, $dat['row']['pid'])) . '"><i class="t3-icon fa fa-long-arrow-left" title="' . $this->insertlabel() . '"></i></a></span>';
|
||||
}
|
||||
}
|
||||
|
||||
$code = '<ul class="list-tree">';
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if ((strpos($line, '<ul') === 0) || (strpos($line, '</ul') === 0)) {
|
||||
$code .= $line;
|
||||
} else {
|
||||
$code .= '<li>' . $line . '</li>';
|
||||
}
|
||||
}
|
||||
|
||||
$code .= '</ul>';
|
||||
return $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap $t_code in bold IF the $dat uid matches $id
|
||||
*
|
||||
* @param string $t_code Title string
|
||||
* @param array $dat Information array with record array inside.
|
||||
* @param int $id The current id.
|
||||
* @return string The title string.
|
||||
*/
|
||||
public function boldTitle($t_code, $dat, $id)
|
||||
{
|
||||
if ($dat['row']['uid'] == $id) {
|
||||
$t_code = '<strong>' . $t_code . '</strong>';
|
||||
}
|
||||
return $t_code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the onclick event for the insert-icons.
|
||||
*
|
||||
* TSconfig mod.newPageWizard.override may contain an alternative module / route which can be
|
||||
* used instead of the normal create new page wizard.
|
||||
*
|
||||
* @param int $pid The pid.
|
||||
* @param int $newPagePID New page id.
|
||||
* @return string Onclick attribute content
|
||||
*/
|
||||
public function onClickEvent($pid, $newPagePID)
|
||||
{
|
||||
$TSconfig = BackendUtility::getPagesTSconfig($newPagePID)['mod.']['newPageWizard.'] ?? [];
|
||||
if (isset($TSconfig['override']) && !empty($TSconfig['override'])) {
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
$url = $uriBuilder->buildUriFromRoute(
|
||||
$TSconfig['override'],
|
||||
[
|
||||
'positionPid' => $pid,
|
||||
'newPageId' => $newPagePID,
|
||||
'cmd' => 'crPage',
|
||||
'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
|
||||
]
|
||||
);
|
||||
return $this->clientContext . '.location.href=' . GeneralUtility::quoteJSvalue((string)$url) . ';';
|
||||
}
|
||||
$params = '&edit[pages][' . $pid . ']=new&returnNewPageId=1';
|
||||
return BackendUtility::editOnClick($params, '', $this->R_URI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get label, htmlspecialchars()'ed
|
||||
*
|
||||
* @return string The localized label for "insert new page here
|
||||
*/
|
||||
public function insertlabel()
|
||||
{
|
||||
return htmlspecialchars($this->getLanguageService()->getLL($this->l_insertNewPageHere));
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapping page title.
|
||||
*
|
||||
* @param string $str Page title.
|
||||
* @param array $rec Page record (?)
|
||||
* @return string Wrapped title.
|
||||
*/
|
||||
public function linkPageTitle($str, $rec)
|
||||
{
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user has permission to created pages inside of the $pid page.
|
||||
* Uses caching so only one regular lookup is made - hence you can call the function multiple times without worrying about performance.
|
||||
*
|
||||
* @param int $pid Page id for which to test.
|
||||
* @return bool
|
||||
*/
|
||||
public function checkNewPageInPid($pid)
|
||||
{
|
||||
if (!isset($this->checkNewPageCache[$pid])) {
|
||||
$pidInfo = BackendUtility::getRecord('pages', $pid);
|
||||
$this->checkNewPageCache[$pid] = $this->getBackendUser()->isAdmin() || $this->getBackendUser()->doesUserHaveAccess($pidInfo, 8);
|
||||
}
|
||||
return $this->checkNewPageCache[$pid];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns module configuration for a pid.
|
||||
*
|
||||
* @param int $pid Page id for which to get the module configuration.
|
||||
* @return array The properties of the module configuration for the page id.
|
||||
* @see onClickEvent()
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function getModConfig($pid)
|
||||
{
|
||||
trigger_error('PagePositionMap->getModConfig() will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
|
||||
if (!isset($this->getModConfigCache[$pid])) {
|
||||
// Acquiring TSconfig for this PID:
|
||||
$this->getModConfigCache[$pid]['properties'] = BackendUtility::getPagesTSconfig($pid)['mod.']['web_list.']['newPageWiz.'] ?? [];
|
||||
}
|
||||
return $this->getModConfigCache[$pid]['properties'];
|
||||
}
|
||||
|
||||
/*************************************
|
||||
*
|
||||
* Content element positioning:
|
||||
*
|
||||
**************************************/
|
||||
/**
|
||||
* Creates HTML for inserting/moving content elements.
|
||||
*
|
||||
* @param int $pid page id onto which to insert content element.
|
||||
* @param int $moveUid Move-uid (tt_content element uid?)
|
||||
* @param string $colPosList List of columns to show
|
||||
* @param bool $showHidden If not set, then hidden/starttime/endtime records are filtered out.
|
||||
* @param string $R_URI Request URI
|
||||
* @return string HTML
|
||||
*/
|
||||
public function printContentElementColumns($pid, $moveUid, $colPosList, $showHidden, $R_URI)
|
||||
{
|
||||
$this->R_URI = $R_URI;
|
||||
$this->moveUid = $moveUid;
|
||||
$colPosArray = GeneralUtility::trimExplode(',', $colPosList, true);
|
||||
$lines = [];
|
||||
foreach ($colPosArray as $kk => $vv) {
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
|
||||
$queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
|
||||
if ($showHidden) {
|
||||
$queryBuilder->getRestrictions()
|
||||
->removeByType(HiddenRestriction::class)
|
||||
->removeByType(StartTimeRestriction::class)
|
||||
->removeByType(EndTimeRestriction::class);
|
||||
}
|
||||
$queryBuilder
|
||||
->select('*')
|
||||
->from('tt_content')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)),
|
||||
$queryBuilder->expr()->eq('colPos', $queryBuilder->createNamedParameter($vv, \PDO::PARAM_INT))
|
||||
)
|
||||
->orderBy('sorting');
|
||||
|
||||
if ((string)$this->cur_sys_language !== '') {
|
||||
$queryBuilder->andWhere(
|
||||
$queryBuilder->expr()->eq(
|
||||
'sys_language_uid',
|
||||
$queryBuilder->createNamedParameter($this->cur_sys_language, \PDO::PARAM_INT)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$res = $queryBuilder->execute();
|
||||
$lines[$vv] = [];
|
||||
$lines[$vv][] = $this->insertPositionIcon('', $vv, $kk, $moveUid, $pid);
|
||||
|
||||
while ($row = $res->fetch()) {
|
||||
BackendUtility::workspaceOL('tt_content', $row);
|
||||
if (is_array($row)) {
|
||||
$lines[$vv][] = $this->wrapRecordHeader($this->getRecordHeader($row), $row);
|
||||
$lines[$vv][] = $this->insertPositionIcon($row, $vv, $kk, $moveUid, $pid);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->printRecordMap($lines, $colPosArray, $pid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the table with the content columns
|
||||
*
|
||||
* @param array $lines Array with arrays of lines for each column
|
||||
* @param array $colPosArray Column position array
|
||||
* @param int $pid The id of the page
|
||||
* @return string HTML
|
||||
*/
|
||||
public function printRecordMap($lines, $colPosArray, $pid = 0)
|
||||
{
|
||||
$count = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange(count($colPosArray), 1);
|
||||
$backendLayout = GeneralUtility::callUserFunction(\TYPO3\CMS\Backend\View\BackendLayoutView::class . '->getSelectedBackendLayout', $pid, $this);
|
||||
if (isset($backendLayout['__config']['backend_layout.'])) {
|
||||
$this->getLanguageService()->includeLLFile('EXT:backend/Resources/Private/Language/locallang_layout.xlf');
|
||||
$table = '<div class="table-fit"><table class="table table-condensed table-bordered table-vertical-top">';
|
||||
$colCount = (int)$backendLayout['__config']['backend_layout.']['colCount'];
|
||||
$rowCount = (int)$backendLayout['__config']['backend_layout.']['rowCount'];
|
||||
$table .= '<colgroup>';
|
||||
for ($i = 0; $i < $colCount; $i++) {
|
||||
$table .= '<col style="width:' . 100 / $colCount . '%"></col>';
|
||||
}
|
||||
$table .= '</colgroup>';
|
||||
$table .= '<tbody>';
|
||||
$tcaItems = GeneralUtility::callUserFunction(\TYPO3\CMS\Backend\View\BackendLayoutView::class . '->getColPosListItemsParsed', $pid, $this);
|
||||
// Cycle through rows
|
||||
for ($row = 1; $row <= $rowCount; $row++) {
|
||||
$rowConfig = $backendLayout['__config']['backend_layout.']['rows.'][$row . '.'];
|
||||
if (!isset($rowConfig)) {
|
||||
continue;
|
||||
}
|
||||
$table .= '<tr>';
|
||||
for ($col = 1; $col <= $colCount; $col++) {
|
||||
$columnConfig = $rowConfig['columns.'][$col . '.'];
|
||||
if (!isset($columnConfig)) {
|
||||
continue;
|
||||
}
|
||||
// Which tt_content colPos should be displayed inside this cell
|
||||
$columnKey = (int)$columnConfig['colPos'];
|
||||
$head = '';
|
||||
foreach ($tcaItems as $item) {
|
||||
if ($item[1] == $columnKey) {
|
||||
$head = htmlspecialchars($this->getLanguageService()->sL($item[0]));
|
||||
}
|
||||
}
|
||||
// Render the grid cell
|
||||
$table .= '<td'
|
||||
. (isset($columnConfig['colspan']) ? ' colspan="' . $columnConfig['colspan'] . '"' : '')
|
||||
. (isset($columnConfig['rowspan']) ? ' rowspan="' . $columnConfig['rowspan'] . '"' : '')
|
||||
. ' class="col-nowrap col-min'
|
||||
. (!isset($columnConfig['colPos']) ? ' warning' : '')
|
||||
. (isset($columnConfig['colPos']) && !$head ? ' danger' : '') . '">';
|
||||
// Render header
|
||||
$table .= '<p>';
|
||||
if (isset($columnConfig['colPos']) && $head) {
|
||||
$table .= '<strong>' . $this->wrapColumnHeader($head, '') . '</strong>';
|
||||
} elseif ($columnConfig['colPos']) {
|
||||
$table .= '<em>' . $this->wrapColumnHeader($this->getLanguageService()->getLL('noAccess'), '') . '</em>';
|
||||
} else {
|
||||
$table .= '<em>' . $this->wrapColumnHeader(($this->getLanguageService()->sL($columnConfig['name']) ?: '') . ' (' . $this->getLanguageService()->getLL('notAssigned') . ')', '') . '</em>';
|
||||
}
|
||||
$table .= '</p>';
|
||||
// Render lines
|
||||
if (isset($columnConfig['colPos']) && $head && !empty($lines[$columnKey])) {
|
||||
$table .= '<ul class="list-unstyled">';
|
||||
foreach ($lines[$columnKey] as $line) {
|
||||
$table .= '<li>' . $line . '</li>';
|
||||
}
|
||||
$table .= '</ul>';
|
||||
}
|
||||
$table .= '</td>';
|
||||
}
|
||||
$table .= '</tr>';
|
||||
}
|
||||
$table .= '</tbody>';
|
||||
$table .= '</table></div>';
|
||||
} else {
|
||||
// Traverse the columns here:
|
||||
$row = '';
|
||||
foreach ($colPosArray as $kk => $vv) {
|
||||
$row .= '<td class="col-nowrap col-min" width="' . round(100 / $count) . '%">';
|
||||
$row .= '<p><strong>' . $this->wrapColumnHeader(htmlspecialchars($this->getLanguageService()->sL(BackendUtility::getLabelFromItemlist('tt_content', 'colPos', $vv))), $vv) . '</strong></p>';
|
||||
if (!empty($lines[$vv])) {
|
||||
$row .= '<ul class="list-unstyled">';
|
||||
foreach ($lines[$vv] as $line) {
|
||||
$row .= '<li>' . $line . '</li>';
|
||||
}
|
||||
$row .= '</ul>';
|
||||
}
|
||||
$row .= '</td>';
|
||||
}
|
||||
$table = '
|
||||
|
||||
<!--
|
||||
Map of records in columns:
|
||||
-->
|
||||
<div class="table-fit">
|
||||
<table class="table table-condensed table-bordered table-vertical-top">
|
||||
<tr>' . $row . '</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
';
|
||||
}
|
||||
return $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapping the column header
|
||||
*
|
||||
* @param string $str Header value
|
||||
* @param string $vv Column info.
|
||||
* @return string
|
||||
* @see printRecordMap()
|
||||
*/
|
||||
public function wrapColumnHeader($str, $vv)
|
||||
{
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a linked position icon.
|
||||
*
|
||||
* @param mixed $row Element row. If this is an array the link will cause an insert after this content element, otherwise
|
||||
* the link will insert at the first position in the column
|
||||
* @param string $vv Column position value.
|
||||
* @param int $kk Column key.
|
||||
* @param int $moveUid Move uid
|
||||
* @param int $pid PID value.
|
||||
* @return string
|
||||
*/
|
||||
public function insertPositionIcon($row, $vv, $kk, $moveUid, $pid)
|
||||
{
|
||||
if (is_array($row) && !empty($row['uid'])) {
|
||||
// Use record uid for the hash when inserting after this content element
|
||||
$uid = $row['uid'];
|
||||
} else {
|
||||
// No uid means insert at first position in the column
|
||||
$uid = '';
|
||||
}
|
||||
$cc = hexdec(substr(md5($uid . '-' . $vv . '-' . $kk), 0, 4));
|
||||
return '<a href="#" onclick="' . htmlspecialchars($this->onClickInsertRecord($row, $vv, $moveUid, $pid, $this->cur_sys_language)) . '" data-dismiss="modal">' . '<i class="t3-icon fa fa-long-arrow-left" name="mImgEnd' . $cc . '" title="' . htmlspecialchars($this->getLanguageService()->getLL($this->l_insertNewRecordHere)) . '"></i></a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create on-click event value.
|
||||
*
|
||||
* @param mixed $row The record. If this is not an array with the record data the insert will be for the first position
|
||||
* in the column
|
||||
* @param string $vv Column position value.
|
||||
* @param int $moveUid Move uid
|
||||
* @param int $pid PID value.
|
||||
* @param int $sys_lang System language (not used currently)
|
||||
* @return string
|
||||
*/
|
||||
public function onClickInsertRecord($row, $vv, $moveUid, $pid, $sys_lang = 0)
|
||||
{
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
if (is_array($row)) {
|
||||
$location = $uriBuilder->buildUriFromRoute('tce_db', [
|
||||
'cmd[tt_content][' . $moveUid . '][' . $this->moveOrCopy . ']' => '-' . $row['uid'],
|
||||
'redirect' => $this->R_URI,
|
||||
]);
|
||||
} else {
|
||||
$location = $uriBuilder->buildUriFromRoute('tce_db', [
|
||||
'cmd[tt_content][' . $moveUid . '][' . $this->moveOrCopy . ']' => $pid,
|
||||
'data[tt_content][' . $moveUid . '][colPos]' => $vv,
|
||||
'redirect' => $this->R_URI,
|
||||
]);
|
||||
}
|
||||
// returns to prev. page
|
||||
return $this->clientContext . '.location.href=' . GeneralUtility::quoteJSvalue((string)$location) . ';return false;';
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapping the record header (from getRecordHeader())
|
||||
*
|
||||
* @param string $str HTML content
|
||||
* @param string $row Record array.
|
||||
* @return string HTML content
|
||||
*/
|
||||
public function wrapRecordHeader($str, $row)
|
||||
{
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create record header (includes the record icon, record title etc.)
|
||||
*
|
||||
* @param array $row Record row.
|
||||
* @return string HTML
|
||||
*/
|
||||
public function getRecordHeader($row)
|
||||
{
|
||||
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
$toolTip = BackendUtility::getRecordToolTip($row, 'tt_content');
|
||||
$line = '<span ' . $toolTip . ' title="' . BackendUtility::getRecordIconAltText($row, 'tt_content') . '">' . $iconFactory->getIconForRecord('tt_content', $row, Icon::SIZE_SMALL)->render() . '</span>';
|
||||
$line .= BackendUtility::getRecordTitle('tt_content', $row, true);
|
||||
return $this->wrapRecordTitle($line, $row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapping the title of the record.
|
||||
*
|
||||
* @param string $str The title value.
|
||||
* @param array $row The record row.
|
||||
* @return string Wrapped title string.
|
||||
*/
|
||||
public function wrapRecordTitle($str, $row)
|
||||
{
|
||||
return '<a href="' . htmlspecialchars(GeneralUtility::linkThisScript(['uid' => (int)$row['uid'], 'moveUid' => ''])) . '">' . $str . '</a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the BackendUser
|
||||
*
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the LanguageService
|
||||
*
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Tree\View;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Generate a page-tree, non-browsable.
|
||||
*/
|
||||
class PageTreeView extends AbstractTreeView
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $fieldArray = [
|
||||
'uid',
|
||||
'pid',
|
||||
'is_siteroot',
|
||||
'title',
|
||||
'doktype',
|
||||
'nav_title',
|
||||
'mount_pid',
|
||||
'php_tree_stop',
|
||||
't3ver_id',
|
||||
't3ver_state',
|
||||
'hidden',
|
||||
'starttime',
|
||||
'endtime',
|
||||
'fe_group',
|
||||
'module',
|
||||
'extendToSubpages',
|
||||
'nav_hide'
|
||||
];
|
||||
|
||||
/**
|
||||
* override to use this treeName
|
||||
* @var string
|
||||
*/
|
||||
public $treeName = 'pages';
|
||||
|
||||
/**
|
||||
* override to use this table
|
||||
* @var string
|
||||
*/
|
||||
public $table = 'pages';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $ext_showNavTitle = false;
|
||||
|
||||
/**
|
||||
* Init function
|
||||
* REMEMBER to feed a $clause which will filter out non-readable pages!
|
||||
*
|
||||
* @param string $clause Part of where query which will filter out non-readable pages.
|
||||
* @param string $orderByFields Record ORDER BY field
|
||||
*/
|
||||
public function init($clause = '', $orderByFields = '')
|
||||
{
|
||||
parent::init(' AND deleted=0 AND sys_language_uid=0 ' . $clause, 'sorting');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns TRUE/FALSE if the next level for $id should be expanded - and all levels should, so we always return 1.
|
||||
*
|
||||
* @param int $id ID (uid) to test for (see extending classes where this is checked against session data)
|
||||
* @return bool
|
||||
*/
|
||||
public function expandNext($id)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the plus/minus icon for the browsable tree.
|
||||
* In this case, there is no plus-minus icon displayed.
|
||||
*
|
||||
* @param array $row Record for the entry
|
||||
* @param int $a The current entry number
|
||||
* @param int $c The total number of entries. If equal to $a, a 'bottom' element is returned.
|
||||
* @param int $nextCount The number of sub-elements to the current element.
|
||||
* @param bool $isExpand The element was expanded to render subelements if this flag is set.
|
||||
* @return string Image tag with the plus/minus icon.
|
||||
* @internal
|
||||
* @see AbstractTreeView::PMicon()
|
||||
*/
|
||||
public function PMicon($row, $a, $c, $nextCount, $isExpand)
|
||||
{
|
||||
return '<span class="treeline-icon treeline-icon-join' . ($a == $c ? 'bottom' : '') . '"></span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stored tree structure AND updating it if needed according to incoming PM GET var.
|
||||
* - Here we just set it to nothing since we want to just render the tree, nothing more.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function initializePositionSaving()
|
||||
{
|
||||
$this->stored = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the title for the input record. If blank, a "no title" label (localized) will be returned.
|
||||
* Do NOT htmlspecialchar the string from this function - has already been done.
|
||||
*
|
||||
* @param array $row The input row array (where the key "title" is used for the title)
|
||||
* @param int $titleLen Title length (30)
|
||||
* @return string The title.
|
||||
*/
|
||||
public function getTitleStr($row, $titleLen = 30)
|
||||
{
|
||||
$lang = $this->getLanguageService();
|
||||
if ($this->ext_showNavTitle && isset($row['nav_title']) && trim($row['nav_title']) !== '') {
|
||||
$title = '<span title="' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:title')) . ' '
|
||||
. htmlspecialchars(trim($row['title'])) . '">'
|
||||
. htmlspecialchars(GeneralUtility::fixed_lgd_cs($row['nav_title'], $titleLen))
|
||||
. '</span>';
|
||||
} else {
|
||||
$title = htmlspecialchars(GeneralUtility::fixed_lgd_cs($row['title'], $titleLen));
|
||||
if (isset($row['nav_title']) && trim($row['nav_title']) !== '') {
|
||||
$title = '<span title="'
|
||||
. htmlspecialchars($lang->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.nav_title'))
|
||||
. ' ' . htmlspecialchars(trim($row['nav_title'])) . '">' . $title
|
||||
. '</span>';
|
||||
}
|
||||
$title = trim($row['title']) === ''
|
||||
? '<em>[' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.no_title')) . ']</em>'
|
||||
: $title;
|
||||
}
|
||||
return $title;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user