Commit d51861e6 authored by Benni Mack's avatar Benni Mack Committed by Oliver Hader
Browse files

[TASK] Remove legacy handling of new content element wizard

Before TYPO3 v7 introduced modals, TYPO3's New Content Element Wizard
could be opened within the list frame as a full document.

The corresponding controller code has now been removed, along
with some legacy functionality and inline JavaScript related
to PagePositionMaps, which are used within the
NewContentElementController, along with some
legacy GeneralUtility::_GP() calls.

In addition, the ContentCreationPagePositionMap
does not inherit from PagePositionMap anymore, as it only
had a minor overlap of functionality.

Resolves: #95331
Releases: master
Change-Id: If87e4743c05aaab0550cc6939310ebed9d095813
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/71219

Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Tested-by: Oliver Hader's avatarOliver Hader <oliver.hader@typo3.org>
Reviewed-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Reviewed-by: Oliver Hader's avatarOliver Hader <oliver.hader@typo3.org>
parent bdba0522
......@@ -155,21 +155,6 @@ class NewContentElementController
$this->pageInfo = BackendUtility::readPageAccess($this->id, $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW)) ?: [];
}
/**
* Injects the request object for the current request or subrequest
* As this controller goes only through the main() method, it is rather simple for now
*
* @param ServerRequestInterface $request the current request
* @return ResponseInterface the response with the content
*/
public function mainAction(ServerRequestInterface $request): ResponseInterface
{
$this->init($request);
$this->prepareContent('window');
$this->moduleTemplate->setContent($this->view->render());
return new HtmlResponse($this->moduleTemplate->renderContent());
}
/**
* Injects the request object for the current request or subrequest
* As this controller goes only through the main() method, it is rather simple for now
......@@ -180,37 +165,33 @@ class NewContentElementController
public function wizardAction(ServerRequestInterface $request): ResponseInterface
{
$this->init($request);
$this->prepareContent('list_frame');
$this->prepareContent();
return new HtmlResponse($this->view->render());
}
/**
* Create on-click event value.
*
* @param string $clientContext
* @return string
*/
protected function onClickInsertRecord(string $clientContext): string
protected function onClickInsertRecord(): string
{
// $this->uid_pid can be negative (= pointing to tt_content record) or positive (= "page ID")
$location = (string)$this->uriBuilder->buildUriFromRoute('record_edit', [
'edit[tt_content][' . $this->uid_pid . ']' => 'new',
'defVals[tt_content][colPos]' => $this->colPos,
'defVals[tt_content][sys_language_uid]' => $this->sys_language,
'returnUrl' => GeneralUtility::_GP('returnUrl')
'returnUrl' => $this->R_URI
]);
return $clientContext . '.location.href=' . GeneralUtility::quoteJSvalue($location) . '+document.editForm.defValues.value; return false;';
return 'list_frame.location.href=' . GeneralUtility::quoteJSvalue($location) . '+document.editForm.defValues.value; return false;';
}
/**
* Creating the module output.
*
* @param string $clientContext JavaScript client context to be used
* + 'window', legacy if rendered in current document
* + 'list_frame', in case rendered in global modal
* @throws \UnexpectedValueException
*/
protected function prepareContent(string $clientContext): void
protected function prepareContent(): void
{
// Setting up the buttons for docheader
$this->getButtons();
......@@ -218,7 +199,7 @@ class NewContentElementController
if ($hasAccess) {
// If a column is pre-set
if (isset($this->colPos)) {
$onClickEvent = $this->onClickInsertRecord($clientContext);
$onClickEvent = $this->onClickInsertRecord();
} else {
$onClickEvent = '';
}
......@@ -277,7 +258,7 @@ class NewContentElementController
$urlParams['data']['tt_content'][$id]['colPos'] = $this->colPos;
$urlParams['data']['tt_content'][$id]['pid'] = $this->uid_pid;
$urlParams['data']['tt_content'][$id]['sys_language_uid'] = $this->sys_language;
$urlParams['redirect'] = GeneralUtility::_GP('returnUrl');
$urlParams['redirect'] = $this->R_URI;
unset($urlParams['defVals']);
$url = $this->uriBuilder->buildUriFromRoute('tce_db', $urlParams);
$aOnClick = 'list_frame.location.href=' . GeneralUtility::quoteJSvalue((string)$url) . '; return false';
......@@ -309,7 +290,7 @@ class NewContentElementController
// If the user must also select a column:
if (!$onClickEvent) {
$this->definePositionMapEntries($clientContext);
$this->definePositionMapEntries();
}
}
$this->view->assign('hasAccess', $hasAccess);
......@@ -318,10 +299,8 @@ class NewContentElementController
/**
* User must select a column as well (when in "main mode"), so the position map is initialized and assigned to
* the view.
*
* @param string $clientContext
*/
protected function definePositionMapEntries(string $clientContext): void
protected function definePositionMapEntries(): void
{
// Load SHARED page-TSconfig settings and retrieve column list from there, if applicable:
$colPosArray = GeneralUtility::makeInstance(BackendLayoutView::class)->getColPosListItemsParsed((int)$this->id);
......@@ -330,13 +309,9 @@ class NewContentElementController
$colPosList = implode(',', array_unique(array_map('intval', $colPosIds)));
// Finally, add the content of the column selector to the content:
// Init position map object
$posMap = GeneralUtility::makeInstance(
ContentCreationPagePositionMap::class,
null,
$clientContext
);
$posMap = GeneralUtility::makeInstance(ContentCreationPagePositionMap::class);
$posMap->cur_sys_language = $this->sys_language;
$this->view->assign('posMap', $posMap->printContentElementColumns($this->id, 0, $colPosList, $this->R_URI));
$this->view->assign('posMap', $posMap->printContentElementColumns($this->id, $colPosList, $this->R_URI));
}
/**
......
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
......@@ -16,51 +18,246 @@
namespace TYPO3\CMS\Backend\Tree\View;
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Backend\View\BackendLayoutView;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
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\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\MathUtility;
/**
* Local position map class when creating new Content Elements
* Local position map class when creating new Content Elements. Previously this was extended from the PagePositionMap
* however it is not related to positioning pages, but only contents.
*
* @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
*/
class ContentCreationPagePositionMap extends PagePositionMap
class ContentCreationPagePositionMap
{
/**
* Can be set to the sys_language uid to select content elements for.
* @var int
*/
public $dontPrintPageInsertIcons = 1;
public $cur_sys_language;
protected string $R_URI = '';
protected IconFactory $iconFactory;
protected UriBuilder $uriBuilder;
public function __construct()
{
$this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
$this->uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
}
/**
* Wrapping the title of the record - here we just return it.
* Creates HTML for inserting/moving content elements.
*
* @param string $str The title value.
* @param array $row The record row.
* @return string Wrapped title string.
* @param int $pid page id onto which to insert content element.
* @param string $colPosList List of columns to show
* @param string $R_URI Request URI
* @return string HTML
*/
public function wrapRecordTitle($str, $row)
public function printContentElementColumns(int $pid, string $colPosList, string $R_URI): string
{
return $str;
$this->R_URI = $R_URI;
$colPosArray = GeneralUtility::trimExplode(',', $colPosList, true);
$lines = [];
foreach ($colPosArray as $colPos) {
$colPos = (int)$colPos;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
$queryBuilder->getRestrictions()
->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, (int)$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, \PDO::PARAM_INT)),
$queryBuilder->expr()->eq('colPos', $queryBuilder->createNamedParameter($colPos, \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[$colPos] = [];
$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, $colPosArray, $pid);
}
/**
* Create on-click event value.
* Creates a linked position icon.
*
* @param array $row The record.
* @param string $vv Column position value.
* @param int $moveUid Move uid
* @param array|null $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 int $colPos Column position value.
* @param int $pid PID value.
* @param int $sys_lang System language
* @return string
*/
public function onClickInsertRecord($row, $vv, $moveUid, $pid, $sys_lang = 0)
protected function insertPositionIcon(?array $row, int $colPos, int $pid): string
{
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
$location = (string)$uriBuilder->buildUriFromRoute('record_edit', [
$location = (string)$this->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')
'defVals[tt_content][colPos]' => $colPos,
'defVals[tt_content][sys_language_uid]' => $this->cur_sys_language,
'returnUrl' => $this->R_URI
]);
return $this->clientContext . '.location.href=' . GeneralUtility::quoteJSvalue($location) . '+document.editForm.defValues.value; return false;';
$location = 'list_frame.location.href=' . GeneralUtility::quoteJSvalue($location) . '+document.editForm.defValues.value; return false;';
return '<a href="#" onclick="' . htmlspecialchars($location) . '" data-bs-dismiss="modal"><i class="t3-icon fa fa-long-arrow-left" title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:insertNewRecordHere')) . '"></i></a>';
}
/**
* 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
*/
protected function printRecordMap($lines, $colPosArray, $pid)
{
$count = MathUtility::forceIntegerInRange(count($colPosArray), 1);
$backendLayoutProvider = GeneralUtility::makeInstance(BackendLayoutView::class);
$backendLayout = $backendLayoutProvider->getSelectedBackendLayout($pid);
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-sm 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 = $backendLayoutProvider->getColPosListItemsParsed($pid);
// 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 . '.'] ?? false;
if (!$columnConfig) {
continue;
}
// Which tt_content colPos should be displayed inside this cell
$columnKey = (int)($columnConfig['colPos'] ?? 0);
$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>' . $head . '</strong>';
} elseif (isset($columnConfig['colPos'])) {
$table .= '<em>' . $this->getLanguageService()->getLL('noAccess') . '</em>';
} else {
$table .= '<em>' . ($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 $colPos) {
$row .= '<td class="col-nowrap col-min" width="' . round(100 / $count) . '%">';
$row .= '<p><strong>' . htmlspecialchars($this->getLanguageService()->sL(BackendUtility::getLabelFromItemlist('tt_content', 'colPos', $colPos))) . '</strong></p>';
if (!empty($lines[$colPos])) {
$row .= '<ul class="list-unstyled">';
foreach ($lines[$colPos] as $line) {
$row .= '<li>' . $line . '</li>';
}
$row .= '</ul>';
}
$row .= '</td>';
}
$table = '
<!--
Map of records in columns:
-->
<div class="table-fit">
<table class="table table-sm table-bordered table-vertical-top">
<tr>' . $row . '</tr>
</table>
</div>
';
}
return $table;
}
/**
* Create record header (includes the record icon, record title etc.)
*
* @param array $row Record row.
* @return string HTML
*/
protected function getRecordHeader(array $row): string
{
$line = '<span ' . BackendUtility::getRecordToolTip($row, 'tt_content') . '">' . $this->iconFactory->getIconForRecord('tt_content', $row, Icon::SIZE_SMALL)->render() . '</span>';
$line .= BackendUtility::getRecordTitle('tt_content', $row, true);
return $line;
}
protected function getBackendUser(): BackendUserAuthentication
{
return $GLOBALS['BE_USER'];
}
protected function getLanguageService(): LanguageService
{
return $GLOBALS['LANG'];
}
}
......@@ -30,7 +30,7 @@ class PageMovingPagePositionMap extends PagePositionMap
public $l_insertNewPageHere = 'movePageToHere';
/**
* Creates the onclick event for the insert-icons.
* Creates the link target for the insert-icons.
*
* @param int $pid The pid.
* @param int $newPagePID New page id.
......
......@@ -27,6 +27,7 @@ use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Type\Bitmask\Permission;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\MathUtility;
......@@ -107,23 +108,16 @@ class PagePositionMap
*/
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')
* @param string|null $pageTreeClassName
*/
public function __construct(string $pageTreeClassName = null, string $clientContext = 'window')
public function __construct(string $pageTreeClassName = null)
{
if ($pageTreeClassName !== null) {
$this->pageTreeClassName = $pageTreeClassName;
}
$this->clientContext = $clientContext;
$this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
}
......@@ -334,7 +328,7 @@ class PagePositionMap
{
if (!isset($this->checkNewPageCache[$pid])) {
$pidInfo = BackendUtility::getRecord('pages', $pid);
$this->checkNewPageCache[$pid] = $this->getBackendUser()->isAdmin() || $this->getBackendUser()->doesUserHaveAccess($pidInfo, 8);
$this->checkNewPageCache[$pid] = $this->getBackendUser()->isAdmin() || $this->getBackendUser()->doesUserHaveAccess($pidInfo, Permission::PAGE_NEW);
}
return $this->checkNewPageCache[$pid];
}
......@@ -359,7 +353,7 @@ class PagePositionMap
$this->moveUid = $moveUid;
$colPosArray = GeneralUtility::trimExplode(',', $colPosList, true);
$lines = [];
foreach ($colPosArray as $kk => $vv) {
foreach ($colPosArray as $vv) {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
$queryBuilder->getRestrictions()
->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, (int)$this->getBackendUser()->workspace))
......@@ -386,13 +380,13 @@ class PagePositionMap
$res = $queryBuilder->execute();
$lines[$vv] = [];
$lines[$vv][] = $this->insertPositionIcon('', $vv, $kk, $moveUid, $pid);
$lines[$vv][] = $this->insertPositionIcon(null, $vv, $moveUid, $pid);
while ($row = $res->fetchAssociative()) {
BackendUtility::workspaceOL('tt_content', $row);
if (is_array($row)) {
$lines[$vv][] = $this->getRecordHeader($row);
$lines[$vv][] = $this->insertPositionIcon($row, $vv, $kk, $moveUid, $pid);
$lines[$vv][] = $this->insertPositionIcon($row, $vv, $moveUid, $pid);
}
}
}
......@@ -478,7 +472,7 @@ class PagePositionMap
} else {
// Traverse the columns here:
$row = '';
foreach ($colPosArray as $kk => $vv) {
foreach ($colPosArray as $vv) {
$row .= '<td class="col-nowrap col-min" width="' . round(100 / $count) . '%">';
$row .= '<p><strong>' . htmlspecialchars($this->getLanguageService()->sL(BackendUtility::getLabelFromItemlist('tt_content', 'colPos', $vv))) . '</strong></p>';
if (!empty($lines[$vv])) {
......@@ -512,36 +506,11 @@ class PagePositionMap
* @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-bs-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)
public function insertPositionIcon($row, $vv, $moveUid, $pid)
{
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
if (is_array($row)) {
......@@ -556,8 +525,7 @@ class PagePositionMap
'redirect' => $this->R_URI,
]);
}
// returns to prev. page
return $this->clientContext . '.location.href=' . GeneralUtility::quoteJSvalue((string)$location) . ';return false;';
return '<a href="' . htmlspecialchars($location) . '" data-bs-dismiss="modal"><i class="t3-icon fa fa-long-arrow-left" title="' . htmlspecialchars($this->getLanguageService()->getLL($this->l_insertNewRecordHere)) . '"></i></a>';
}
/**
......
......@@ -147,13 +147,7 @@ return [
'target' => Controller\Page\NewMultiplePagesController::class . '::mainAction'
],
// Register new content element module (as whole document)
'new_content_element' => [
'path' => '/record/content/new',
'target' => Controller\ContentElement\NewContentElementController::class . '::mainAction'
],
// Register new content element module (in modal)
// Register new content element module (used in a modal)
'new_content_element_wizard' => [
'path' => '/record/content/wizard/new',
'target' => Controller\ContentElement\NewContentElementController::class . '::wizardAction'
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment