first commit

This commit is contained in:
salam committed 2026-10-19 08:18:39 +02:00
commit 1218dd1517
12716 files changed
+989233

No files matched your search

@@ -0,0 +1,541 @@
<?php
namespace TYPO3\CMS\Backend\Controller\ContentElement;
/*
* 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 Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\History\RecordHistory;
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
use TYPO3\CMS\Backend\Template\ModuleTemplate;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\DataHandling\History\RecordHistoryStore;
use TYPO3\CMS\Core\Http\HtmlResponse;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Type\Bitmask\Permission;
use TYPO3\CMS\Core\Utility\DiffUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Fluid\View\StandaloneView;
/**
* Controller for showing the history module of TYPO3s backend
* @see \TYPO3\CMS\Backend\History\RecordHistory
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
*/
class ElementHistoryController
{
/**
* @var StandaloneView
*/
protected $view;
/**
* @var RecordHistory
*/
protected $historyObject;
/**
* Display diff or not (0-no diff, 1-inline)
*
* @var int
*/
protected $showDiff = 1;
/**
* @var array
*/
protected $recordCache = [];
/**
* ModuleTemplate object
*
* @var ModuleTemplate
*/
protected $moduleTemplate;
/**
* Restrict editing by non-Admins (0-no, 1-yes)
*
* @var bool
*/
protected $editLock = false;
/**
* Constructor
*/
public function __construct()
{
$this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
$this->view = $this->initializeView();
}
/**
* 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->moduleTemplate->getDocHeaderComponent()->setMetaInformation([]);
$parsedBody = $request->getParsedBody();
$queryParams = $request->getQueryParams();
$lastHistoryEntry = (int)($parsedBody['historyEntry'] ?? $queryParams['historyEntry'] ?? 0);
$rollbackFields = $parsedBody['rollbackFields'] ?? $queryParams['rollbackFields'] ?? null;
$element = $parsedBody['element'] ?? $queryParams['element'] ?? null;
$displaySettings = $this->prepareDisplaySettings($request);
$this->view->assign('currentSelection', $displaySettings);
$this->showDiff = (int)$displaySettings['showDiff'];
// Start history object
$this->historyObject = GeneralUtility::makeInstance(RecordHistory::class, $element, $rollbackFields);
$this->historyObject->setShowSubElements((int)$displaySettings['showSubElements']);
$this->historyObject->setLastHistoryEntry($lastHistoryEntry);
if ($displaySettings['maxSteps']) {
$this->historyObject->setMaxSteps((int)$displaySettings['maxSteps']);
}
// Do the actual logic now (rollback, show a diff for certain changes,
// or show the full history of a page or a specific record)
$this->historyObject->createChangeLog();
if (!empty($this->historyObject->changeLog)) {
if ($this->historyObject->shouldPerformRollback()) {
$this->historyObject->performRollback();
} elseif ($lastHistoryEntry) {
$completeDiff = $this->historyObject->createMultipleDiff();
$this->displayMultipleDiff($completeDiff);
$this->view->assign('showDifferences', true);
$this->view->assign('fullViewUrl', $this->buildUrl(['historyEntry' => '']));
}
if ($this->historyObject->getElementData()) {
$this->displayHistory($this->historyObject->changeLog);
}
}
/** @var \TYPO3\CMS\Core\Http\NormalizedParams $normalizedParams */
$normalizedParams = $request->getAttribute('normalizedParams');
$elementData = $this->historyObject->getElementData();
if ($elementData) {
$this->setPagePath($elementData[0], $elementData[1]);
$this->editLock = $this->getEditLockFromElement($elementData[0], $elementData[1]);
// Get link to page history if the element history is shown
if ($elementData[0] !== 'pages') {
$this->view->assign('singleElement', true);
$parentPage = BackendUtility::getRecord($elementData[0], $elementData[1], '*', '', false);
if ($parentPage['pid'] > 0 && BackendUtility::readPageAccess($parentPage['pid'], $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW))) {
$this->view->assign('fullHistoryUrl', $this->buildUrl([
'element' => 'pages:' . $parentPage['pid'],
'historyEntry' => '',
'returnUrl' => $normalizedParams->getRequestUri(),
]));
}
}
}
$this->view->assign('TYPO3_REQUEST_URI', $normalizedParams->getRequestUrl());
$this->view->assign('editLock', $this->editLock);
// Setting up the buttons and markers for docheader
$this->getButtons($request);
// Build the <body> for the module
$this->moduleTemplate->setContent($this->view->render());
return new HtmlResponse($this->moduleTemplate->renderContent());
}
/**
* Creates the correct path to the current record
*
* @param string $table
* @param int $uid
*/
protected function setPagePath($table, $uid)
{
$uid = (int)$uid;
if ($table === 'pages') {
$pageId = $uid;
} else {
$record = BackendUtility::getRecord($table, $uid, '*', '', false);
$pageId = $record['pid'];
}
$pageAccess = BackendUtility::readPageAccess($pageId, $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW));
if (is_array($pageAccess)) {
$this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($pageAccess);
}
}
/**
* Create the panel of buttons for submitting the form or otherwise perform operations.
*
* @param ServerRequestInterface $request
*/
protected function getButtons(ServerRequestInterface $request)
{
$buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
$helpButton = $buttonBar->makeHelpButton()
->setModuleName('xMOD_csh_corebe')
->setFieldName('history_log');
$buttonBar->addButton($helpButton);
// Get returnUrl parameter
$parsedBody = $request->getParsedBody();
$queryParams = $request->getQueryParams();
$returnUrl = GeneralUtility::sanitizeLocalUrl($parsedBody['returnUrl'] ?? $queryParams['returnUrl'] ?? '');
if ($returnUrl) {
$backButton = $buttonBar->makeLinkButton()
->setHref($returnUrl)
->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.closeDoc'))
->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-view-go-back', Icon::SIZE_SMALL));
$buttonBar->addButton($backButton, ButtonBar::BUTTON_POSITION_LEFT, 10);
}
}
/**
* Displays settings evaluation
*
* @param ServerRequestInterface $request
* @return array
*/
protected function prepareDisplaySettings(ServerRequestInterface $request)
{
// Get current selection from UC, merge data, write it back to UC
$currentSelection = is_array($this->getBackendUser()->uc['moduleData']['history'])
? $this->getBackendUser()->uc['moduleData']['history']
: ['maxSteps' => '', 'showDiff' => 1, 'showSubElements' => 1];
$parsedBody = $request->getParsedBody();
$queryParams = $request->getQueryParams();
$currentSelectionOverride = $parsedBody['settings'] ?? $queryParams['settings'] ?? null;
if (is_array($currentSelectionOverride) && !empty($currentSelectionOverride)) {
$currentSelection = array_merge($currentSelection, $currentSelectionOverride);
$this->getBackendUser()->uc['moduleData']['history'] = $currentSelection;
$this->getBackendUser()->writeUC($this->getBackendUser()->uc);
}
// Display selector for number of history entries
$selector['maxSteps'] = [
10 => [
'value' => 10
],
20 => [
'value' => 20
],
50 => [
'value' => 50
],
100 => [
'value' => 100
],
999 => [
'value' => 'maxSteps_all'
]
];
$selector['showDiff'] = [
0 => [
'value' => 'showDiff_no'
],
1 => [
'value' => 'showDiff_inline'
]
];
$selector['showSubElements'] = [
0 => [
'value' => 'no'
],
1 => [
'value' => 'yes'
]
];
$scriptUrl = GeneralUtility::linkThisScript();
foreach ($selector as $key => $values) {
foreach ($values as $singleKey => $singleVal) {
$selector[$key][$singleKey]['scriptUrl'] = htmlspecialchars(GeneralUtility::quoteJSvalue($scriptUrl . '&settings[' . $key . ']=' . $singleKey));
}
}
$this->view->assign('settings', $selector);
return $currentSelection;
}
/**
* Displays a diff over multiple fields including rollback links
*
* @param array $diff Difference array
*/
protected function displayMultipleDiff(array $diff)
{
// Get all array keys needed
$arrayKeys = array_merge(array_keys($diff['newData']), array_keys($diff['insertsDeletes']), array_keys($diff['oldData']));
$arrayKeys = array_unique($arrayKeys);
if (!empty($arrayKeys)) {
$lines = [];
foreach ($arrayKeys as $key) {
$singleLine = [];
$elParts = explode(':', $key);
// Turn around diff because it should be a "rollback preview"
if ((int)$diff['insertsDeletes'][$key] === 1) {
// insert
$singleLine['insertDelete'] = 'delete';
} elseif ((int)$diff['insertsDeletes'][$key] === -1) {
$singleLine['insertDelete'] = 'insert';
}
// Build up temporary diff array
// turn around diff because it should be a "rollback preview"
if ($diff['newData'][$key]) {
$tmpArr = [
'newRecord' => $diff['oldData'][$key],
'oldRecord' => $diff['newData'][$key]
];
$singleLine['differences'] = $this->renderDiff($tmpArr, $elParts[0], $elParts[1]);
}
$elParts = explode(':', $key);
$singleLine['revertRecordUrl'] = $this->buildUrl(['rollbackFields' => $key]);
$singleLine['title'] = $this->generateTitle($elParts[0], $elParts[1]);
$lines[] = $singleLine;
}
$this->view->assign('revertAllUrl', $this->buildUrl(['rollbackFields' => 'ALL']));
$this->view->assign('multipleDiff', $lines);
}
}
/**
* Shows the full change log
*
* @param array $historyEntries
*/
protected function displayHistory(array $historyEntries)
{
if (empty($historyEntries)) {
return;
}
$languageService = $this->getLanguageService();
$lines = [];
$beUserArray = BackendUtility::getUserNames();
// Traverse changeLog array:
foreach ($historyEntries as $entry) {
// Build up single line
$singleLine = [];
// Get user names
$singleLine['backendUserUid'] = $entry['userid'];
$singleLine['backendUserName'] = $entry['userid'] ? $beUserArray[$entry['userid']]['username'] : '';
// Executed by switch user
if (!empty($entry['originaluserid'])) {
$singleLine['originalBackendUserName'] = $beUserArray[$entry['originaluserid']]['username'];
}
// Diff link
$singleLine['diffUrl'] = $this->buildUrl(['historyEntry' => $entry['uid']]);
// Add time
$singleLine['time'] = BackendUtility::datetime($entry['tstamp']);
// Add age
$singleLine['age'] = BackendUtility::calcAge($GLOBALS['EXEC_TIME'] - $entry['tstamp'], $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears'));
$singleLine['title'] = $this->generateTitle($entry['tablename'], $entry['recuid']);
$singleLine['elementUrl'] = $this->buildUrl(['element' => $entry['tablename'] . ':' . $entry['recuid']]);
$singleLine['actiontype'] = $entry['actiontype'];
if ((int)$entry['actiontype'] === RecordHistoryStore::ACTION_MODIFY) {
// show changes
if (!$this->showDiff) {
// Display field names instead of full diff
// Re-write field names with labels
$tmpFieldList = array_keys($entry['newRecord']);
foreach ($tmpFieldList as $key => $value) {
$tmp = str_replace(':', '', $languageService->sL(BackendUtility::getItemLabel($entry['tablename'], $value)));
if ($tmp) {
$tmpFieldList[$key] = $tmp;
} else {
// remove fields if no label available
unset($tmpFieldList[$key]);
}
}
$singleLine['fieldNames'] = implode(',', $tmpFieldList);
} else {
// Display diff
$singleLine['differences'] = $this->renderDiff($entry, $entry['tablename']);
}
}
// put line together
$lines[] = $singleLine;
}
$this->view->assign('history', $lines);
}
/**
* Renders HTML table-rows with the comparison information of an sys_history entry record
*
* @param array $entry sys_history entry record.
* @param string $table The table name
* @param int $rollbackUid If set to UID of record, display rollback links
* @return array array of records
*/
protected function renderDiff($entry, $table, $rollbackUid = 0): array
{
$lines = [];
if (is_array($entry['newRecord'])) {
/* @var DiffUtility $diffUtility */
$diffUtility = GeneralUtility::makeInstance(DiffUtility::class);
$diffUtility->stripTags = false;
$fieldsToDisplay = array_keys($entry['newRecord']);
$languageService = $this->getLanguageService();
foreach ($fieldsToDisplay as $fN) {
if (is_array($GLOBALS['TCA'][$table]['columns'][$fN]) && $GLOBALS['TCA'][$table]['columns'][$fN]['config']['type'] !== 'passthrough') {
// Create diff-result:
$diffres = $diffUtility->makeDiffDisplay(
BackendUtility::getProcessedValue($table, $fN, $entry['oldRecord'][$fN], 0, true),
BackendUtility::getProcessedValue($table, $fN, $entry['newRecord'][$fN], 0, true)
);
$rollbackUrl = '';
if ($rollbackUid) {
$rollbackUrl = $this->buildUrl(['rollbackFields' => $table . ':' . $rollbackUid . ':' . $fN]);
}
$lines[] = [
'title' => $languageService->sL(BackendUtility::getItemLabel($table, $fN)),
'rollbackUrl' => $rollbackUrl,
'result' => str_replace('\n', PHP_EOL, str_replace('\r\n', '\n', $diffres))
];
}
}
}
return $lines;
}
/**
* Generates the URL for a link to the current page
*
* @param array $overrideParameters
* @return string
*/
protected function buildUrl($overrideParameters = []): string
{
$params = [];
// Setting default values based on GET parameters:
if ($this->historyObject->getElementData()) {
$params['element'] = $this->historyObject->getElementString();
}
$params['historyEntry'] = $this->historyObject->lastHistoryEntry;
// Merging overriding values:
$params = array_merge($params, $overrideParameters);
// Make the link:
/** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
$uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
return (string)$uriBuilder->buildUriFromRoute('record_history', $params);
}
/**
* Generates the title and puts the record title behind
*
* @param string $table
* @param string $uid
* @return string
*/
protected function generateTitle($table, $uid): string
{
$title = $table . ':' . $uid;
if (!empty($GLOBALS['TCA'][$table]['ctrl']['label'])) {
$record = $this->getRecord($table, $uid);
$title .= ' (' . BackendUtility::getRecordTitle($table, $record, true) . ')';
}
return $title;
}
/**
* Gets a database record (cached).
*
* @param string $table
* @param int $uid
* @return array|null
*/
protected function getRecord($table, $uid)
{
if (!isset($this->recordCache[$table][$uid])) {
$this->recordCache[$table][$uid] = BackendUtility::getRecord($table, $uid, '*', '', false);
}
return $this->recordCache[$table][$uid];
}
/**
* Returns a new standalone view, shorthand function
*
* @return StandaloneView
*/
protected function initializeView()
{
/** @var StandaloneView $view */
$view = GeneralUtility::makeInstance(StandaloneView::class);
$view->setLayoutRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Layouts')]);
$view->setPartialRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Partials')]);
$view->setTemplateRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates')]);
$view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates/RecordHistory/Main.html'));
$view->getRequest()->setControllerExtensionName('Backend');
return $view;
}
/**
* Returns LanguageService
*
* @return \TYPO3\CMS\Core\Localization\LanguageService
*/
protected function getLanguageService()
{
return $GLOBALS['LANG'];
}
/**
* Gets the current backend user.
*
* @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
*/
protected function getBackendUser()
{
return $GLOBALS['BE_USER'];
}
/**
* Get the editlock value from page of a history element
*
* @param string $tableName
* @param int $elementUid
*
* @return bool
*/
protected function getEditLockFromElement($tableName, $elementUid): bool
{
// If the user is admin, then he may always edit the page.
if ($this->getBackendUser()->isAdmin()) {
return false;
}
$record = BackendUtility::getRecord($tableName, $elementUid, '*', '', false);
// we need the parent page record for the editlock info if element isn't a page
if ($tableName !== 'pages') {
$pageId = $record['pid'];
$record = BackendUtility::getRecord('pages', $pageId, '*', '', false);
}
return (bool)$record['editlock'];
}
}
@@ -0,0 +1,830 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Backend\Controller\ContentElement;
/*
* 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 Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\Backend\Avatar\Avatar;
use TYPO3\CMS\Backend\Template\ModuleTemplate;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Compatibility\PublicMethodDeprecationTrait;
use TYPO3\CMS\Core\Compatibility\PublicPropertyDeprecationTrait;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Http\HtmlResponse;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Resource\AbstractFile;
use TYPO3\CMS\Core\Resource\File;
use TYPO3\CMS\Core\Resource\Folder;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Type\Bitmask\Permission;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Fluid\View\StandaloneView;
/**
* Script Class for showing information about an item.
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
*/
class ElementInformationController
{
use PublicMethodDeprecationTrait;
use PublicPropertyDeprecationTrait;
/**
* @var array
*/
private $deprecatedPublicMethods = [
'getLabelForTableColumn' => 'Using ElementInformationController::getLabelForTableColumn() is deprecated and will not be possible anymore in TYPO3 v10.0.',
];
/**
* Properties which have been moved to protected status from public
*
* @var array
*/
private $deprecatedPublicProperties = [
'table' => 'Using $table of class ElementInformationController from the outside is discouraged, as this variable is only used for internal storage.',
'uid' => 'Using $uid of class ElementInformationController from the outside is discouraged, as this variable is only used for internal storage.',
'access' => 'Using $access of class ElementInformationController from the outside is discouraged, as this variable is only used for internal storage.',
'type' => 'Using $type of class ElementInformationController from the outside is discouraged, as this variable is only used for internal storage.',
'pageInfo' => 'Using $pageInfo of class ElementInformationController from the outside is discouraged, as this variable is only used for internal storage.',
];
/**
* Record table name
*
* @var string
*/
protected $table;
/**
* Record uid
*
* @var int
*/
protected $uid;
/**
* @var string
*/
protected $permsClause;
/**
* @var bool
*/
protected $access = false;
/**
* Which type of element:
* - "file"
* - "db"
*
* @var string
*/
protected $type = '';
/**
* @var ModuleTemplate
*/
protected $moduleTemplate;
/**
* For type "db": Set to page record of the parent page of the item set
* (if type="db")
*
* @var array
*/
protected $pageInfo;
/**
* Database records identified by table/uid
*
* @var array
*/
protected $row;
/**
* @var \TYPO3\CMS\Core\Resource\File
*/
protected $fileObject;
/**
* @var Folder
*/
protected $folderObject;
/**
* @var IconFactory
*/
protected $iconFactory;
/**
* Constructor
*/
public function __construct()
{
$this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
$GLOBALS['SOBE'] = $this;
// @deprecated since TYPO3 v9, will be obsolete in TYPO3 v10.0 with removal of init()
$request = $GLOBALS['TYPO3_REQUEST'];
// @deprecated since TYPO3 v9, will be moved out of __construct() in TYPO3 v10.0
$this->init($request);
}
/**
* 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->main($request);
return new HtmlResponse($this->moduleTemplate->renderContent());
}
/**
* Determines if table/uid point to database record or file and
* if user has access to view information
*
* @param ServerRequestInterface|null $request
*/
public function init(ServerRequestInterface $request = null): void
{
if ($request === null) {
// Missing argument? This method must have been called from outside.
// Method will be protected and $request mandatory in TYPO3 v10.0, giving core freedom to move stuff around
// New v10 signature: "protected function init(ServerRequestInterface $request): void"
// @deprecated since TYPO3 v9, method argument $request will be set to mandatory
trigger_error('ElementInformationController->init() will be set to protected in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
$request = $GLOBALS['TYPO3_REQUEST'];
}
$queryParams = $request->getQueryParams();
$this->table = $queryParams['table'] ?? null;
$this->uid = $queryParams['uid'] ?? null;
$this->permsClause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW);
$this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
$this->moduleTemplate->getDocHeaderComponent()->disable();
if (isset($GLOBALS['TCA'][$this->table])) {
$this->initDatabaseRecord();
} elseif ($this->table === '_FILE' || $this->table === '_FOLDER' || $this->table === 'sys_file') {
$this->initFileOrFolderRecord();
}
}
/**
* Init database records (table)
*/
protected function initDatabaseRecord()
{
$this->type = 'db';
$this->uid = (int)$this->uid;
// Check permissions and uid value:
if ($this->uid && $this->getBackendUser()->check('tables_select', $this->table)) {
if ((string)$this->table === 'pages') {
$this->pageInfo = BackendUtility::readPageAccess($this->uid, $this->permsClause);
$this->access = is_array($this->pageInfo);
$this->row = $this->pageInfo;
} else {
$this->row = BackendUtility::getRecordWSOL($this->table, $this->uid);
if ($this->row) {
// Find the correct "pid" when a versionized record is given, otherwise "pid = -1" always fails
if (!empty($this->row['t3ver_oid'])) {
$t3OrigRow = BackendUtility::getRecord($this->table, (int)$this->row['t3ver_oid']);
$this->pageInfo = BackendUtility::readPageAccess((int)$t3OrigRow['pid'], $this->permsClause);
} else {
$this->pageInfo = BackendUtility::readPageAccess($this->row['pid'], $this->permsClause);
}
$this->access = is_array($this->pageInfo);
}
}
}
}
/**
* Init file/folder parameters
*/
protected function initFileOrFolderRecord()
{
$fileOrFolderObject = ResourceFactory::getInstance()->retrieveFileOrFolderObject($this->uid);
if ($fileOrFolderObject instanceof Folder) {
$this->folderObject = $fileOrFolderObject;
$this->access = $this->folderObject->checkActionPermission('read');
$this->type = 'folder';
} elseif ($fileOrFolderObject instanceof File) {
$this->fileObject = $fileOrFolderObject;
$this->access = $this->fileObject->checkActionPermission('read');
$this->type = 'file';
$this->table = 'sys_file';
try {
$this->row = BackendUtility::getRecordWSOL($this->table, $fileOrFolderObject->getUid());
} catch (\Exception $e) {
$this->row = [];
}
}
}
/**
* Compiles the whole content to be outputted, which is then set as content to the moduleTemplate
* There is a hook to do a custom rendering of a record.
*
* @param ServerRequestInterface $request
*/
public function main(ServerRequestInterface $request = null): void
{
if ($request === null) {
// Missing argument? This method must have been called from outside.
// @deprecated since TYPO3 v9, method argument $request will be set to mandatory
trigger_error('ElementInformationController->main() will be set to protected in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
$request = $GLOBALS['TYPO3_REQUEST'];
}
$content = '';
// Rendering of the output via fluid
$view = GeneralUtility::makeInstance(StandaloneView::class);
$view->setTemplateRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates')]);
$view->setPartialRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Partials')]);
$view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName(
'EXT:backend/Resources/Private/Templates/ContentElement/ElementInformation.html'
));
if ($this->access) {
// render type by user func
$typeRendered = false;
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/show_item.php']['typeRendering'] ?? [] as $className) {
$typeRenderObj = GeneralUtility::makeInstance($className);
if (is_object($typeRenderObj) && method_exists($typeRenderObj, 'isValid') && method_exists($typeRenderObj, 'render')) {
if ($typeRenderObj->isValid($this->type, $this)) {
$content .= $typeRenderObj->render($this->type, $this);
$typeRendered = true;
break;
}
}
}
if (!$typeRendered) {
$view->assign('accessAllowed', true);
$view->assignMultiple($this->getPageTitle());
$view->assignMultiple($this->getPreview());
$view->assignMultiple($this->getPropertiesForTable());
$view->assignMultiple($this->getReferences($request));
$view->assign('returnUrl', GeneralUtility::sanitizeLocalUrl($request->getQueryParams()['returnUrl']));
$view->assign('maxTitleLength', $this->getBackendUser()->uc['titleLen'] ?? 20);
$content .= $view->render();
}
} else {
$content .= $view->render();
}
$this->moduleTemplate->setContent($content);
}
/**
* Get page title with icon, table title and record title
*
* @return array
*/
protected function getPageTitle(): array
{
$pageTitle = [
'title' => BackendUtility::getRecordTitle($this->table, $this->row, false)
];
if ($this->type === 'folder') {
$pageTitle['table'] = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:folder');
$pageTitle['icon'] = $this->iconFactory->getIconForResource($this->folderObject, Icon::SIZE_SMALL)->render();
} elseif ($this->type === 'file') {
$pageTitle['table'] = $this->getLanguageService()->sL($GLOBALS['TCA'][$this->table]['ctrl']['title']);
$pageTitle['icon'] = $this->iconFactory->getIconForResource($this->fileObject, Icon::SIZE_SMALL)->render();
} else {
$pageTitle['table'] = $this->getLanguageService()->sL($GLOBALS['TCA'][$this->table]['ctrl']['title']);
$pageTitle['icon'] = $this->iconFactory->getIconForRecord($this->table, $this->row, Icon::SIZE_SMALL);
}
$this->moduleTemplate->setTitle($pageTitle['table'] . ': ' . $pageTitle['title']);
return $pageTitle;
}
/**
* Get preview for current record
*
* @return array
*/
protected function getPreview(): array
{
$preview = [];
// Perhaps @todo in future: Also display preview for records - without fileObject
if (!$this->fileObject) {
return $preview;
}
// check if file is marked as missing
if ($this->fileObject->isMissing()) {
$preview['missingFile'] = $this->fileObject->getName();
} else {
/** @var \TYPO3\CMS\Core\Resource\Rendering\RendererRegistry $rendererRegistry */
$rendererRegistry = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\Rendering\RendererRegistry::class);
$fileRenderer = $rendererRegistry->getRenderer($this->fileObject);
$fileExtension = $this->fileObject->getExtension();
$preview['url'] = $this->fileObject->getPublicUrl(true);
$width = '590m';
$heigth = '400m';
// Check if there is a FileRenderer
if ($fileRenderer !== null) {
$preview['fileRenderer'] = $fileRenderer->render(
$this->fileObject,
$width,
$heigth,
[],
true
);
// else check if we can create an Image preview
} elseif (GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], $fileExtension)) {
$preview['fileObject'] = $this->fileObject;
$preview['width'] = $width;
$preview['heigth'] = $heigth;
}
}
return $preview;
}
/**
* Get property array for html table
*
* @return array
*/
protected function getPropertiesForTable(): array
{
$propertiesForTable = [];
$lang = $this->getLanguageService();
$extraFields = [
'uid' => htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:show_item.php.uid'))
];
if (in_array($this->type, ['folder', 'file'], true)) {
if ($this->type === 'file') {
$extraFields['creation_date'] = htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.creationDate'));
$extraFields['modification_date'] = htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.timestamp'));
if ($this->fileObject->getType() === AbstractFile::FILETYPE_IMAGE) {
$extraFields['width'] = htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.width'));
$extraFields['height'] = htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.height'));
}
}
$extraFields['storage'] = htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file.storage'));
$extraFields['folder'] = htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:folder'));
} else {
$extraFields['crdate'] = htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.creationDate'));
$extraFields['cruser_id'] = htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.creationUserId'));
$extraFields['tstamp'] = htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.timestamp'));
// check if the special fields are defined in the TCA ctrl section of the table
foreach ($extraFields as $fieldName => $fieldLabel) {
if (isset($GLOBALS['TCA'][$this->table]['ctrl'][$fieldName])) {
$extraFields[$GLOBALS['TCA'][$this->table]['ctrl'][$fieldName]] = $fieldLabel;
} elseif ($fieldName !== 'uid') {
unset($extraFields[$fieldName]);
}
}
}
foreach ($extraFields as $name => $fieldLabel) {
$rowValue = '';
$thisRow = [];
if (!isset($this->row[$name])) {
$resourceObject = $this->fileObject ?: $this->folderObject;
if ($name === 'storage') {
$rowValue = $resourceObject->getStorage()->getName();
} elseif ($name === 'folder') {
$rowValue = $resourceObject->getParentFolder()->getReadablePath();
} elseif ($name === 'width') {
$rowValue = $this->fileObject->getProperty('width') . 'px';
} elseif ($name === 'height') {
$rowValue = $this->fileObject->getProperty('height') . 'px';
}
} elseif ($name === 'creation_date' || $name === 'modification_date' || $name === 'tstamp' || $name === 'crdate') {
$rowValue = BackendUtility::datetime($this->row[$name]);
} else {
$rowValue = BackendUtility::getProcessedValueExtra($this->table, $name, $this->row[$name]);
}
$thisRow['value'] = $rowValue;
$thisRow['fieldLabel'] = rtrim($fieldLabel, ':');
// show the backend username who created the issue
if ($name === 'cruser_id' && $rowValue) {
$creatorRecord = BackendUtility::getRecord('be_users', $rowValue);
if ($creatorRecord) {
/** @var Avatar $avatar */
$avatar = GeneralUtility::makeInstance(Avatar::class);
$creatorRecord['icon'] = $avatar->render($creatorRecord);
$thisRow['creatorRecord'] = $creatorRecord;
$thisRow['value'] = '';
}
}
$propertiesForTable['extraFields'][] = $thisRow;
}
// Traverse the list of fields to display for the record:
$fieldList = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$this->table]['interface']['showRecordFieldList'], true);
foreach ($fieldList as $name) {
$thisRow = [];
$name = trim($name);
$uid = $this->row['uid'];
if (!isset($GLOBALS['TCA'][$this->table]['columns'][$name])) {
continue;
}
// Storage is already handled above
if ($this->type === 'file' && $name === 'storage') {
continue;
}
// format file size as bytes/kilobytes/megabytes
if ($this->type === 'file' && $name === 'size') {
$this->row[$name] = GeneralUtility::formatSize($this->row[$name], htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:byteSizeUnits')));
}
$isExcluded = !(!$GLOBALS['TCA'][$this->table]['columns'][$name]['exclude'] || $this->getBackendUser()->check('non_exclude_fields', $this->table . ':' . $name));
if ($isExcluded) {
continue;
}
$thisRow['fieldValue'] = BackendUtility::getProcessedValue($this->table, $name, $this->row[$name], 0, 0, false, $uid);
$thisRow['fieldLabel'] = htmlspecialchars($lang->sL(BackendUtility::getItemLabel($this->table, $name)));
$propertiesForTable['fields'][] = $thisRow;
}
return $propertiesForTable;
}
/**
* Get references section (references from and references to current record)
*
* @param ServerRequestInterface $request
* @return array
*/
protected function getReferences(ServerRequestInterface $request): array
{
$references = [];
switch ($this->type) {
case 'db': {
$references['refLines'] = $this->makeRef($this->table, $this->row['uid'], $request);
$references['refFromLines'] = $this->makeRefFrom($this->table, $this->row['uid'], $request);
break;
}
case 'file': {
if ($this->fileObject && $this->fileObject->isIndexed()) {
$references['refLines'] = $this->makeRef('_FILE', $this->fileObject, $request);
}
break;
}
}
return $references;
}
/**
* Get field name for specified table/column name
*
* @param string $tableName Table name
* @param string $fieldName Column name
* @return string label
*/
protected function getLabelForTableColumn($tableName, $fieldName)
{
if ($GLOBALS['TCA'][$tableName]['columns'][$fieldName]['label'] !== null) {
$field = $this->getLanguageService()->sL($GLOBALS['TCA'][$tableName]['columns'][$fieldName]['label']);
if (trim($field) === '') {
$field = $fieldName;
}
} else {
$field = $fieldName;
}
return $field;
}
/**
* Returns the record actions
*
* @param string $table
* @param int $uid
* @param ServerRequestInterface $request
* @return array
*/
protected function getRecordActions($table, $uid, ServerRequestInterface $request): array
{
if ($table === '' || $uid < 0) {
return [];
}
$actions = [];
// Edit button
$urlParameters = [
'edit' => [
$table => [
$uid => 'edit'
]
],
'returnUrl' => $request->getAttribute('normalizedParams')->getRequestUri()
];
/** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
$uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
$actions['recordEditUrl'] = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
// History button
$urlParameters = [
'element' => $table . ':' . $uid,
'returnUrl' => $request->getAttribute('normalizedParams')->getRequestUri()
];
$actions['recordHistoryUrl'] = (string)$uriBuilder->buildUriFromRoute('record_history', $urlParameters);
if ($table === 'pages') {
// Recordlist button
$actions['webListUrl'] = (string)$uriBuilder->buildUriFromRoute('web_list', ['id' => $uid, 'returnUrl' => $request->getAttribute('normalizedParams')->getRequestUri()]);
// View page button
$actions['viewOnClick'] = BackendUtility::viewOnClick($uid, '', BackendUtility::BEgetRootLine($uid));
}
return $actions;
}
/**
* Make reference display
*
* @param string $table Table name
* @param string|\TYPO3\CMS\Core\Resource\File $ref Filename or uid
* @param ServerRequestInterface $request
* @return array
*/
protected function makeRef($table, $ref, ServerRequestInterface $request)
{
$refLines = [];
$lang = $this->getLanguageService();
// Files reside in sys_file table
if ($table === '_FILE') {
$selectTable = 'sys_file';
$selectUid = $ref->getUid();
} else {
$selectTable = $table;
$selectUid = $ref;
}
/** @var \TYPO3\CMS\Core\Database\Query\QueryBuilder $queryBuilder */
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_refindex');
$predicates = [
$queryBuilder->expr()->eq(
'ref_table',
$queryBuilder->createNamedParameter($selectTable, \PDO::PARAM_STR)
),
$queryBuilder->expr()->eq(
'ref_uid',
$queryBuilder->createNamedParameter($selectUid, \PDO::PARAM_INT)
),
$queryBuilder->expr()->eq(
'deleted',
$queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
)
];
$backendUser = $this->getBackendUser();
if (!$backendUser->isAdmin()) {
$allowedSelectTables = GeneralUtility::trimExplode(',', $backendUser->groupData['tables_select']);
$predicates[] = $queryBuilder->expr()->in(
'tablename',
$queryBuilder->createNamedParameter($allowedSelectTables, Connection::PARAM_STR_ARRAY)
);
}
$rows = $queryBuilder
->select('*')
->from('sys_refindex')
->where(...$predicates)
->execute()
->fetchAll();
// Compile information for title tag:
foreach ($rows as $row) {
if ($row['tablename'] === 'sys_file_reference') {
$row = $this->transformFileReferenceToRecordReference($row);
if ($row['tablename'] === null || $row['recuid'] === null) {
return;
}
}
$line = [];
$record = BackendUtility::getRecord($row['tablename'], $row['recuid']);
if ($record) {
BackendUtility::fixVersioningPid($row['tablename'], $record);
if (!$this->canAccessPage($row['tablename'], $record)) {
continue;
}
$parentRecord = BackendUtility::getRecord('pages', $record['pid']);
$parentRecordTitle = is_array($parentRecord)
? BackendUtility::getRecordTitle('pages', $parentRecord)
: '';
$urlParameters = [
'edit' => [
$row['tablename'] => [
$row['recuid'] => 'edit'
]
],
'returnUrl' => $request->getAttribute('normalizedParams')->getRequestUri()
];
/** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
$uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
$url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
$line['url'] = $url;
$line['icon'] = $this->iconFactory->getIconForRecord($row['tablename'], $record, Icon::SIZE_SMALL)->render();
$line['row'] = $row;
$line['record'] = $record;
$line['recordTitle'] = BackendUtility::getRecordTitle($row['tablename'], $record, false, true);
$line['parentRecordTitle'] = $parentRecordTitle;
$line['title'] = $lang->sL($GLOBALS['TCA'][$row['tablename']]['ctrl']['title']);
$line['labelForTableColumn'] = $this->getLabelForTableColumn($row['tablename'], $row['field']);
$line['path'] = BackendUtility::getRecordPath($record['pid'], '', 0, 0);
$line['actions'] = $this->getRecordActions($row['tablename'], $row['recuid'], $request);
} else {
$line['row'] = $row;
$line['title'] = $lang->sL($GLOBALS['TCA'][$row['tablename']]['ctrl']['title']) ?: $row['tablename'];
$line['labelForTableColumn'] = $this->getLabelForTableColumn($row['tablename'], $row['field']);
}
$refLines[] = $line;
}
return $refLines;
}
/**
* Make reference display (what this elements points to)
*
* @param string $table Table name
* @param string $ref Filename or uid
* @param ServerRequestInterface $request
* @return array
*/
protected function makeRefFrom($table, $ref, ServerRequestInterface $request): array
{
$refFromLines = [];
$lang = $this->getLanguageService();
/** @var \TYPO3\CMS\Core\Database\Query\QueryBuilder $queryBuilder */
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_refindex');
$predicates = [
$queryBuilder->expr()->eq(
'tablename',
$queryBuilder->createNamedParameter($table, \PDO::PARAM_STR)
),
$queryBuilder->expr()->eq(
'recuid',
$queryBuilder->createNamedParameter($ref, \PDO::PARAM_INT)
)
];
$backendUser = $this->getBackendUser();
if (!$backendUser->isAdmin()) {
$allowedSelectTables = GeneralUtility::trimExplode(',', $backendUser->groupData['tables_select']);
$predicates[] = $queryBuilder->expr()->in(
'ref_table',
$queryBuilder->createNamedParameter($allowedSelectTables, Connection::PARAM_STR_ARRAY)
);
}
$rows = $queryBuilder
->select('*')
->from('sys_refindex')
->where(...$predicates)
->execute()
->fetchAll();
// Compile information for title tag:
foreach ($rows as $row) {
$line = [];
$record = BackendUtility::getRecord($row['ref_table'], $row['ref_uid']);
if ($record) {
BackendUtility::fixVersioningPid($row['ref_table'], $record);
if (!$this->canAccessPage($row['ref_table'], $record)) {
continue;
}
$urlParameters = [
'edit' => [
$row['ref_table'] => [
$row['ref_uid'] => 'edit'
]
],
'returnUrl' => $request->getAttribute('normalizedParams')->getRequestUri()
];
/** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
$uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
$url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
$line['url'] = $url;
$line['icon'] = $this->iconFactory->getIconForRecord($row['tablename'], $record, Icon::SIZE_SMALL)->render();
$line['row'] = $row;
$line['record'] = $record;
$line['recordTitle'] = BackendUtility::getRecordTitle($row['ref_table'], $record, false, true);
$line['title'] = $lang->sL($GLOBALS['TCA'][$row['ref_table']]['ctrl']['title']);
$line['labelForTableColumn'] = $this->getLabelForTableColumn($table, $row['field']);
$line['path'] = BackendUtility::getRecordPath($record['pid'], '', 0, 0);
$line['actions'] = $this->getRecordActions($row['ref_table'], $row['ref_uid'], $request);
} else {
$line['row'] = $row;
$line['title'] = $lang->sL($GLOBALS['TCA'][$row['ref_table']]['ctrl']['title']);
$line['labelForTableColumn'] = $this->getLabelForTableColumn($table, $row['field']);
}
$refFromLines[] = $line;
}
return $refFromLines;
}
/**
* Convert FAL file reference (sys_file_reference) to reference index (sys_refindex) table format
*
* @param array $referenceRecord
* @return array
*/
protected function transformFileReferenceToRecordReference(array $referenceRecord)
{
/** @var \TYPO3\CMS\Core\Database\Query\QueryBuilder $queryBuilder */
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$queryBuilder->getRestrictions()->removeAll();
$fileReference = $queryBuilder
->select('*')
->from('sys_file_reference')
->where(
$queryBuilder->expr()->eq(
'uid',
$queryBuilder->createNamedParameter($referenceRecord['recuid'], \PDO::PARAM_INT)
)
)
->execute()
->fetch();
return [
'recuid' => $fileReference['uid_foreign'],
'tablename' => $fileReference['tablenames'],
'field' => $fileReference['fieldname'],
'flexpointer' => '',
'softref_key' => '',
'sorting' => $fileReference['sorting_foreign']
];
}
/**
* @param string $tableName Name of the table
* @param array $record Record to be checked (ensure pid is resolved for workspaces)
* @return bool
*/
protected function canAccessPage(string $tableName, array $record): bool
{
$recordPid = (int)($tableName === 'pages' ? $record['uid'] : $record['pid']);
return $this->getBackendUser()->isInWebMount($tableName === 'pages' ? $record : $record['pid'])
|| $recordPid === 0 && !empty($GLOBALS['TCA'][$tableName]['ctrl']['security']['ignoreRootLevelRestriction']);
}
/**
* Returns LanguageService
*
* @return \TYPO3\CMS\Core\Localization\LanguageService
*/
protected function getLanguageService()
{
return $GLOBALS['LANG'];
}
/**
* Returns the current BE user.
*
* @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
*/
protected function getBackendUser()
{
return $GLOBALS['BE_USER'];
}
}
@@ -0,0 +1,346 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Backend\Controller\ContentElement;
/*
* 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 Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\Template\ModuleTemplate;
use TYPO3\CMS\Backend\Tree\View\ContentMovingPagePositionMap;
use TYPO3\CMS\Backend\Tree\View\PageMovingPagePositionMap;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Compatibility\PublicPropertyDeprecationTrait;
use TYPO3\CMS\Core\Http\HtmlResponse;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Type\Bitmask\Permission;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Fluid\View\StandaloneView;
/**
* Script Class for rendering the move-element wizard display
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
*/
class MoveElementController
{
use PublicPropertyDeprecationTrait;
/**
* Properties which have been moved to protected status from public
*
* @var array
*/
protected $deprecatedPublicProperties = [
'sys_language' => 'Using $sys_language of MoveElementController from the outside is discouraged, as this variable is used for internal storage.',
'page_id' => 'Using $page_id of MoveElementController from the outside is discouraged, as this variable is used for internal storage.',
'table' => 'Using $table of MoveElementController from the outside is discouraged, as this variable is used for internal storage.',
'R_URI' => 'Using $R_URI of MoveElementController from the outside is discouraged, as this variable is used for internal storage.',
'input_moveUid' => 'Using $input_moveUid of MoveElementController from the outside is discouraged, as this variable is used for internal storage.',
'moveUid' => 'Using $moveUid of MoveElementController from the outside is discouraged, as this variable is used for internal storage.',
'makeCopy' => 'Using $makeCopy of MoveElementController from the outside is discouraged, as this variable is used for internal storage.',
'perms_clause' => 'Using $perms_clause of MoveElementController from the outside is discouraged, as this variable is used for internal storage.',
'content' => 'Using $content of MoveElementController from the outside is discouraged, as this variable is used for internal storage.',
];
/**
* @var int
*/
protected $sys_language = 0;
/**
* @var int
*/
protected $page_id;
/**
* @var string
*/
protected $table;
/**
* @var string
*/
protected $R_URI;
/**
* @var int
*/
protected $input_moveUid;
/**
* @var int
*/
protected $moveUid;
/**
* @var int
*/
protected $makeCopy;
/**
* Pages-select clause
*
* @var string
*/
protected $perms_clause;
/**
* Content for module accumulated here.
*
* @var string
*/
protected $content;
/**
* ModuleTemplate object
*
* @var ModuleTemplate
*/
protected $moduleTemplate;
/**
* Constructor
*/
public function __construct()
{
$this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
$this->getLanguageService()->includeLLFile('EXT:core/Resources/Private/Language/locallang_misc.xlf');
// @deprecated since TYPO3 v9, will be obsolete in TYPO3 v10.0 with removal of init()
$request = $GLOBALS['TYPO3_REQUEST'];
// @deprecated since TYPO3 v9, will be moved out of __construct() in TYPO3 v10.0
$this->init($request);
}
/**
* 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->renderContent();
return new HtmlResponse($this->content);
}
/**
* Constructor, initializing internal variables.
*
* @param ServerRequestInterface|null $request
*/
public function init(ServerRequestInterface $request = null)
{
if ($request === null) {
// Method signature in TYPO3 v10.0: protected function init(ServerRequestInterface $request)
trigger_error('MoveElementController->init() will be set to protected in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
$request = $GLOBALS['TYPO3_REQUEST'];
}
$parsedBody = $request->getParsedBody();
$queryParams = $request->getQueryParams();
// Setting internal vars:
$this->sys_language = (int)($parsedBody['sys_language'] ?? $queryParams['sys_language'] ?? 0);
$this->page_id = (int)($parsedBody['uid'] ?? $queryParams['uid'] ?? 0);
$this->table = $parsedBody['table'] ?? $queryParams['table'] ?? null;
$this->R_URI = GeneralUtility::sanitizeLocalUrl($parsedBody['returnUrl'] ?? $queryParams['returnUrl'] ?? '');
$this->input_moveUid = $parsedBody['moveUid'] ?? $queryParams['moveUid'] ?? null;
$this->moveUid = $this->input_moveUid ? $this->input_moveUid : $this->page_id;
$this->makeCopy = $parsedBody['makeCopy'] ?? $queryParams['makeCopy'] ?? 0;
// Select-pages where clause for read-access:
$this->perms_clause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW);
}
/**
* Creating the module output.
*/
protected function renderContent(): void
{
$lang = $this->getLanguageService();
if ($this->page_id) {
$assigns = [];
$backendUser = $this->getBackendUser();
$this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/Tooltip');
// Get record for element:
$elRow = BackendUtility::getRecordWSOL($this->table, $this->moveUid);
// Headerline: Icon, record title:
$assigns['table'] = $this->table;
$assigns['elRow'] = $elRow;
$assigns['recordTooltip'] = BackendUtility::getRecordToolTip($elRow, $this->table);
$assigns['recordTitle'] = BackendUtility::getRecordTitle($this->table, $elRow, true);
// Make-copy checkbox (clicking this will reload the page with the GET var makeCopy set differently):
$onClick = 'window.location.href=' . GeneralUtility::quoteJSvalue(GeneralUtility::linkThisScript(['makeCopy' => !$this->makeCopy])) . ';';
$assigns['makeCopyChecked'] = $this->makeCopy ? ' checked="checked"' : '';
$assigns['makeCopyOnClick'] = $onClick;
// IF the table is "pages":
if ((string)$this->table === 'pages') {
// Get page record (if accessible):
$pageInfo = BackendUtility::readPageAccess($this->page_id, $this->perms_clause);
if (is_array($pageInfo) && $backendUser->isInWebMount($pageInfo['pid'], $this->perms_clause)) {
// Initialize the position map:
$posMap = GeneralUtility::makeInstance(PageMovingPagePositionMap::class);
$posMap->moveOrCopy = $this->makeCopy ? 'copy' : 'move';
$posMap->moveUid = $this->moveUid;
// Print a "go-up" link IF there is a real parent page (and if the user has read-access to that page).
if ($pageInfo['pid']) {
$pidPageInfo = BackendUtility::readPageAccess($pageInfo['pid'], $this->perms_clause);
if (is_array($pidPageInfo)) {
if ($backendUser->isInWebMount($pidPageInfo['pid'], $this->perms_clause)) {
$assigns['pages']['goUpUrl'] = GeneralUtility::linkThisScript([
'uid' => (int)$pageInfo['pid'],
'moveUid' => $this->moveUid
]);
} else {
$assigns['pages']['pidPageInfo'] = $pidPageInfo;
}
$assigns['pages']['pidRecordTitle'] = BackendUtility::getRecordTitle('pages', $pidPageInfo, true);
}
}
// Create the position tree:
$assigns['pages']['positionTree'] = $posMap->positionTree($this->page_id, $pageInfo, $this->perms_clause, $this->R_URI);
}
}
// IF the table is "tt_content":
if ((string)$this->table === 'tt_content') {
// First, get the record:
$tt_content_rec = BackendUtility::getRecord('tt_content', $this->moveUid);
// ?
if (!$this->input_moveUid) {
$this->page_id = $tt_content_rec['pid'];
}
// Checking if the parent page is readable:
$pageInfo = BackendUtility::readPageAccess($this->page_id, $this->perms_clause);
if (is_array($pageInfo) && $backendUser->isInWebMount($pageInfo['pid'], $this->perms_clause)) {
// Initialize the position map:
$posMap = GeneralUtility::makeInstance(ContentMovingPagePositionMap::class);
$posMap->moveOrCopy = $this->makeCopy ? 'copy' : 'move';
$posMap->moveUid = $this->moveUid;
$posMap->cur_sys_language = $this->sys_language;
// Headerline for the parent page: Icon, record title:
$assigns['ttContent']['pageInfo'] = $pageInfo;
$assigns['ttContent']['recordTooltip'] = BackendUtility::getRecordToolTip($pageInfo, 'pages');
$assigns['ttContent']['recordTitle'] = BackendUtility::getRecordTitle('pages', $pageInfo, true);
$colPosArray = GeneralUtility::callUserFunction(\TYPO3\CMS\Backend\View\BackendLayoutView::class . '->getColPosListItemsParsed', $this->page_id, $this);
$colPosIds = [];
foreach ($colPosArray as $colPos) {
$colPosIds[] = $colPos[1];
}
// Removing duplicates, if any
$colPosList = implode(',', array_unique($colPosIds));
// Adding parent page-header and the content element columns from position-map:
$assigns['ttContent']['contentElementColumns'] = $posMap->printContentElementColumns($this->page_id, $this->moveUid, $colPosList, 1, $this->R_URI);
// Print a "go-up" link IF there is a real parent page (and if the user has read-access to that page).
if ($pageInfo['pid']) {
$pidPageInfo = BackendUtility::readPageAccess($pageInfo['pid'], $this->perms_clause);
if (is_array($pidPageInfo)) {
if ($backendUser->isInWebMount($pidPageInfo['pid'], $this->perms_clause)) {
$assigns['ttContent']['goUpUrl'] = GeneralUtility::linkThisScript([
'uid' => (int)$pageInfo['pid'],
'moveUid' => $this->moveUid
]);
} else {
$assigns['ttContent']['pidPageInfo'] = $pidPageInfo;
}
$assigns['ttContent']['pidRecordTitle'] = BackendUtility::getRecordTitle('pages', $pidPageInfo, true);
}
}
// Create the position tree (for pages):
$assigns['ttContent']['positionTree'] = $posMap->positionTree($this->page_id, $pageInfo, $this->perms_clause, $this->R_URI);
}
}
// Rendering of the output via fluid
$view = GeneralUtility::makeInstance(StandaloneView::class);
$view->setTemplateRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates')]);
$view->setPartialRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Partials')]);
$view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName(
'EXT:backend/Resources/Private/Templates/ContentElement/MoveElement.html'
));
$view->assignMultiple($assigns);
$this->content .= $view->render();
}
// Setting up the buttons and markers for docheader
$this->getButtons();
// Build the <body> for the module
$this->moduleTemplate->setTitle($lang->getLL('movingElement'));
$this->moduleTemplate->setContent($this->content);
$this->content = $this->moduleTemplate->renderContent();
}
/**
* Creating the module output.
*
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
*/
public function main()
{
trigger_error('MoveElementController->main() will be replaced by protected method renderContent() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
$this->renderContent();
}
/**
* Create the panel of buttons for submitting the form or otherwise perform operations.
*/
protected function getButtons()
{
$buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
if ($this->page_id) {
if ((string)$this->table === 'pages') {
$cshButton = $buttonBar->makeHelpButton()
->setModuleName('xMOD_csh_corebe')
->setFieldName('move_el_pages');
$buttonBar->addButton($cshButton);
} elseif ((string)$this->table === 'tt_content') {
$cshButton = $buttonBar->makeHelpButton()
->setModuleName('xMOD_csh_corebe')
->setFieldName('move_el_cs');
$buttonBar->addButton($cshButton);
}
if ($this->R_URI) {
$backButton = $buttonBar->makeLinkButton()
->setHref($this->R_URI)
->setTitle($this->getLanguageService()->getLL('goBack'))
->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
'actions-view-go-back',
Icon::SIZE_SMALL
));
$buttonBar->addButton($backButton);
}
}
}
/**
* @return LanguageService
*/
protected function getLanguageService(): LanguageService
{
return $GLOBALS['LANG'];
}
/**
* @return BackendUserAuthentication
*/
protected function getBackendUser(): BackendUserAuthentication
{
return $GLOBALS['BE_USER'];
}
}
@@ -0,0 +1,744 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Backend\Controller\ContentElement;
/*
* 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 Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\Template\DocumentTemplate;
use TYPO3\CMS\Backend\Template\ModuleTemplate;
use TYPO3\CMS\Backend\Tree\View\ContentCreationPagePositionMap;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Backend\View\BackendLayoutView;
use TYPO3\CMS\Backend\Wizard\NewContentElementWizardHookInterface;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Compatibility\PublicPropertyDeprecationTrait;
use TYPO3\CMS\Core\Http\HtmlResponse;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Service\DependencyOrderingService;
use TYPO3\CMS\Core\Type\Bitmask\Permission;
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\HttpUtility;
use TYPO3\CMS\Fluid\View\StandaloneView;
/**
* Script Class for the New Content element wizard
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
*/
class NewContentElementController
{
use PublicPropertyDeprecationTrait;
/**
* @var array
*/
protected $deprecatedPublicProperties = [
'id' => 'Using $id of NewContentElementController from the outside is discouraged as this variable is used for internal storage.',
'sys_language' => 'Using $sys_language of NewContentElementController from the outside is discouraged as this variable is used for internal storage.',
'R_URI' => 'Using $R_URI of NewContentElementController from the outside is discouraged as this variable is used for internal storage.',
'colPos' => 'Using $colPos of NewContentElementController from the outside is discouraged as this variable is used for internal storage.',
'uid_pid' => 'Using $uid_pid of NewContentElementController from the outside is discouraged as this variable is used for internal storage.',
'modTSconfig' => 'Using $modTSconfig of NewContentElementController from the outside is discouraged as this variable is used for internal storage.',
'doc' => 'Using $doc of NewContentElementController from the outside is discouraged as this variable is used for internal storage.',
'content' => 'Using $content of NewContentElementController from the outside is discouraged as this variable is used for internal storage.',
'access' => 'Using $access of NewContentElementController from the outside is discouraged as this variable is used for internal storage.',
'config' => 'Using $config of NewContentElementController from the outside is discouraged as this variable is used for internal storage.',
];
/**
* Page id
*
* @var int
*/
protected $id;
/**
* Sys language
*
* @var int
*/
protected $sys_language = 0;
/**
* Return URL.
*
* @var string
*/
protected $R_URI = '';
/**
* If set, the content is destined for a specific column.
*
* @var int|null
*/
protected $colPos;
/**
* @var int
*/
protected $uid_pid;
/**
* Module TSconfig.
*
* @var array
*/
protected $modTSconfig = [];
/**
* Internal backend template object
*
* @var DocumentTemplate
*/
protected $doc;
/**
* Used to accumulate the content of the module.
*
* @var string
*/
protected $content;
/**
* Access boolean.
*
* @var bool
*/
protected $access;
/**
* config of the wizard
*
* @var array
*/
protected $config;
/**
* @var array
*/
protected $pageInfo;
/**
* @var string
*/
protected $onClickEvent;
/**
* @var array
*/
protected $MCONF;
/**
* @var StandaloneView
*/
protected $view;
/**
* @var StandaloneView
*/
protected $menuItemView;
/**
* ModuleTemplate object
*
* @var ModuleTemplate
*/
protected $moduleTemplate;
/**
* Constructor
*/
public function __construct()
{
$this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
$GLOBALS['SOBE'] = $this;
$this->view = $this->getFluidTemplateObject();
$this->menuItemView = $this->getFluidTemplateObject('MenuItem.html');
// @deprecated since TYPO3 v9, will be obsolete in TYPO3 v10.0 with removal of init()
$request = $GLOBALS['TYPO3_REQUEST'];
// @deprecated since TYPO3 v9, will be moved out of __construct() in TYPO3 v10.0
$this->init($request);
}
/**
* Constructor, initializing internal variables.
*
* @param ServerRequestInterface|null $request
*/
public function init(ServerRequestInterface $request = null)
{
if ($request === null) {
// Method signature in TYPO3 v10.0: protected function init(ServerRequestInterface $request)
trigger_error('NewContentElementController->init() will be set to protected in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
$request = $GLOBALS['TYPO3_REQUEST'];
}
$lang = $this->getLanguageService();
$lang->includeLLFile('EXT:core/Resources/Private/Language/locallang_misc.xlf');
$LOCAL_LANG_orig = $GLOBALS['LOCAL_LANG'];
$lang->includeLLFile('EXT:backend/Resources/Private/Language/locallang_db_new_content_el.xlf');
ArrayUtility::mergeRecursiveWithOverrule($LOCAL_LANG_orig, $GLOBALS['LOCAL_LANG']);
$GLOBALS['LOCAL_LANG'] = $LOCAL_LANG_orig;
$parsedBody = $request->getParsedBody();
$queryParams = $request->getQueryParams();
// Setting internal vars:
$this->id = (int)($parsedBody['id'] ?? $queryParams['id'] ?? 0);
$this->sys_language = (int)($parsedBody['sys_language_uid'] ?? $queryParams['sys_language_uid'] ?? 0);
$this->R_URI = GeneralUtility::sanitizeLocalUrl($parsedBody['returnUrl'] ?? $queryParams['returnUrl'] ?? '');
$colPos = $parsedBody['colPos'] ?? $queryParams['colPos'] ?? null;
$this->colPos = $colPos === null ? null : (int)$colPos;
$this->uid_pid = (int)($parsedBody['uid_pid'] ?? $queryParams['uid_pid'] ?? 0);
$this->MCONF['name'] = 'xMOD_db_new_content_el';
$this->modTSconfig['properties'] = BackendUtility::getPagesTSconfig($this->id)['mod.']['wizards.']['newContentElement.'] ?? [];
$config = BackendUtility::getPagesTSconfig($this->id);
$this->config = $config['mod.']['wizards.']['newContentElement.'];
// Starting the document template object:
// We keep this here in case somebody relies on it in a hook or alike
$this->doc = GeneralUtility::makeInstance(DocumentTemplate::class);
// Setting up the context sensitive menu:
$this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
// Getting the current page and receiving access information (used in main())
$perms_clause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW);
$this->pageInfo = BackendUtility::readPageAccess($this->id, $perms_clause);
$this->access = is_array($this->pageInfo);
}
/**
* 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->prepareContent('window');
$this->moduleTemplate->setContent($this->content);
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
*
* @param ServerRequestInterface $request the current request
* @return ResponseInterface the response with the content
*/
public function wizardAction(ServerRequestInterface $request): ResponseInterface
{
$this->prepareContent('list_frame');
return new HtmlResponse($this->content);
}
/**
* Creating the module output.
*
* @deprecated since TYPO3 v9, will be removed in TYPO3v10 without substitute
*/
public function main()
{
trigger_error('NewContentElementController->main() will be replaced by protected method prepareContent() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
$this->prepareContent('window');
}
/**
* Returns the array of elements in the wizard display.
* For the plugin section there is support for adding elements there from a global variable.
*
* @return array
*/
public function wizardArray()
{
trigger_error('NewContentElementController->wizardArray() will be replaced by protected method getWizards() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
return $this->getWizards();
}
/**
* @param mixed $wizardElements
* @return array
*/
public function wizard_appendWizards($wizardElements)
{
trigger_error('NewContentElementController->wizard_appendWizards() will be replaced by protected method getAppendWizards() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
return $this->getAppendWizards($wizardElements);
}
/**
* @param string $groupKey Not used
* @param string $itemKey Not used
* @param array $itemConf
* @return array
*/
public function wizard_getItem($groupKey, $itemKey, $itemConf)
{
trigger_error('NewContentElementController->wizard_getItem() will be replaced by protected method getWizardItem() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
return $this->getWizardItem($groupKey, $itemKey, $itemConf);
}
/**
* @param string $groupKey Not used
* @param array $wizardGroup
* @return array
*/
public function wizard_getGroupHeader($groupKey, $wizardGroup)
{
trigger_error('NewContentElementController->wizard_getGroupHeader() will be replaced by protected method getWizardGroupHeader() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
return $this->getWizardGroupHeader($wizardGroup);
}
/**
* Checks the array for elements which might contain unallowed default values and will unset them!
* Looks for the "tt_content_defValues" key in each element and if found it will traverse that array as fieldname /
* value pairs and check.
* The values will be added to the "params" key of the array (which should probably be unset or empty by default).
*
* @param array $wizardItems Wizard items, passed by reference
*/
public function removeInvalidElements(&$wizardItems)
{
trigger_error('NewContentElementController->removeInvalidElements() will be replaced by protected method removeInvalidWizardItems() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
$this->removeInvalidWizardItems($wizardItems);
}
/**
* 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
{
$hasAccess = true;
if ($this->id && $this->access) {
// Init position map object:
$posMap = GeneralUtility::makeInstance(
ContentCreationPagePositionMap::class,
null,
$clientContext
);
$posMap->cur_sys_language = $this->sys_language;
// If a column is pre-set:
if (isset($this->colPos)) {
if ($this->uid_pid < 0) {
$row = [];
$row['uid'] = abs($this->uid_pid);
} else {
$row = '';
}
$this->onClickEvent = $posMap->onClickInsertRecord(
$row,
$this->colPos,
'',
$this->uid_pid,
$this->sys_language
);
} else {
$this->onClickEvent = '';
}
// ***************************
// Creating content
// ***************************
// Wizard
$wizardItems = $this->getWizards();
// Wrapper for wizards
// Hook for manipulating wizardItems, wrapper, onClickEvent etc.
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms']['db_new_content_el']['wizardItemsHook'] ?? [] as $className) {
$hookObject = GeneralUtility::makeInstance($className);
if (!$hookObject instanceof NewContentElementWizardHookInterface) {
throw new \UnexpectedValueException(
$className . ' must implement interface ' . NewContentElementWizardHookInterface::class,
1227834741
);
}
$hookObject->manipulateWizardItems($wizardItems, $this);
}
// Traverse items for the wizard.
// An item is either a header or an item rendered with a radio button and title/description and icon:
$cc = ($key = 0);
$menuItems = [];
$this->view->assignMultiple([
'hasClickEvent' => $this->onClickEvent !== '',
'onClickEvent' => 'function goToalt_doc() { ' . $this->onClickEvent . '}',
]);
foreach ($wizardItems as $wizardKey => $wInfo) {
$wizardOnClick = '';
if (isset($wInfo['header'])) {
$menuItems[] = [
'label' => $wInfo['header'] ?: '-',
'content' => ''
];
$key = count($menuItems) - 1;
} else {
if (!$this->onClickEvent) {
// Radio button:
$wizardOnClick = 'document.editForm.defValues.value=unescape(' . GeneralUtility::quoteJSvalue(rawurlencode($wInfo['params'])) . '); window.location.hash=\'#sel2\';';
// Onclick action for icon/title:
$aOnClick = 'document.getElementsByName(\'tempB\')[' . $cc . '].checked=1;' . $wizardOnClick . 'return false;';
} else {
$aOnClick = "document.editForm.defValues.value=unescape('" . rawurlencode($wInfo['params']) . "');goToalt_doc();";
}
$icon = $this->moduleTemplate->getIconFactory()->getIcon($wInfo['iconIdentifier'])->render();
$this->menuItemView->assignMultiple([
'onClickEvent' => $this->onClickEvent,
'aOnClick' => $aOnClick,
'wizardInformation' => $wInfo,
'icon' => $icon,
'wizardOnClick' => $wizardOnClick,
'wizardKey' => $wizardKey
]);
$menuItems[$key]['content'] .= $this->menuItemView->render();
$cc++;
}
}
$this->view->assign('renderedTabs', $this->moduleTemplate->getDynamicTabMenu(
$menuItems,
'new-content-element-wizard'
));
// If the user must also select a column:
if (!$this->onClickEvent) {
// Load SHARED page-TSconfig settings and retrieve column list from there, if applicable:
$colPosArray = GeneralUtility::callUserFunction(
BackendLayoutView::class . '->getColPosListItemsParsed',
$this->id,
$this
);
$colPosIds = array_column($colPosArray, 1);
// Removing duplicates, if any
$colPosList = implode(',', array_unique(array_map('intval', $colPosIds)));
// Finally, add the content of the column selector to the content:
$this->view->assign('posMap', $posMap->printContentElementColumns($this->id, 0, $colPosList, 1, $this->R_URI));
}
} else {
// In case of no access:
$hasAccess = false;
}
$this->view->assign('hasAccess', $hasAccess);
$this->content = $this->view->render();
// Setting up the buttons and markers for docheader
$this->getButtons();
}
/**
* Create the panel of buttons for submitting the form or otherwise perform operations.
*/
protected function getButtons()
{
$buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
if ($this->R_URI) {
$backButton = $buttonBar->makeLinkButton()
->setHref($this->R_URI)
->setTitle($this->getLanguageService()->getLL('goBack'))
->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
'actions-view-go-back',
Icon::SIZE_SMALL
));
$buttonBar->addButton($backButton);
}
$cshButton = $buttonBar->makeHelpButton()->setModuleName('xMOD_csh_corebe')->setFieldName('new_ce');
$buttonBar->addButton($cshButton);
}
/**
* Returns the array of elements in the wizard display.
* For the plugin section there is support for adding elements there from a global variable.
*
* @return array
*/
protected function getWizards(): array
{
$wizardItems = [];
if (is_array($this->config)) {
$wizards = $this->config['wizardItems.'] ?? [];
$appendWizards = $this->getAppendWizards($wizards['elements.'] ?? []);
if (is_array($wizards)) {
foreach ($wizards as $groupKey => $wizardGroup) {
$this->prepareDependencyOrdering($wizards[$groupKey], 'before');
$this->prepareDependencyOrdering($wizards[$groupKey], 'after');
}
$wizards = GeneralUtility::makeInstance(DependencyOrderingService::class)->orderByDependencies($wizards);
foreach ($wizards as $groupKey => $wizardGroup) {
$groupKey = rtrim($groupKey, '.');
$showItems = GeneralUtility::trimExplode(',', $wizardGroup['show'], true);
$showAll = in_array('*', $showItems, true);
$groupItems = [];
if (is_array($appendWizards[$groupKey . '.']['elements.'])) {
$wizardElements = array_merge((array)$wizardGroup['elements.'], $appendWizards[$groupKey . '.']['elements.']);
} else {
$wizardElements = $wizardGroup['elements.'];
}
if (is_array($wizardElements)) {
foreach ($wizardElements as $itemKey => $itemConf) {
$itemKey = rtrim($itemKey, '.');
if ($showAll || in_array($itemKey, $showItems)) {
$tmpItem = $this->getWizardItem($groupKey, $itemKey, $itemConf);
if ($tmpItem) {
$groupItems[$groupKey . '_' . $itemKey] = $tmpItem;
}
}
}
}
if (!empty($groupItems)) {
$wizardItems[$groupKey] = $this->getWizardGroupHeader($wizardGroup);
$wizardItems = array_merge($wizardItems, $groupItems);
}
}
}
}
// Remove elements where preset values are not allowed:
$this->removeInvalidWizardItems($wizardItems);
return $wizardItems;
}
/**
* @param array $wizardElements
* @return array
*/
protected function getAppendWizards(array $wizardElements): array
{
if (!is_array($wizardElements)) {
$wizardElements = [];
}
if (is_array($GLOBALS['TBE_MODULES_EXT']['xMOD_db_new_content_el']['addElClasses'])) {
foreach ($GLOBALS['TBE_MODULES_EXT']['xMOD_db_new_content_el']['addElClasses'] as $class => $path) {
if (!class_exists($class) && file_exists($path)) {
require_once $path;
}
$modObj = GeneralUtility::makeInstance($class);
if (method_exists($modObj, 'proc')) {
$wizardElements = $modObj->proc($wizardElements);
}
}
}
$returnElements = [];
foreach ($wizardElements as $key => $wizardItem) {
preg_match('/^[a-zA-Z0-9]+_/', $key, $group);
$wizardGroup = $group[0] ? substr($group[0], 0, -1) . '.' : $key;
$returnElements[$wizardGroup]['elements.'][substr($key, strlen($wizardGroup)) . '.'] = $wizardItem;
}
return $returnElements;
}
/**
* @param string $groupKey Not used
* @param string $itemKey Not used
* @param array $itemConf
* @return array
*/
protected function getWizardItem(string $groupKey, string $itemKey, array $itemConf): array
{
$itemConf['title'] = $this->getLanguageService()->sL($itemConf['title']);
$itemConf['description'] = $this->getLanguageService()->sL($itemConf['description']);
$itemConf['tt_content_defValues'] = $itemConf['tt_content_defValues.'];
unset($itemConf['tt_content_defValues.']);
return $itemConf;
}
/**
* @param array $wizardGroup
* @return array
*/
protected function getWizardGroupHeader(array $wizardGroup): array
{
return [
'header' => $this->getLanguageService()->sL($wizardGroup['header'])
];
}
/**
* Checks the array for elements which might contain unallowed default values and will unset them!
* Looks for the "tt_content_defValues" key in each element and if found it will traverse that array as fieldname /
* value pairs and check.
* The values will be added to the "params" key of the array (which should probably be unset or empty by default).
*
* @param array $wizardItems Wizard items, passed by reference
*/
protected function removeInvalidWizardItems(array &$wizardItems): void
{
// Get TCEFORM from TSconfig of current page
$row = ['pid' => $this->id];
$TCEFORM_TSconfig = BackendUtility::getTCEFORM_TSconfig('tt_content', $row);
$headersUsed = [];
// Traverse wizard items:
foreach ($wizardItems as $key => $cfg) {
// Exploding parameter string, if any (old style)
if ($wizardItems[$key]['params']) {
// Explode GET vars recursively
$tempGetVars = [];
parse_str($wizardItems[$key]['params'], $tempGetVars);
// If tt_content values are set, merge them into the tt_content_defValues array,
// unset them from $tempGetVars and re-implode $tempGetVars into the param string
// (in case remaining parameters are around).
if (is_array($tempGetVars['defVals']['tt_content'])) {
$wizardItems[$key]['tt_content_defValues'] = array_merge(
is_array($wizardItems[$key]['tt_content_defValues']) ? $wizardItems[$key]['tt_content_defValues'] : [],
$tempGetVars['defVals']['tt_content']
);
unset($tempGetVars['defVals']['tt_content']);
$wizardItems[$key]['params'] = HttpUtility::buildQueryString($tempGetVars, '&');
}
}
// If tt_content_defValues are defined...:
if (is_array($wizardItems[$key]['tt_content_defValues'])) {
$backendUser = $this->getBackendUser();
// Traverse field values:
foreach ($wizardItems[$key]['tt_content_defValues'] as $fN => $fV) {
if (is_array($GLOBALS['TCA']['tt_content']['columns'][$fN])) {
// Get information about if the field value is OK:
$config = &$GLOBALS['TCA']['tt_content']['columns'][$fN]['config'];
$authModeDeny = $config['type'] === 'select' && $config['authMode']
&& !$backendUser->checkAuthMode('tt_content', $fN, $fV, $config['authMode']);
// explode TSconfig keys only as needed
if (!isset($removeItems[$fN]) && isset($TCEFORM_TSconfig[$fN]['removeItems']) && $TCEFORM_TSconfig[$fN]['removeItems'] !== '') {
$removeItems[$fN] = array_flip(GeneralUtility::trimExplode(
',',
$TCEFORM_TSconfig[$fN]['removeItems'],
true
));
}
if (!isset($keepItems[$fN]) && isset($TCEFORM_TSconfig[$fN]['keepItems']) && $TCEFORM_TSconfig[$fN]['keepItems'] !== '') {
$keepItems[$fN] = array_flip(GeneralUtility::trimExplode(
',',
$TCEFORM_TSconfig[$fN]['keepItems'],
true
));
}
$isNotInKeepItems = !empty($keepItems[$fN]) && !isset($keepItems[$fN][$fV]);
if ($authModeDeny || ($fN === 'CType' && (isset($removeItems[$fN][$fV]) || $isNotInKeepItems))) {
// Remove element all together:
unset($wizardItems[$key]);
break;
}
// Add the parameter:
$wizardItems[$key]['params'] .= '&defVals[tt_content][' . $fN . ']=' . rawurlencode($this->getLanguageService()->sL($fV));
$tmp = explode('_', $key);
$headersUsed[$tmp[0]] = $tmp[0];
}
}
}
}
// remove headers without elements
foreach ($wizardItems as $key => $cfg) {
$tmp = explode('_', $key);
if ($tmp[0] && !$tmp[1] && !in_array($tmp[0], $headersUsed)) {
unset($wizardItems[$key]);
}
}
}
/**
* Prepare a wizard tab configuration for sorting.
*
* @param array $wizardGroup TypoScript wizard tab configuration
* @param string $key Which array key should be prepared
*/
protected function prepareDependencyOrdering(&$wizardGroup, $key)
{
if (isset($wizardGroup[$key])) {
$wizardGroup[$key] = GeneralUtility::trimExplode(',', $wizardGroup[$key]);
$wizardGroup[$key] = array_map(function ($s) {
return $s . '.';
}, $wizardGroup[$key]);
}
}
/**
* @return LanguageService
*/
protected function getLanguageService(): LanguageService
{
return $GLOBALS['LANG'];
}
/**
* @return BackendUserAuthentication
*/
protected function getBackendUser(): BackendUserAuthentication
{
return $GLOBALS['BE_USER'];
}
/**
* @param string $filename
* @return StandaloneView
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidExtensionNameException
*/
protected function getFluidTemplateObject(string $filename = 'Main.html'): StandaloneView
{
/** @var StandaloneView $view */
$view = GeneralUtility::makeInstance(StandaloneView::class);
$view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates/NewContentElement/' . $filename));
$view->getRequest()->setControllerExtensionName('Backend');
return $view;
}
/**
* Provide information about the current page making use of the wizard
*
* @return array
*/
public function getPageInfo(): array
{
return $this->pageInfo;
}
/**
* Provide information about the column position of the button that triggered the wizard
*
* @return int|null
*/
public function getColPos(): ?int
{
return $this->colPos;
}
/**
* Provide information about the language used while triggering the wizard
*
* @return int
*/
public function getSysLanguage(): int
{
return $this->sys_language;
}
/**
* Provide information about the element to position the new element after (uid) or into (pid)
*
* @return int
*/
public function getUidPid(): int
{
return $this->uid_pid;
}
}