TYPO3 v15 dev-main snapshot ()

This commit is contained in:
2026-08-10 22:31:00 +02:00
commit f9941541b7
1178 changed files with 135377 additions and 0 deletions
@@ -0,0 +1,323 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Backend\Tree\View;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Backend\View\BackendLayoutView;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
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\Database\Query\Restriction\WorkspaceRestriction;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
*/
abstract class AbstractContentPagePositionMap
{
/**
* Can be set to the language id to select content elements for.
*/
public int $cur_sys_language = 0;
protected BackendLayoutView $backendLayoutView;
public function __construct(BackendLayoutView $backendLayoutView)
{
$this->backendLayoutView = $backendLayoutView;
}
/**
* Creates a linked position icon
*
* @param array|null $row The record 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 int $colPos Column position value.
* @param int $pid PID value.
* @return string HTML
*/
abstract protected function insertPositionIcon(?array $row, int $colPos, int $pid): string;
/**
* Create content element header (includes record type (CType) icon, content element title, etc.)
*
* @param array $row The element row
* @return string HTML
*/
abstract protected function getRecordHeader(array $row): string;
/**
* Creates HTML for inserting/moving content elements.
*
* @param int $pid page id onto which to insert content element.
* @return string HTML
*/
public function printContentElementColumns(int $pid, array $pageInfo, ServerRequestInterface $request): string
{
$lines = [];
$columnsConfiguration = $this->getColumnsConfiguration($pid);
foreach ($columnsConfiguration as $columnConfiguration) {
if ($columnConfiguration['isRestricted']) {
// Do not fetch records of restricted columns
continue;
}
$colPos = $columnConfiguration['colPos'];
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
$queryBuilder
->getRestrictions()
->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->getBackendUser()->workspace))
->removeByType(HiddenRestriction::class)
->removeByType(StartTimeRestriction::class)
->removeByType(EndTimeRestriction::class);
$queryBuilder
->select('*')
->from('tt_content')
->where(
$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, Connection::PARAM_INT)),
$queryBuilder->expr()->eq('colPos', $queryBuilder->createNamedParameter($colPos, Connection::PARAM_INT))
)
->orderBy('sorting');
$queryBuilder->andWhere(
$queryBuilder->expr()->eq(
'sys_language_uid',
$queryBuilder->createNamedParameter($this->cur_sys_language, Connection::PARAM_INT)
)
);
$res = $queryBuilder->executeQuery();
$lines[$colPos] = [
$this->insertPositionIcon(null, $colPos, $pid),
];
while ($row = $res->fetchAssociative()) {
BackendUtility::workspaceOL('tt_content', $row);
if (is_array($row)) {
$lines[$colPos][] = $this->getRecordHeader($row);
$lines[$colPos][] = $this->insertPositionIcon($row, $colPos, $pid);
}
}
}
return $this->printRecordMap($lines, $columnsConfiguration, $pid);
}
/**
* Creates the table with the content columns
*
* @param array $lines Array with arrays of lines for each column
* @param array $tcaColumnsConfiguration Column configuration array
* @param int $pid The id of the page
* @return string HTML
*/
protected function printRecordMap(array $lines, array $tcaColumnsConfiguration, int $pid): string
{
$lang = $this->getLanguageService();
$hideRestrictedColumns = (bool)(BackendUtility::getPagesTSconfig($pid)['mod.']['web_layout.']['hideRestrictedCols'] ?? false);
$backendLayout = $this->backendLayoutView->getSelectedBackendLayout($pid);
if (isset($backendLayout['__config']['backend_layout.'])) {
// Build position map based on the fetched backend layout
$colCount = (int)($backendLayout['__config']['backend_layout.']['colCount'] ?? 0);
$rowCount = (int)($backendLayout['__config']['backend_layout.']['rowCount'] ?? 0);
// Cycle through rows
$tableRows = [];
for ($row = 1; $row <= $rowCount; $row++) {
$rowConfig = $backendLayout['__config']['backend_layout.']['rows.'][$row . '.'] ?? null;
if (!$rowConfig) {
// Skip empty rows
continue;
}
// Cycle through cells
$tableCells = [];
for ($col = 1; $col <= $colCount; $col++) {
$columnConfig = $rowConfig['columns.'][$col . '.'] ?? null;
if (!$columnConfig) {
// Skip empty columns
continue;
}
// Set table cell attributes
$tableCellAttributes = [
'class' => 'col-nowrap col-min',
];
if (isset($columnConfig['colspan'])) {
$tableCellAttributes['colspan'] = $columnConfig['colspan'];
}
if (isset($columnConfig['rowspan'])) {
$tableCellAttributes['rowspan'] = $columnConfig['rowspan'];
}
$columnKey = null;
$columnTitle = '';
$isRestricted = false;
$isUnassigned = true;
if (isset($columnConfig['colPos'])) {
// If colPos is defined, initialize column information (e.g. title and restricted state)
$columnKey = (int)$columnConfig['colPos'];
foreach ($tcaColumnsConfiguration as $tcaColumnConfiguration) {
if ($tcaColumnConfiguration['colPos'] === $columnKey) {
$columnTitle = '<strong>' . htmlspecialchars($lang->sL($tcaColumnConfiguration['title'])) . '</strong>';
$isRestricted = $tcaColumnConfiguration['isRestricted'];
$isUnassigned = false;
}
}
}
// Generate the cell content, based on the columns' state (e.g. restricted or unassigned)
$cellContent = '';
if ($isRestricted) {
if ($hideRestrictedColumns) {
// Hide in case this column is not accessible and hideRestrictedColumns is set
$tableCellAttributes['class'] = 'hidden';
} else {
$cellContent = '
<p class="column-title">
' . $columnTitle . ' <em>(' . htmlspecialchars($lang->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:noAccess')) . ')</em>
</p>';
$tableCellAttributes['class'] .= ' danger';
}
} elseif ($isUnassigned) {
if ($hideRestrictedColumns) {
// Hide in case this column is not assigned and hideRestrictedColumns is set
$tableCellAttributes['class'] = 'hidden';
} else {
$cellContent = '
<em>
' . htmlspecialchars($lang->sL($columnConfig['name']) ?: '') . '
' . ' (' . htmlspecialchars($lang->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:notAssigned')) . ')' . '
</em>';
$tableCellAttributes['class'] .= ' warning';
}
} else {
// If not restricted and not unassigned, wrap column title and render list (if available)
$cellContent = '<p class="column-title">' . $columnTitle . '</p>';
if (!empty($lines[$columnKey])) {
$cellContent .= '
<ul>
' . implode(LF, array_map(static fn(string $line): string => '<li>' . $line . '</li>', $lines[$columnKey])) . '
</ul>';
}
}
// Add the table cell
$tableCells[] = '<td ' . GeneralUtility::implodeAttributes($tableCellAttributes) . '>' . $cellContent . '</td>';
}
// Add the table row
$tableRows[] = '<tr>' . implode(LF, $tableCells) . '</tr>';
}
// Create the table content
$tableContent
= '<colgroup>' . str_repeat('<col span="1" style="width: calc(100% / ' . $colCount . ')">', $colCount) . '</colgroup>'
. '<tbody>' . implode(LF, $tableRows) . '</tbody>';
} else {
// Build position map based on TCA colPos configuration
$tableCells = [];
foreach ($tcaColumnsConfiguration as $tcaColumnConfiguration) {
if ($hideRestrictedColumns && $tcaColumnConfiguration['isRestricted']) {
// Skip in case this column is not accessible and restricted columns should be hidden
continue;
}
// Generate the cell content, based on the columns' state (e.g. restricted or unassigned)
$tableCellClasses = 'col-nowrap col-min';
$columnTitle = '<strong>' . htmlspecialchars($tcaColumnConfiguration['title']) . '</strong>';
if ($tcaColumnConfiguration['isRestricted']) {
// If this colPos is restricted, add an information to the column title and color the cell
$tableCellClasses .= ' danger';
$cellContent = '
<p class="column-title">
' . $columnTitle . ' <em>(' . htmlspecialchars($lang->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:noAccess')) . ')</em>
</p>';
} else {
// If not restricted, wrap column title and render list (if available)
$cellContent = '<p class="column-title">' . $columnTitle . '</p>';
if (!empty($lines[$tcaColumnConfiguration['colPos']])) {
$cellContent .= '
<ul>
' . implode(LF, array_map(static fn(string $line): string => '<li>' . $line . '</li>', $lines[$tcaColumnConfiguration['colPos']])) . '
</ul>';
}
}
// Add the table cell
$tableCells[] = '<td class="' . $tableCellClasses . '">' . $cellContent . '</td>';
}
// Create the table content
$tableContent = '<tbody><tr>' . implode(LF, $tableCells) . '</tr></tbody>';
}
// Return the record map (table)
return '
<table class="page-position-grid">
' . $tableContent . '
</table>';
}
/**
* Fetch TCA colPos list from BackendLayoutView and prepare for map generation.
* This also takes the "colPos_list" TSconfig into account.
*/
protected function getColumnsConfiguration(int $pageId): array
{
$backendLayout = $this->backendLayoutView->getBackendLayoutForPage($pageId);
$items = [];
// Prepare the columns configuration (using named keys, etc.)
foreach ($backendLayout->getUsedColumns() as $colPos => $label) {
$items[] = [
'title' => $label,
'colPos' => (int)$colPos,
'isRestricted' => false,
];
}
$sharedColPosList = trim(BackendUtility::getPagesTSconfig($pageId)['mod.']['SHARED.']['colPos_list'] ?? '');
if ($sharedColPosList !== '') {
$activeColPosArray = array_unique(GeneralUtility::intExplode(',', $sharedColPosList));
if (!empty($items) && !empty($activeColPosArray)) {
foreach ($items as &$item) {
if (!in_array((int)$item['colPos'], $activeColPosArray, true)) {
$item['isRestricted'] = true;
}
}
unset($item);
}
}
return $items;
}
protected function getBackendUser(): BackendUserAuthentication
{
return $GLOBALS['BE_USER'];
}
protected function getLanguageService(): LanguageService
{
return $GLOBALS['LANG'];
}
}
+419
View File
@@ -0,0 +1,419 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Backend\Tree\View;
use Doctrine\DBAL\Result;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Database\Connection;
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\Imaging\IconFactory;
use TYPO3\CMS\Core\Imaging\IconProvider\AbstractSvgIconProvider;
use TYPO3\CMS\Core\Imaging\IconSize;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Base class for creating a browsable array/page/folder tree in HTML
*
* @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
*/
abstract class AbstractTreeView
{
/**
* Database table to get the tree data from.
* Leave blank if data comes from an array.
*/
protected string $table = 'pages';
/**
* Defines the field of $table which is the parent id field (like pid for table pages).
*/
protected string $parentField = 'pid';
/**
* WHERE clause used for selecting records for the tree. Is set by function init.
*
* @see init()
*/
protected string $clause = '';
/**
* Field for ORDER BY. Is set by function init.
*
* @see init()
*/
public string $orderByFields = 'sorting';
/**
* Default set of fields selected from the tree table.
* Make SURE that these fields names listed herein are actually possible to select from $this->table (if that variable is set to a TCA table name)
*
* @see addField()
*/
protected array $fieldArray = [
'uid',
'pid',
'title',
'is_siteroot',
'doktype',
'nav_title',
'mount_pid',
'php_tree_stop',
't3ver_state',
'hidden',
'starttime',
'endtime',
'fe_group',
'module',
'extendToSubpages',
'nav_hide',
't3ver_wsid',
'crdate',
'tstamp',
'sorting',
'deleted',
'perms_userid',
'perms_groupid',
'perms_user',
'perms_group',
'perms_everybody',
'editlock',
'l18n_cfg',
];
/**
* If true, HTML code is also accumulated in ->tree array during rendering of the tree
*/
public bool $makeHTML = true;
// *********
// Internal
// *********
// For record trees:
// one-dim array of the uid's selected.
protected array $ids = [];
// The hierarchy of element uids
protected array $ids_hierarchy = [];
// The hierarchy of versioned element uids
public array $orig_ids_hierarchy = [];
// Temporary, internal array
public array $buffer_idH = [];
// For both types
// Tree is accumulated in this variable
public array $tree = [];
/**
* @param string $clause Record WHERE clause
* @param string $orderByFields Record ORDER BY field
*/
public function init($clause = '', $orderByFields = '')
{
if ($clause) {
$this->clause = $clause;
}
if ($orderByFields) {
$this->orderByFields = $orderByFields;
}
}
public function addField(string $field): void
{
$this->fieldArray[] = $field;
}
/**
* Resets the tree, recs, ids, ids_hierarchy and orig_ids_hierarchy internal variables. Use it if you need it.
*/
protected function reset(): void
{
$this->tree = [];
$this->ids = [];
$this->ids_hierarchy = [];
$this->orig_ids_hierarchy = [];
}
/*******************************************
*
* rendering parts
*
*******************************************/
/**
* Generate the plus/minus icon for the browsable tree.
*
* @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 $isOpen The element was expanded to render subelements if this flag is set.
* @return string Image tag with the plus/minus icon.
* @see \TYPO3\CMS\Backend\Tree\View\PageTreeView::PMicon()
*/
protected function PMicon($row, $a, $c, $nextCount, $isOpen)
{
if ($nextCount) {
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
// Wrap the plus/minus icon in a link
$anchor = $row['uid'] ? '#' . $row['uid'] : '';
$name = $row['uid'] ? ' name="' . $row['uid'] . '"' : '';
$aUrl = $anchor;
if ($isOpen) {
$class = 'treelist-control-open';
$icon = $iconFactory->getIcon('actions-chevron-down', IconSize::SMALL);
} else {
$class = 'treelist-control-collapsed';
$icon = $iconFactory->getIcon('actions-chevron-end', IconSize::SMALL);
}
return '<a class="treelist-control ' . $class . '" href="' . htmlspecialchars($aUrl) . '"' . $name . '>' . $icon->render(AbstractSvgIconProvider::MARKUP_IDENTIFIER_INLINE) . '</a>';
}
return '';
}
/*******************************************
*
* tree handling
*
*******************************************/
/**
* Returns TRUE/FALSE if the next level for $id should be expanded - based on
* data in $this->stored[][] and ->expandAll flag.
* Used in subclasses
*
* @param int $id Record id/key
* @return bool
* @internal
* @see \TYPO3\CMS\Backend\Tree\View\PageTreeView::expandNext()
*/
public function expandNext($id)
{
return false;
}
/******************************
*
* Functions that might be overwritten by extended classes
*
********************************/
/**
* Get the icon markup for the row
*
* @param array $row The row to get the icon for
* @return string The icon markup, wrapped into a span tag, with the records title as title attribute
*/
protected function getIcon(array $row): string
{
$title = $this->getTitleAttrib($row);
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
$icon = $row['is_siteroot'] ? $iconFactory->getIcon('apps-pagetree-folder-root', IconSize::SMALL) : $iconFactory->getIconForRecord($this->table, $row, IconSize::SMALL);
return $icon->setTitle($title)->render();
}
/**
* Returns the value for the image "title" attribute
*
* @param array $row The input row array (where the key "title" is used for the title)
* @return string The attribute value (is htmlspecialchared() already)
*/
protected function getTitleAttrib($row)
{
return htmlspecialchars($row['title']);
}
/********************************
*
* tree data building
*
********************************/
/**
* Fetches the data for the tree
*
* @param int $uid item id for which to select subitems (parent id)
* @param int $depth Max depth (recursivity limit)
* @param string $depthData HTML-code prefix for recursive calls.
* @return int<0, max> The count of items on the level
*/
public function getTree(int $uid, int $depth = 999, string $depthData = ''): int
{
// Buffer for id hierarchy is reset:
$this->buffer_idH = [];
// Init vars
$HTML = '';
$a = 0;
$res = $this->getDataInit($uid);
$c = $res->rowCount();
$crazyRecursionLimiter = 9999;
$idH = [];
// Traverse the records:
while ($crazyRecursionLimiter > 0 && ($row = $this->getDataNext($res))) {
if (!$this->getBackendUser()->isInWebMount($this->table === 'pages' ? $row : $row['pid'])) {
// Current record is not within web mount => skip it
continue;
}
$a++;
$crazyRecursionLimiter--;
$newID = $row['uid'];
if ($newID == 0) {
throw new \RuntimeException('Endless recursion detected: TYPO3 has detected an error in the database. Please fix it manually (e.g. using phpMyAdmin) and change the UID of ' . $this->table . ':0 to a new value. See https://forge.typo3.org/issues/16150 to get more information about a possible cause.', 1294586383);
}
// Reserve space.
$this->tree[] = [];
end($this->tree);
// Get the key for this space
$treeKey = key($this->tree);
// Accumulate the id of the element in the internal arrays
$this->ids[] = ($idH[$row['uid']]['uid'] = $row['uid']);
$this->ids_hierarchy[$depth][] = $row['uid'];
$this->orig_ids_hierarchy[$depth][] = (!empty($row['_ORIG_uid'])) ? $row['_ORIG_uid'] : $row['uid'];
// Make a recursive call to the next level
$nextLevelDepthData = $depthData . '<span class="treeline-icon treeline-icon-' . ($a === $c ? 'clear' : 'line') . '"></span>';
$hasSub = $this->expandNext($newID) && !($row['php_tree_stop'] ?? false);
if ($depth > 1 && $hasSub) {
$nextCount = $this->getTree($newID, $depth - 1, $nextLevelDepthData);
if (!empty($this->buffer_idH)) {
$idH[$row['uid']]['subrow'] = $this->buffer_idH;
}
// Set "did expand" flag
$isOpen = true;
} else {
$nextCount = $this->getCount((int)$newID);
// Clear "did expand" flag
$isOpen = false;
}
// Set HTML-icons, if any:
if ($this->makeHTML) {
$HTML = $this->PMicon($row, $a, $c, $nextCount, $isOpen);
}
// Finally, add the row/HTML content to the ->tree array in the reserved key.
$this->tree[$treeKey] = [
'row' => $row,
'HTML' => $HTML,
'icon' => $this->getIcon($row),
'invertedDepth' => $depth,
'depthData' => $depthData,
'hasSub' => $nextCount && $hasSub,
'isFirst' => $a === 1,
'isLast' => $a === $c,
];
}
$res->free();
$this->buffer_idH = $idH;
return $c;
}
/********************************
*
* Data handling
* Works with records and arrays
*
********************************/
/**
* Returns the number of records having the parent id, $uid
*
* @param int $uid Id to count subitems for
*/
protected function getCount(int $uid): int
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->table);
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->getBackendUser()->workspace));
$count = $queryBuilder
->count('uid')
->from($this->table)
->where(
$queryBuilder->expr()->eq(
$this->parentField,
$queryBuilder->createNamedParameter($uid, Connection::PARAM_INT)
),
QueryHelper::stripLogicalOperatorPrefix($this->clause)
)
->executeQuery()
->fetchOne();
return (int)$count;
}
/**
* Getting the tree data: Selecting/Initializing data pointer to items for a certain parent id.
* For tables: This will make a database query to select all children to "parent"
*/
protected function getDataInit(int $parentId): Result
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->table);
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->getBackendUser()->workspace));
$queryBuilder
->select(...$this->fieldArray)
->from($this->table)
->where(
$queryBuilder->expr()->eq(
$this->parentField,
$queryBuilder->createNamedParameter($parentId, Connection::PARAM_INT)
),
QueryHelper::stripLogicalOperatorPrefix($this->clause)
);
foreach (QueryHelper::parseOrderBy($this->orderByFields) as $orderPair) {
[$fieldName, $order] = $orderPair;
$queryBuilder->addOrderBy($fieldName, $order);
}
return $queryBuilder->executeQuery();
}
/**
* Getting the tree data: next entry
*
* @see getDataInit()
*/
protected function getDataNext(Result $res): array|false
{
while ($row = $res->fetchAssociative()) {
BackendUtility::workspaceOL($this->table, $row, $this->getBackendUser()->workspace, true);
if (is_array($row)) {
break;
}
}
return $row;
}
protected function getLanguageService(): LanguageService
{
return $GLOBALS['LANG'];
}
protected function getBackendUser(): BackendUserAuthentication
{
return $GLOBALS['BE_USER'];
}
}
@@ -0,0 +1,119 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Backend\Tree\View;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Backend\View\BackendLayoutView;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Imaging\IconSize;
use TYPO3\CMS\Core\Utility\StringUtility;
/**
* Position map class for creating content elements
*
* @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
*/
#[Autoconfigure(public: true)]
class ContentCreationPagePositionMap extends AbstractContentPagePositionMap
{
/**
* Default values defined for the item
*/
public array $defVals = [];
/**
* Whether the item should directly be persisted (avoiding FormEngine)
*/
public bool $saveAndClose = false;
/**
* The return url, forwarded to FormEngine (or SimpleDataHandler)
*/
public string $R_URI = '';
protected IconFactory $iconFactory;
protected UriBuilder $uriBuilder;
public function __construct(IconFactory $iconFactory, UriBuilder $uriBuilder, BackendLayoutView $backendLayoutView)
{
$this->iconFactory = $iconFactory;
$this->uriBuilder = $uriBuilder;
parent::__construct($backendLayoutView);
}
/**
* {@inheritdoc}
*/
protected function insertPositionIcon(?array $row, int $colPos, int $pid): string
{
if ($this->saveAndClose) {
$target = (string)$this->uriBuilder->buildUriFromRoute('tce_db', [
'data' => [
'tt_content' => [
StringUtility::getUniqueId('NEW') => array_replace($this->defVals, [
'colPos' => $colPos,
'pid' => (is_array($row) ? -$row['uid'] : $pid),
'sys_language_uid' => $this->cur_sys_language,
]),
],
],
'redirect' => $this->R_URI,
]);
} else {
// @todo pass module context to this handler and pass to record_edit here
$target = (string)$this->uriBuilder->buildUriFromRoute('record_edit', [
'edit' => [
'tt_content' => [
(is_array($row) ? -$row['uid'] : $pid) => 'new',
],
],
'returnUrl' => $this->R_URI,
'defVals' => [
'tt_content' => array_replace($this->defVals, [
'colPos' => $colPos,
'sys_language_uid' => $this->cur_sys_language,
]),
],
]);
}
$buttonLabel = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:insertNewRecordHere'));
return '
<div class="page-position-action">
<button type="button" class="btn btn-default btn-sm" data-target="' . htmlspecialchars($target) . '" title="' . $buttonLabel . '">
' . $this->iconFactory->getIcon('actions-arrow-left-alt', IconSize::SMALL)->render() . ' ' . $buttonLabel . '
</button>
</div>';
}
/**
* {@inheritdoc}
*/
protected function getRecordHeader(array $row): string
{
return '
<div class="page-position-record">
<span title="' . BackendUtility::getRecordIconAltText($row, 'tt_content') . '">
' . $this->iconFactory->getIconForRecord('tt_content', $row, IconSize::SMALL)->render() . '
' . BackendUtility::getRecordTitle('tt_content', $row, true) . '
</span>
</div>';
}
}
@@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Backend\Tree\View;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Backend\View\BackendLayoutView;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Imaging\IconSize;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* 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.
*/
#[Autoconfigure(public: true)]
class ContentMovingPagePositionMap extends AbstractContentPagePositionMap
{
/**
* The move uid
*/
public int $moveUid = 0;
/**
* The copy mode (either "move" or "copy")
*/
public string $copyMode = 'move';
protected IconFactory $iconFactory;
public function __construct(IconFactory $iconFactory, BackendLayoutView $backendLayoutView)
{
$this->iconFactory = $iconFactory;
parent::__construct($backendLayoutView);
}
/**
* {@inheritdoc}
*/
protected function insertPositionIcon(?array $row, int $colPos, int $pid): string
{
if (is_array($row)) {
$attributes = [
'data-action' => 'paste',
'data-position' => '-' . $row['uid'],
'data-colpos' => $colPos,
];
} else {
$attributes = [
'data-action' => 'paste',
'data-position' => $pid,
'data-colpos' => $colPos,
];
}
$buttonLabelTransUnit = $this->copyMode === 'move' ? 'moveElementToHere' : 'copyElementToHere';
$buttonLabel = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:' . $buttonLabelTransUnit));
return '
<div class="page-position-action">
<button class="btn btn-default" title="' . $buttonLabel . '" ' . GeneralUtility::implodeAttributes($attributes, true) . '>
' . $this->iconFactory->getIcon('actions-arrow-left-alt', IconSize::SMALL)->render() . ' <span class="t3js-button-label">' . $buttonLabel . '</span>
</button>
</div>';
}
/**
* Create record header (includes the record icon, record title etc.)
*
* @param array $row Record row.
* @return string HTML
*/
protected function getRecordHeader(array $row): string
{
return '
<div class="page-position-record">
<span title="' . BackendUtility::getRecordIconAltText($row, 'tt_content') . '">
' . $this->iconFactory->getIconForRecord('tt_content', $row, IconSize::SMALL)->render() . '
' . ($this->moveUid === (int)$row['uid'] ? '<strong>' : '') . '
' . BackendUtility::getRecordTitle('tt_content', $row, true) . '
' . ($this->moveUid === (int)$row['uid'] ? '</strong>' : '') . '
</span>
</div>';
}
}
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Backend\Tree\View;
interface LinkParameterProviderInterface
{
/**
* Provides an array or GET parameters for URL generation
*
* @param array $values Array of values to include into the parameters or which might influence the parameters
* @return string[] Array of parameters which have to be added to URLs
*/
public function getUrlParameters(array $values): array;
}
+78
View File
@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Backend\Tree\View;
/**
* Generate a page-tree, non-browsable.
*
* @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
*/
class PageTreeView extends AbstractTreeView
{
protected ?int $currentPageId = null;
/**
* 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 = '')
{
$tag = \Local\Multilanguage\Service\DefaultLanguageTagService::getTag();
parent::init(" AND deleted=0 AND (language_tag='" . $tag . "' OR language_tag='') " . $clause, $orderByFields);
}
/**
* Returns TRUE/FALSE if the next level for $id should be expanded - and all levels should, so we always return true.
* Here the branch is expanded if the current id matches the global id for the listing/new
*
* @param int $id ID (uid) to test for
* @return bool
*/
public function expandNext($id)
{
if ($this->currentPageId !== null) {
return (int)$id === $this->currentPageId;
}
return true;
}
/**
* 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 $isOpen The element was expanded to render subelements if this flag is set.
* @return string Image tag with the plus/minus icon.
* @see AbstractTreeView::PMicon()
*/
protected function PMicon($row, $a, $c, $nextCount, $isOpen)
{
return '<span class="treeline-icon treeline-icon-join' . ($a == $c ? 'bottom' : '') . '"></span>';
}
public function setCurrentPageId(int $currentPageId): void
{
$this->currentPageId = $currentPageId;
}
}