first commit
This commit is contained in:
commit
1218dd1517
12716 files changed
+989233
No files matched your search
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Localization\LocalizationFactory;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\PathUtility;
|
||||
|
||||
/**
|
||||
* Abstract class for a couple of FormEngine controllers triggered by
|
||||
* ajax calls. The class containers some helpers to for instance prepare
|
||||
* the form render result for json output.
|
||||
*
|
||||
* @internal Marked as internal for now, methods in this class may change any time.
|
||||
*/
|
||||
abstract class AbstractFormEngineAjaxController
|
||||
{
|
||||
/**
|
||||
* Gets result array from FormEngine and returns string with js modules
|
||||
* that need to be loaded and evaluated by JavaScript.
|
||||
*
|
||||
* @param array $result
|
||||
* @return array
|
||||
*/
|
||||
protected function createExecutableStringRepresentationOfRegisteredRequireJsModules(array $result): array
|
||||
{
|
||||
if (empty($result['requireJsModules'])) {
|
||||
return [];
|
||||
}
|
||||
$requireJs = [];
|
||||
foreach ($result['requireJsModules'] as $module) {
|
||||
$moduleName = null;
|
||||
$callback = null;
|
||||
if (is_string($module)) {
|
||||
// if $module is a string, no callback
|
||||
$moduleName = $module;
|
||||
$callback = null;
|
||||
} elseif (is_array($module)) {
|
||||
// if $module is an array, callback is possible
|
||||
foreach ($module as $key => $value) {
|
||||
$moduleName = $key;
|
||||
$callback = $value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($moduleName !== null) {
|
||||
$inlineCodeKey = $moduleName;
|
||||
$javaScriptCode = 'require(["' . $moduleName . '"]';
|
||||
if ($callback !== null) {
|
||||
$inlineCodeKey .= sha1($callback);
|
||||
$javaScriptCode .= ', ' . $callback;
|
||||
}
|
||||
$javaScriptCode .= ');';
|
||||
$requireJs[] = '/*RequireJS-Module-' . $inlineCodeKey . '*/' . LF . $javaScriptCode;
|
||||
}
|
||||
}
|
||||
return $requireJs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a CSS file position, possibly prefixed with 'EXT:'
|
||||
*
|
||||
* @param string $stylesheetFile Given file, possibly prefixed with EXT:
|
||||
* @return string Web root relative position to file
|
||||
*/
|
||||
protected function getRelativePathToStylesheetFile(string $stylesheetFile): string
|
||||
{
|
||||
if (strpos($stylesheetFile, 'EXT:') === 0) {
|
||||
$stylesheetFile = GeneralUtility::getFileAbsFileName($stylesheetFile);
|
||||
$stylesheetFile = PathUtility::getRelativePathTo($stylesheetFile);
|
||||
$stylesheetFile = rtrim($stylesheetFile, '/');
|
||||
} else {
|
||||
$stylesheetFile = GeneralUtility::resolveBackPath($stylesheetFile);
|
||||
}
|
||||
$stylesheetFile = GeneralUtility::createVersionNumberedFilename($stylesheetFile);
|
||||
return PathUtility::getAbsoluteWebPath($stylesheetFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a language file and get a label/value array from it.
|
||||
*
|
||||
* @param string $file EXT:path/to/file
|
||||
* @return array Label/value array
|
||||
*/
|
||||
protected function getLabelsFromLocalizationFile($file)
|
||||
{
|
||||
/** @var LocalizationFactory $languageFactory */
|
||||
$languageFactory = GeneralUtility::makeInstance(LocalizationFactory::class);
|
||||
$language = $GLOBALS['LANG']->lang;
|
||||
$localizationArray = $languageFactory->getParsedData($file, $language);
|
||||
if (is_array($localizationArray) && !empty($localizationArray)) {
|
||||
if (!empty($localizationArray[$language])) {
|
||||
$xlfLabelArray = $localizationArray['default'];
|
||||
ArrayUtility::mergeRecursiveWithOverrule($xlfLabelArray, $localizationArray[$language], true, false);
|
||||
} else {
|
||||
$xlfLabelArray = $localizationArray['default'];
|
||||
}
|
||||
} else {
|
||||
$xlfLabelArray = [];
|
||||
}
|
||||
$labelArray = [];
|
||||
foreach ($xlfLabelArray as $key => $value) {
|
||||
if (isset($value[0]['target'])) {
|
||||
$labelArray[$key] = $value[0]['target'];
|
||||
} else {
|
||||
$labelArray[$key] = '';
|
||||
}
|
||||
}
|
||||
return $labelArray;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Controller;
|
||||
|
||||
/*
|
||||
* 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\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
|
||||
/**
|
||||
* This is the ajax handler for backend login after timeout.
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class AjaxLoginController
|
||||
{
|
||||
/**
|
||||
* Handles the actual login process, more specifically it defines the response.
|
||||
* The login details were sent in as part of the ajax request and automatically logged in
|
||||
* the user inside the TYPO3 CMS bootstrap part of the ajax call. If that was successful, we have
|
||||
* a BE user and reset the timer and hide the login window.
|
||||
* If it was unsuccessful, we display that and show the login box again.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function loginAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
if ($this->isAuthorizedBackendSession()) {
|
||||
$result = ['success' => true];
|
||||
if ($this->hasLoginBeenProcessed()) {
|
||||
/** @var \TYPO3\CMS\Core\FormProtection\BackendFormProtection $formProtection */
|
||||
$formProtection = FormProtectionFactory::get();
|
||||
$formProtection->setSessionTokenFromRegistry();
|
||||
$formProtection->persistSessionToken();
|
||||
}
|
||||
} else {
|
||||
$result = ['success' => false];
|
||||
}
|
||||
return new JsonResponse(['login' => $result]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs out the current BE user
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function logoutAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$backendUser = $this->getBackendUser();
|
||||
$backendUser->logoff();
|
||||
return new JsonResponse([
|
||||
'logout' => [
|
||||
'success' => !isset($backendUser->user['uid'])
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function preflightAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$headers = $request->getHeaders();
|
||||
return new JsonResponse([
|
||||
'capabilities' => [
|
||||
'cookie' => !empty($request->getCookieParams()),
|
||||
// using legacy `Referer` (sic!) header name
|
||||
'referrer' => array_filter($headers['referer'] ?? []) !== [],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the login without needing login information. We just refresh the session.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function refreshAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->getBackendUser()->checkAuthentication();
|
||||
return new JsonResponse([
|
||||
'refresh' => [
|
||||
'success' => true
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user session is expired yet
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function isTimedOutAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$session = [
|
||||
'timed_out' => false,
|
||||
'will_time_out' => false,
|
||||
'locked' => false
|
||||
];
|
||||
$backendUser = $this->getBackendUser();
|
||||
if (@is_file(Environment::getLegacyConfigPath() . '/LOCK_BACKEND')) {
|
||||
$session['locked'] = true;
|
||||
} elseif (!isset($backendUser->user['uid'])) {
|
||||
$session['timed_out'] = true;
|
||||
} else {
|
||||
$backendUser->fetchUserSession(true);
|
||||
$ses_tstamp = $backendUser->user['ses_tstamp'];
|
||||
$timeout = $backendUser->sessionTimeout;
|
||||
// If 120 seconds from now is later than the session timeout, we need to show the refresh dialog.
|
||||
// 120 is somewhat arbitrary to allow for a little room during the countdown and load times, etc.
|
||||
$session['will_time_out'] = $GLOBALS['EXEC_TIME'] >= $ses_tstamp + $timeout - 120;
|
||||
}
|
||||
return new JsonResponse(['login' => $session]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a user is logged in and the session is active.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isAuthorizedBackendSession()
|
||||
{
|
||||
$backendUser = $this->getBackendUser();
|
||||
return $backendUser !== null && $backendUser instanceof BackendUserAuthentication && isset($backendUser->user['uid']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the user was already authorized or not
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasLoginBeenProcessed()
|
||||
{
|
||||
$loginFormData = $this->getBackendUser()->getLoginFormData();
|
||||
return $loginFormData['status'] === 'login' && !empty($loginFormData['uname']) && !empty($loginFormData['uident']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackendUserAuthentication|null
|
||||
*/
|
||||
protected function getBackendUser()
|
||||
{
|
||||
return $GLOBALS['BE_USER'] ?? null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,679 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Controller;
|
||||
|
||||
/*
|
||||
* 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 TYPO3\CMS\Backend\Domain\Repository\Module\BackendModuleRepository;
|
||||
use TYPO3\CMS\Backend\Module\ModuleLoader;
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Toolbar\ToolbarItemInterface;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Compatibility\PublicMethodDeprecationTrait;
|
||||
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Page\PageRenderer;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
||||
use TYPO3\CMS\Core\Type\File\ImageInfo;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
use TYPO3\CMS\Core\Utility\PathUtility;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
|
||||
/**
|
||||
* Class for rendering the TYPO3 backend
|
||||
*/
|
||||
class BackendController
|
||||
{
|
||||
use PublicMethodDeprecationTrait;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $deprecatedPublicMethods = [
|
||||
'render' => 'Using BackendController::render() is deprecated and will not be possible anymore in TYPO3 v10.0.',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $content = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $css = '';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $cssFiles = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $js = '';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $jsFiles = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $toolbarItems = [];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $debug;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $templatePath = 'EXT:backend/Resources/Private/Templates/';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $partialPath = 'EXT:backend/Resources/Private/Partials/';
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Backend\Domain\Repository\Module\BackendModuleRepository
|
||||
*/
|
||||
protected $backendModuleRepository;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Backend\Module\ModuleLoader Object for loading backend modules
|
||||
*/
|
||||
protected $moduleLoader;
|
||||
|
||||
/**
|
||||
* @var PageRenderer
|
||||
*/
|
||||
protected $pageRenderer;
|
||||
|
||||
/**
|
||||
* @var IconFactory
|
||||
*/
|
||||
protected $iconFactory;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->getLanguageService()->includeLLFile('EXT:core/Resources/Private/Language/locallang_misc.xlf');
|
||||
|
||||
$this->backendModuleRepository = GeneralUtility::makeInstance(BackendModuleRepository::class);
|
||||
$this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
// Set debug flag for BE development only
|
||||
$this->debug = (int)$GLOBALS['TYPO3_CONF_VARS']['BE']['debug'] === 1;
|
||||
// Initializes the backend modules structure for use later.
|
||||
$this->moduleLoader = GeneralUtility::makeInstance(ModuleLoader::class);
|
||||
$this->moduleLoader->load($GLOBALS['TBE_MODULES']);
|
||||
$this->pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
|
||||
// Add default BE javascript
|
||||
$this->jsFiles = [
|
||||
'md5' => 'EXT:backend/Resources/Public/JavaScript/md5.js',
|
||||
'evalfield' => 'EXT:backend/Resources/Public/JavaScript/jsfunc.evalfield.js',
|
||||
'backend' => 'EXT:backend/Resources/Public/JavaScript/backend.js',
|
||||
];
|
||||
$this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/LoginRefresh', 'function(LoginRefresh) {
|
||||
LoginRefresh.setIntervalTime(' . MathUtility::forceIntegerInRange((int)$GLOBALS['TYPO3_CONF_VARS']['BE']['sessionTimeout'] - 60, 60) . ');
|
||||
LoginRefresh.setLoginFramesetUrl(' . GeneralUtility::quoteJSvalue((string)$uriBuilder->buildUriFromRoute('login_frameset')) . ');
|
||||
LoginRefresh.setLogoutUrl(' . GeneralUtility::quoteJSvalue((string)$uriBuilder->buildUriFromRoute('logout')) . ');
|
||||
LoginRefresh.initialize();
|
||||
}');
|
||||
|
||||
// load module menu
|
||||
$this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ModuleMenu');
|
||||
|
||||
// load Toolbar class
|
||||
$this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Toolbar');
|
||||
|
||||
// load Utility class
|
||||
$this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Utility');
|
||||
|
||||
// load Notification functionality
|
||||
$this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Notification');
|
||||
|
||||
// load Modals
|
||||
$this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Modal');
|
||||
|
||||
// load InfoWindow
|
||||
$this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/InfoWindow');
|
||||
|
||||
// load ContextMenu
|
||||
$this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
|
||||
|
||||
// load the storage API and fill the UC into the PersistentStorage, so no additional AJAX call is needed
|
||||
$this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Storage');
|
||||
$this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Storage/Persistent', 'function(PersistentStorage) {
|
||||
PersistentStorage.load(' . json_encode($this->getBackendUser()->uc) . ');
|
||||
}');
|
||||
|
||||
// load debug console
|
||||
$this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/DebugConsole');
|
||||
|
||||
$this->pageRenderer->addInlineLanguageLabelFile('EXT:core/Resources/Private/Language/locallang_core.xlf');
|
||||
$this->pageRenderer->addInlineLanguageLabelFile('EXT:core/Resources/Private/Language/locallang_misc.xlf');
|
||||
$this->pageRenderer->addInlineLanguageLabelFile('EXT:backend/Resources/Private/Language/locallang_layout.xlf');
|
||||
$this->pageRenderer->addInlineLanguageLabelFile('EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf');
|
||||
|
||||
$this->pageRenderer->addInlineLanguageLabelFile('EXT:core/Resources/Private/Language/debugger.xlf');
|
||||
$this->pageRenderer->addInlineLanguageLabelFile('EXT:core/Resources/Private/Language/wizard.xlf');
|
||||
|
||||
$this->pageRenderer->addInlineSetting('ShowItem', 'moduleUrl', (string)$uriBuilder->buildUriFromRoute('show_item'));
|
||||
$this->pageRenderer->addInlineSetting('RecordHistory', 'moduleUrl', (string)$uriBuilder->buildUriFromRoute('record_history'));
|
||||
$this->pageRenderer->addInlineSetting('NewRecord', 'moduleUrl', (string)$uriBuilder->buildUriFromRoute('db_new'));
|
||||
$this->pageRenderer->addInlineSetting('FormEngine', 'moduleUrl', (string)$uriBuilder->buildUriFromRoute('record_edit'));
|
||||
$this->pageRenderer->addInlineSetting('RecordCommit', 'moduleUrl', (string)$uriBuilder->buildUriFromRoute('tce_db'));
|
||||
$this->pageRenderer->addInlineSetting('WebLayout', 'moduleUrl', (string)$uriBuilder->buildUriFromRoute('web_layout'));
|
||||
|
||||
$this->css = '';
|
||||
|
||||
$this->initializeToolbarItems();
|
||||
$this->executeHook('constructPostProcess');
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize toolbar item objects
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function initializeToolbarItems()
|
||||
{
|
||||
$toolbarItemInstances = [];
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['BE']['toolbarItems'] ?? [] as $className) {
|
||||
$toolbarItemInstance = GeneralUtility::makeInstance($className);
|
||||
if (!$toolbarItemInstance instanceof ToolbarItemInterface) {
|
||||
throw new \RuntimeException(
|
||||
'class ' . $className . ' is registered as toolbar item but does not implement'
|
||||
. ToolbarItemInterface::class,
|
||||
1415958218
|
||||
);
|
||||
}
|
||||
$index = (int)$toolbarItemInstance->getIndex();
|
||||
if ($index < 0 || $index > 100) {
|
||||
throw new \RuntimeException(
|
||||
'getIndex() must return an integer between 0 and 100',
|
||||
1415968498
|
||||
);
|
||||
}
|
||||
// Find next free position in array
|
||||
while (array_key_exists($index, $toolbarItemInstances)) {
|
||||
$index++;
|
||||
}
|
||||
$toolbarItemInstances[$index] = $toolbarItemInstance;
|
||||
}
|
||||
ksort($toolbarItemInstances);
|
||||
$this->toolbarItems = $toolbarItemInstances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects the request object for the current request or subrequest
|
||||
* As this controller goes only through the render() method, it is rather simple for now
|
||||
*
|
||||
* @return ResponseInterface the response with the content
|
||||
*/
|
||||
public function mainAction(): ResponseInterface
|
||||
{
|
||||
$this->render();
|
||||
return new HtmlResponse($this->content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function generating the BE scaffolding
|
||||
*/
|
||||
protected function render()
|
||||
{
|
||||
$this->executeHook('renderPreProcess');
|
||||
|
||||
// Prepare the scaffolding, at this point extension may still add javascript and css
|
||||
$view = $this->getFluidTemplateObject($this->templatePath . 'Backend/Main.html');
|
||||
|
||||
$view->assign('moduleMenuCollapsed', $this->getCollapseStateOfMenu());
|
||||
$view->assign('moduleMenu', $this->generateModuleMenu());
|
||||
$view->assign('topbar', $this->renderTopbar());
|
||||
|
||||
/******************************************************
|
||||
* Now put the complete backend document together
|
||||
******************************************************/
|
||||
foreach ($this->cssFiles as $cssFileName => $cssFile) {
|
||||
$this->pageRenderer->addCssFile($cssFile);
|
||||
// Load additional css files to overwrite existing core styles
|
||||
if (!empty($GLOBALS['TBE_STYLES']['stylesheets'][$cssFileName])) {
|
||||
$this->pageRenderer->addCssFile($GLOBALS['TBE_STYLES']['stylesheets'][$cssFileName]);
|
||||
}
|
||||
}
|
||||
if (!empty($this->css)) {
|
||||
$this->pageRenderer->addCssInlineBlock('BackendInlineCSS', $this->css);
|
||||
}
|
||||
foreach ($this->jsFiles as $jsFile) {
|
||||
$this->pageRenderer->addJsFile($jsFile);
|
||||
}
|
||||
$this->generateJavascript();
|
||||
$this->pageRenderer->addJsInlineCode('BackendInlineJavascript', $this->js, false);
|
||||
|
||||
// Set document title:
|
||||
$title = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ? $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . ' [TYPO3 CMS ' . TYPO3_version . ']' : 'TYPO3 CMS ' . TYPO3_version;
|
||||
// Renders the module page
|
||||
$this->content = $this->getDocumentTemplate()->render($title, $view->render());
|
||||
$hookConfiguration = ['content' => &$this->content];
|
||||
$this->executeHook('renderPostProcess', $hookConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the topbar, containing the backend logo, sitename etc.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function renderTopbar()
|
||||
{
|
||||
$view = $this->getFluidTemplateObject($this->partialPath . 'Backend/Topbar.html');
|
||||
|
||||
// Extension Configuration to find the TYPO3 logo in the left corner
|
||||
$extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('backend');
|
||||
$logoPath = '';
|
||||
if (!empty($extConf['backendLogo'])) {
|
||||
$customBackendLogo = GeneralUtility::getFileAbsFileName(ltrim($extConf['backendLogo'], '/'));
|
||||
if (!empty($customBackendLogo)) {
|
||||
$logoPath = $customBackendLogo;
|
||||
}
|
||||
}
|
||||
// if no custom logo was set or the path is invalid, use the original one
|
||||
if (empty($logoPath) || !file_exists($logoPath)) {
|
||||
$logoPath = GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Public/Images/typo3_logo_orange.svg');
|
||||
$logoWidth = 22;
|
||||
$logoHeight = 22;
|
||||
} else {
|
||||
// set width/height for custom logo
|
||||
$imageInfo = GeneralUtility::makeInstance(ImageInfo::class, $logoPath);
|
||||
$logoWidth = $imageInfo->getWidth() ?? '22';
|
||||
$logoHeight = $imageInfo->getHeight() ?? '22';
|
||||
|
||||
// High-resolution?
|
||||
if (strpos($logoPath, '@2x.') !== false) {
|
||||
$logoWidth /= 2;
|
||||
$logoHeight /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
$view->assign('logoUrl', PathUtility::getAbsoluteWebPath($logoPath));
|
||||
$view->assign('logoWidth', $logoWidth);
|
||||
$view->assign('logoHeight', $logoHeight);
|
||||
$view->assign('applicationVersion', TYPO3_version);
|
||||
$view->assign('siteName', $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']);
|
||||
$view->assign('toolbar', $this->renderToolbar());
|
||||
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the items in the top toolbar
|
||||
*
|
||||
* @return string top toolbar elements as HTML
|
||||
*/
|
||||
protected function renderToolbar()
|
||||
{
|
||||
$toolbar = [];
|
||||
foreach ($this->toolbarItems as $toolbarItem) {
|
||||
/** @var \TYPO3\CMS\Backend\Toolbar\ToolbarItemInterface $toolbarItem */
|
||||
if ($toolbarItem->checkAccess()) {
|
||||
$hasDropDown = (bool)$toolbarItem->hasDropDown();
|
||||
$additionalAttributes = (array)$toolbarItem->getAdditionalAttributes();
|
||||
|
||||
$liAttributes = [];
|
||||
|
||||
// Merge class: Add dropdown class if hasDropDown, add classes from additional attributes
|
||||
$classes = [];
|
||||
$classes[] = 'toolbar-item';
|
||||
$classes[] = 't3js-toolbar-item';
|
||||
if (isset($additionalAttributes['class'])) {
|
||||
$classes[] = $additionalAttributes['class'];
|
||||
unset($additionalAttributes['class']);
|
||||
}
|
||||
$liAttributes['class'] = implode(' ', $classes);
|
||||
|
||||
// Add further attributes
|
||||
foreach ($additionalAttributes as $name => $value) {
|
||||
$liAttributes[$name] = $value;
|
||||
}
|
||||
|
||||
// Create a unique id from class name
|
||||
$fullyQualifiedClassName = \get_class($toolbarItem);
|
||||
$className = GeneralUtility::underscoredToLowerCamelCase($fullyQualifiedClassName);
|
||||
$className = GeneralUtility::camelCaseToLowerCaseUnderscored($className);
|
||||
$className = str_replace(['_', '\\'], '-', $className);
|
||||
$liAttributes['id'] = $className;
|
||||
|
||||
// Create data attribute identifier
|
||||
$shortName = substr($fullyQualifiedClassName, strrpos($fullyQualifiedClassName, '\\') + 1);
|
||||
$dataToolbarIdentifier = GeneralUtility::camelCaseToLowerCaseUnderscored($shortName);
|
||||
$dataToolbarIdentifier = str_replace('_', '-', $dataToolbarIdentifier);
|
||||
$liAttributes['data-toolbar-identifier'] = $dataToolbarIdentifier;
|
||||
|
||||
$toolbar[] = '<li ' . GeneralUtility::implodeAttributes($liAttributes, true) . '>';
|
||||
|
||||
if ($hasDropDown) {
|
||||
$toolbar[] = '<a href="#" class="toolbar-item-link dropdown-toggle" data-toggle="dropdown">';
|
||||
$toolbar[] = $toolbarItem->getItem();
|
||||
$toolbar[] = '</a>';
|
||||
$toolbar[] = '<div class="dropdown-menu" role="menu">';
|
||||
$toolbar[] = $toolbarItem->getDropDown();
|
||||
$toolbar[] = '</div>';
|
||||
} else {
|
||||
$toolbar[] = $toolbarItem->getItem();
|
||||
}
|
||||
$toolbar[] = '</li>';
|
||||
}
|
||||
}
|
||||
return implode(LF, $toolbar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the JavaScript code for the backend.
|
||||
*/
|
||||
protected function generateJavascript()
|
||||
{
|
||||
$beUser = $this->getBackendUser();
|
||||
// Needed for FormEngine manipulation (date picker)
|
||||
$dateFormat = ($GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat'] ? ['MM-DD-YYYY', 'HH:mm MM-DD-YYYY'] : ['DD-MM-YYYY', 'HH:mm DD-MM-YYYY']);
|
||||
$this->pageRenderer->addInlineSetting('DateTimePicker', 'DateFormat', $dateFormat);
|
||||
|
||||
// If another page module was specified, replace the default Page module with the new one
|
||||
$newPageModule = trim($beUser->getTSConfig()['options.']['overridePageModule'] ?? '');
|
||||
$pageModule = BackendUtility::isModuleSetInTBE_MODULES($newPageModule) ? $newPageModule : 'web_layout';
|
||||
$pageModuleUrl = '';
|
||||
if (!$beUser->check('modules', $pageModule)) {
|
||||
$pageModule = '';
|
||||
} else {
|
||||
$pageModuleUrl = (string)GeneralUtility::makeInstance(UriBuilder::class)->buildUriFromRoute($pageModule);
|
||||
}
|
||||
$t3Configuration = [
|
||||
'username' => htmlspecialchars($beUser->user['username']),
|
||||
'pageModule' => $pageModule,
|
||||
'pageModuleUrl' => $pageModuleUrl,
|
||||
'inWorkspace' => $beUser->workspace !== 0,
|
||||
'showRefreshLoginPopup' => (bool)($GLOBALS['TYPO3_CONF_VARS']['BE']['showRefreshLoginPopup'] ?? false)
|
||||
];
|
||||
$this->js .= '
|
||||
TYPO3.configuration = ' . json_encode($t3Configuration) . ';
|
||||
/**
|
||||
* Frameset Module object
|
||||
*
|
||||
* Used in main modules with a frameset for submodules to keep the ID between modules
|
||||
* Typically that is set by something like this in a Web>* sub module:
|
||||
* if (top.fsMod) top.fsMod.recentIds["web"] = "\'.(int)$this->id.\'";
|
||||
* if (top.fsMod) top.fsMod.recentIds["file"] = "...(file reference/string)...";
|
||||
*/
|
||||
var fsMod = {
|
||||
recentIds: [], // used by frameset modules to track the most recent used id for list frame.
|
||||
navFrameHighlightedID: [], // used by navigation frames to track which row id was highlighted last time
|
||||
currentBank: "0"
|
||||
};
|
||||
|
||||
top.goToModule = function(modName, cMR_flag, addGetVars) {
|
||||
TYPO3.ModuleMenu.App.showModule(modName, addGetVars);
|
||||
}
|
||||
' . $this->setStartupModule();
|
||||
// Check editing of page:
|
||||
$this->handlePageEditing();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checking if the "&edit" variable was sent so we can open it for editing the page.
|
||||
*/
|
||||
protected function handlePageEditing()
|
||||
{
|
||||
$beUser = $this->getBackendUser();
|
||||
$userTsConfig = $this->getBackendUser()->getTSConfig();
|
||||
// EDIT page:
|
||||
$editId = preg_replace('/[^[:alnum:]_]/', '', GeneralUtility::_GET('edit'));
|
||||
if ($editId) {
|
||||
// Looking up the page to edit, checking permissions:
|
||||
$where = ' AND (' . $beUser->getPagePermsClause(Permission::PAGE_EDIT) . ' OR ' . $beUser->getPagePermsClause(Permission::CONTENT_EDIT) . ')';
|
||||
if (MathUtility::canBeInterpretedAsInteger($editId)) {
|
||||
$editRecord = BackendUtility::getRecordWSOL('pages', $editId, '*', $where);
|
||||
} else {
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
|
||||
$queryBuilder->getRestrictions()
|
||||
->removeAll()
|
||||
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
|
||||
->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
|
||||
|
||||
$editRecord = $queryBuilder->select('*')
|
||||
->from('pages')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'alias',
|
||||
$queryBuilder->createNamedParameter($editId, \PDO::PARAM_STR)
|
||||
),
|
||||
$queryBuilder->expr()->orX(
|
||||
$beUser->getPagePermsClause(Permission::PAGE_EDIT),
|
||||
$beUser->getPagePermsClause(Permission::CONTENT_EDIT)
|
||||
)
|
||||
)
|
||||
->setMaxResults(1)
|
||||
->execute()
|
||||
->fetch();
|
||||
|
||||
if ($editRecord !== false) {
|
||||
BackendUtility::workspaceOL('pages', $editRecord);
|
||||
}
|
||||
}
|
||||
// If the page was accessible, then let the user edit it.
|
||||
if (is_array($editRecord) && $beUser->isInWebMount($editRecord)) {
|
||||
// Setting JS code to open editing:
|
||||
$this->js .= '
|
||||
// Load page to edit:
|
||||
window.setTimeout("top.loadEditId(' . (int)$editRecord['uid'] . ');", 500);
|
||||
';
|
||||
// Checking page edit parameter:
|
||||
if (!($userTsConfig['options.']['bookmark_onEditId_dontSetPageTree'] ?? false)) {
|
||||
$bookmarkKeepExpanded = (bool)($userTsConfig['options.']['bookmark_onEditId_keepExistingExpanded'] ?? false);
|
||||
// Expanding page tree:
|
||||
BackendUtility::openPageTree((int)$editRecord['pid'], !$bookmarkKeepExpanded);
|
||||
}
|
||||
} else {
|
||||
$this->js .= '
|
||||
// Warning about page editing:
|
||||
require(["TYPO3/CMS/Backend/Modal", "TYPO3/CMS/Backend/Severity"], function(Modal, Severity) {
|
||||
Modal.show("", ' . GeneralUtility::quoteJSvalue(sprintf($this->getLanguageService()->getLL('noEditPage'), $editId)) . ', Severity.notice, [{
|
||||
text: ' . GeneralUtility::quoteJSvalue($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:close')) . ',
|
||||
active: true,
|
||||
btnClass: "btn-info",
|
||||
name: "cancel",
|
||||
trigger: function () {
|
||||
Modal.currentModal.trigger("modal-dismiss");
|
||||
}
|
||||
}])
|
||||
});';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the startup module from either GETvars module and modParams or user configuration.
|
||||
*
|
||||
* @return string the JavaScript code for the startup module
|
||||
*/
|
||||
protected function setStartupModule()
|
||||
{
|
||||
$startModule = preg_replace('/[^[:alnum:]_]/', '', GeneralUtility::_GET('module'));
|
||||
$startModuleParameters = '';
|
||||
if (!$startModule) {
|
||||
$beUser = $this->getBackendUser();
|
||||
// start module on first login, will be removed once used the first time
|
||||
if (isset($beUser->uc['startModuleOnFirstLogin'])) {
|
||||
$startModule = $beUser->uc['startModuleOnFirstLogin'];
|
||||
unset($beUser->uc['startModuleOnFirstLogin']);
|
||||
$beUser->writeUC();
|
||||
} elseif ($beUser->uc['startModule']) {
|
||||
$startModule = $beUser->uc['startModule'];
|
||||
}
|
||||
|
||||
// check if the start module has additional parameters, so a redirect to a specific
|
||||
// action is possible
|
||||
if (strpos($startModule, '->') !== false) {
|
||||
list($startModule, $startModuleParameters) = explode('->', $startModule, 2);
|
||||
}
|
||||
}
|
||||
|
||||
$moduleParameters = GeneralUtility::_GET('modParams');
|
||||
// if no GET parameters are set, check if there are parameters given from the UC
|
||||
if (!$moduleParameters && $startModuleParameters) {
|
||||
$moduleParameters = $startModuleParameters;
|
||||
}
|
||||
|
||||
if ($startModule) {
|
||||
return '
|
||||
// start in module:
|
||||
top.startInModule = [' . GeneralUtility::quoteJSvalue($startModule) . ', ' . GeneralUtility::quoteJSvalue($moduleParameters) . '];
|
||||
';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a css snippet to the backend
|
||||
*
|
||||
* @param string $css Css snippet
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function addCss($css)
|
||||
{
|
||||
if (!is_string($css)) {
|
||||
throw new \InvalidArgumentException('parameter $css must be of type string', 1195129642);
|
||||
}
|
||||
$this->css .= $css;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes defined hooks functions for the given identifier.
|
||||
*
|
||||
* These hook identifiers are valid:
|
||||
* + constructPostProcess
|
||||
* + renderPreProcess
|
||||
* + renderPostProcess
|
||||
*
|
||||
* @param string $identifier Specific hook identifier
|
||||
* @param array $hookConfiguration Additional configuration passed to hook functions
|
||||
*/
|
||||
protected function executeHook($identifier, array $hookConfiguration = [])
|
||||
{
|
||||
$options = &$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/backend.php'];
|
||||
foreach ($options[$identifier] ?? [] as $hookFunction) {
|
||||
GeneralUtility::callUserFunction($hookFunction, $hookConfiguration, $this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* loads all modules from the repository
|
||||
* and renders it with a template
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function generateModuleMenu()
|
||||
{
|
||||
// get all modules except the user modules for the side menu
|
||||
$moduleStorage = $this->backendModuleRepository->loadAllowedModules(['user', 'help']);
|
||||
|
||||
$view = $this->getFluidTemplateObject($this->templatePath . 'ModuleMenu/Main.html');
|
||||
$view->assign('modules', $moduleStorage);
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
protected function getCollapseStateOfMenu(): bool
|
||||
{
|
||||
$uc = json_decode(json_encode($this->getBackendUser()->uc), true);
|
||||
$collapseState = $uc['BackendComponents']['States']['typo3-module-menu']['collapsed'] ?? false;
|
||||
|
||||
return $collapseState === true || $collapseState === 'true';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Module menu for the AJAX request
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function getModuleMenu(): ResponseInterface
|
||||
{
|
||||
return new JsonResponse(['menu' => $this->generateModuleMenu()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the toolbar for the AJAX request
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function getTopbar(): ResponseInterface
|
||||
{
|
||||
return new JsonResponse(['topbar' => $this->renderTopbar()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a new standalone view, shorthand function
|
||||
*
|
||||
* @param string $templatePathAndFileName optional the path to set the template path and filename
|
||||
* @return \TYPO3\CMS\Fluid\View\StandaloneView
|
||||
*/
|
||||
protected function getFluidTemplateObject($templatePathAndFileName = null)
|
||||
{
|
||||
$view = GeneralUtility::makeInstance(StandaloneView::class);
|
||||
$view->setPartialRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Partials')]);
|
||||
if ($templatePathAndFileName) {
|
||||
$view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templatePathAndFileName));
|
||||
}
|
||||
return $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of DocumentTemplate
|
||||
*
|
||||
* @return \TYPO3\CMS\Backend\Template\DocumentTemplate
|
||||
*/
|
||||
protected function getDocumentTemplate()
|
||||
{
|
||||
return $GLOBALS['TBE_TEMPLATE'];
|
||||
}
|
||||
}
|
||||
@@ -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'];
|
||||
}
|
||||
}
|
||||
+830
@@ -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'];
|
||||
}
|
||||
}
|
||||
+744
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Controller;
|
||||
|
||||
/*
|
||||
* 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\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Class ContextHelpAjaxController
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class ContextHelpAjaxController
|
||||
{
|
||||
/**
|
||||
* The main dispatcher function. Collect data and prepare HTML output.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function getHelpAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$params = $request->getParsedBody()['params'] ?? $request->getQueryParams()['params'];
|
||||
if (($params['action'] ?? '') !== 'getContextHelp') {
|
||||
throw new \RuntimeException('Action must be set to "getContextHelp"', 1518787887);
|
||||
}
|
||||
$result = $this->getContextHelp($params['table'], $params['field']);
|
||||
return new JsonResponse([
|
||||
'title' => $result['title'],
|
||||
'content' => $result['description'],
|
||||
'link' => $result['moreInfo']
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the context help for the given table/field parameters
|
||||
*
|
||||
* @param string $table Table identifier
|
||||
* @param string $field Field identifier
|
||||
* @return array complete Help information
|
||||
*/
|
||||
protected function getContextHelp($table, $field)
|
||||
{
|
||||
$helpTextArray = BackendUtility::helpTextArray($table, $field);
|
||||
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
$moreIcon = $helpTextArray['moreInfo'] ? $iconFactory->getIcon('actions-view-go-forward', Icon::SIZE_SMALL)->render() : '';
|
||||
return [
|
||||
'title' => $helpTextArray['title'],
|
||||
'description' => '<p class="t3-help-short' . ($moreIcon ? ' tipIsLinked' : '') . '">' . $helpTextArray['description'] . $moreIcon . '</p>',
|
||||
'id' => $table . '.' . $field,
|
||||
'moreInfo' => $helpTextArray['moreInfo']
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller;
|
||||
|
||||
/*
|
||||
* 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\Clipboard\Clipboard;
|
||||
use TYPO3\CMS\Backend\ContextMenu\ContextMenu;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Script Class for the Context Sensitive Menu in TYPO3
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class ContextMenuController
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->getLanguageService()->includeLLFile('EXT:core/Resources/Private/Language/locallang_misc.xlf');
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a context menu
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function getContextMenuAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$contextMenu = GeneralUtility::makeInstance(ContextMenu::class);
|
||||
|
||||
$params = $request->getQueryParams();
|
||||
$context = $params['context'] ?? '';
|
||||
$items = $contextMenu->getItems($params['table'], $params['uid'], $context);
|
||||
if (!is_array($items)) {
|
||||
$items = [];
|
||||
}
|
||||
return (new JsonResponse())->setPayload($items);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function clipboardAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
/** @var Clipboard $clipboard */
|
||||
$clipboard = GeneralUtility::makeInstance(Clipboard::class);
|
||||
$clipboard->initializeClipboard();
|
||||
$clipboard->lockToNormal();
|
||||
|
||||
$clipboard->setCmd($request->getQueryParams()['CB']);
|
||||
$clipboard->cleanCurrent();
|
||||
|
||||
$clipboard->endClipboard();
|
||||
return (new JsonResponse())->setPayload([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns LanguageService
|
||||
*
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Controller;
|
||||
|
||||
/*
|
||||
* 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 TYPO3\CMS\Backend\Template\DocumentTemplate;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
|
||||
/**
|
||||
* '/empty' routing target returns dummy content.
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class DummyController
|
||||
{
|
||||
/**
|
||||
* Return simple dummy content
|
||||
*
|
||||
* @return ResponseInterface the response with the content
|
||||
*/
|
||||
public function mainAction(): ResponseInterface
|
||||
{
|
||||
$documentTemplate = $this->getDocumentTemplate();
|
||||
$content = $documentTemplate->startPage('Dummy document') . $documentTemplate->endPage();
|
||||
return new HtmlResponse($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of DocumentTemplate
|
||||
*
|
||||
* @return DocumentTemplate
|
||||
*/
|
||||
protected function getDocumentTemplate()
|
||||
{
|
||||
return $GLOBALS['TBE_TEMPLATE'];
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,315 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller\File;
|
||||
|
||||
/*
|
||||
* 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\Routing\UriBuilder;
|
||||
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\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
|
||||
use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry;
|
||||
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
|
||||
/**
|
||||
* Script class for the create-new script
|
||||
*
|
||||
* Displays forms for creating folders (1 to 10), a media asset or a new file.
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class CreateFolderController
|
||||
{
|
||||
use PublicMethodDeprecationTrait;
|
||||
use PublicPropertyDeprecationTrait;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $deprecatedPublicMethods = [
|
||||
'main' => 'Using CreateFolderController::main() is deprecated and will not be possible anymore in TYPO3 v10.0.',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $deprecatedPublicProperties = [
|
||||
'number' => 'Using $number of class CreateFolderController from outside is discouraged, as this variable is only used for internal storage.',
|
||||
'folderNumber' => 'Using $folderNumber of class CreateFolderController from outside is discouraged, as this variable is only used for internal storage.',
|
||||
'target' => 'Using $target of class CreateFolderController from outside is discouraged, as this variable is only used for internal storage.',
|
||||
'content' => 'Using $content of class CreateFolderController from outside is discouraged, as this variable is only used for internal storage.',
|
||||
'returnUrl' => 'Using $content of class CreateFolderController from outside is discouraged, as this variable is only used for internal storage.',
|
||||
'title' => 'Using $content of class CreateFolderController from outside is discouraged, as this variable is only used for internal storage.',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $folderNumber = 10;
|
||||
|
||||
/**
|
||||
* Name of the filemount
|
||||
*
|
||||
* @var string
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, unused
|
||||
*/
|
||||
protected $title;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $number;
|
||||
|
||||
/**
|
||||
* Set with the target path inputted in &target
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $target;
|
||||
|
||||
/**
|
||||
* The folder object which is the target directory
|
||||
*
|
||||
* @var \TYPO3\CMS\Core\Resource\Folder $folderObject
|
||||
*/
|
||||
protected $folderObject;
|
||||
|
||||
/**
|
||||
* Return URL of list module.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $returnUrl;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $pathInfo;
|
||||
|
||||
/**
|
||||
* Accumulating content
|
||||
*
|
||||
* @var string
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, unused
|
||||
*/
|
||||
protected $content;
|
||||
|
||||
/**
|
||||
* ModuleTemplate object
|
||||
*
|
||||
* @var ModuleTemplate
|
||||
*/
|
||||
protected $moduleTemplate;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
|
||||
// @deprecated since TYPO3 v9, will be moved out of __construct() in TYPO3 v10.0
|
||||
$this->init($GLOBALS['TYPO3_REQUEST']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the request, currently everything is handled and put together via "main()"
|
||||
*
|
||||
* @param ServerRequestInterface $request the current request
|
||||
* @return ResponseInterface the response with the content
|
||||
*/
|
||||
public function mainAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->main();
|
||||
return new HtmlResponse($this->moduleTemplate->renderContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface|null $request
|
||||
*
|
||||
* @throws InsufficientFolderAccessPermissionsException
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function init(ServerRequestInterface $request): void
|
||||
{
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$queryParams = $request->getQueryParams();
|
||||
|
||||
$this->number = $parsedBody['number'] ?? $queryParams['number'] ?? 0;
|
||||
$this->target = ($combinedIdentifier = $parsedBody['target'] ?? $queryParams['target'] ?? '');
|
||||
$this->returnUrl = GeneralUtility::sanitizeLocalUrl($parsedBody['returnUrl'] ?? $queryParams['returnUrl'] ?? '');
|
||||
// create the folder object
|
||||
if ($combinedIdentifier) {
|
||||
$this->folderObject = ResourceFactory::getInstance()
|
||||
->getFolderObjectFromCombinedIdentifier($combinedIdentifier);
|
||||
}
|
||||
// Cleaning and checking target directory
|
||||
if (!$this->folderObject) {
|
||||
$title = $this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:paramError');
|
||||
$message = $this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:targetNoDir');
|
||||
throw new \RuntimeException($title . ': ' . $message, 1294586845);
|
||||
}
|
||||
if ($this->folderObject->getStorage()->getUid() === 0) {
|
||||
throw new InsufficientFolderAccessPermissionsException(
|
||||
'You are not allowed to access folders outside your storages',
|
||||
1375889838
|
||||
);
|
||||
}
|
||||
|
||||
$pathInfo = [
|
||||
'combined_identifier' => $this->folderObject->getCombinedIdentifier(),
|
||||
];
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
|
||||
$this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($pathInfo);
|
||||
$this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
|
||||
$this->moduleTemplate->addJavaScriptCode(
|
||||
'CreateFolderInlineJavaScript',
|
||||
'var path = "' . $this->target . '";
|
||||
var confirmTitle = '
|
||||
. GeneralUtility::quoteJSvalue(
|
||||
$this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:pleaseConfirm')
|
||||
)
|
||||
. ';
|
||||
var confirmText = '
|
||||
. GeneralUtility::quoteJSvalue(
|
||||
$this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:mess.redraw')
|
||||
)
|
||||
. ';
|
||||
function reload(a) {
|
||||
var params = "&target="+encodeURIComponent(path)+"&number="+a+"&returnUrl=' . rawurlencode($this->returnUrl) . '";
|
||||
var url = \'' . (string)$uriBuilder->buildUriFromRoute('file_newfolder') . '\';
|
||||
if (!changed) {
|
||||
window.location.href = url + params;
|
||||
} else {
|
||||
var modal = top.TYPO3.Modal.confirm(confirmTitle, confirmText);
|
||||
modal.on(\'confirm.button.cancel\', function(e) {
|
||||
top.TYPO3.Modal.currentModal.trigger(\'modal-dismiss\');
|
||||
});
|
||||
modal.on(\'confirm.button.ok\', function(e) {
|
||||
top.TYPO3.Modal.currentModal.trigger(\'modal-dismiss\');
|
||||
window.location.href = url + params;
|
||||
});
|
||||
}
|
||||
}
|
||||
function backToList() {
|
||||
top.goToModule("file_FilelistList");
|
||||
}
|
||||
var changed = 0;'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function, rendering the main module content
|
||||
*/
|
||||
protected function main()
|
||||
{
|
||||
$lang = $this->getLanguageService();
|
||||
$assigns = [];
|
||||
$assigns['target'] = $this->target;
|
||||
if ($this->folderObject->checkActionPermission('add')) {
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
$assigns['moduleUrlTceFile'] = (string)$uriBuilder->buildUriFromRoute('tce_file');
|
||||
$assigns['cshFileNewFolder'] = BackendUtility::cshItem('xMOD_csh_corebe', 'file_newfolder');
|
||||
// Making the selector box for the number of concurrent folder-creations
|
||||
$this->number = MathUtility::forceIntegerInRange($this->number, 1, 10);
|
||||
for ($a = 1; $a <= $this->folderNumber; $a++) {
|
||||
$options = [];
|
||||
$options['value'] = $a;
|
||||
$options['selected'] = ($this->number == $a ? ' selected="selected"' : '');
|
||||
$assigns['options'][] = $options;
|
||||
}
|
||||
// Making the number of new-folder boxes needed:
|
||||
for ($a = 0; $a < $this->number; $a++) {
|
||||
$folder = [];
|
||||
$folder['this'] = $a;
|
||||
$folder['next'] = $a + 1;
|
||||
$assigns['folders'][] = $folder;
|
||||
}
|
||||
// Making submit button for folder creation:
|
||||
$assigns['returnUrl'] = $this->returnUrl;
|
||||
}
|
||||
|
||||
if ($this->folderObject->getStorage()->checkUserActionPermission('add', 'File')) {
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
$assigns['moduleUrlOnlineMedia'] = (string)$uriBuilder->buildUriFromRoute('online_media');
|
||||
$assigns['cshFileNewMedia'] = BackendUtility::cshItem('xMOD_csh_corebe', 'file_newMedia');
|
||||
// Create a list of allowed file extensions with the readable format "youtube, vimeo" etc.
|
||||
$fileExtList = [];
|
||||
$onlineMediaFileExt = OnlineMediaHelperRegistry::getInstance()->getSupportedFileExtensions();
|
||||
foreach ($onlineMediaFileExt as $fileExt) {
|
||||
if (GeneralUtility::verifyFilenameAgainstDenyPattern('.' . $fileExt)) {
|
||||
$fileExtList[] = strtoupper(htmlspecialchars($fileExt));
|
||||
}
|
||||
}
|
||||
$assigns['fileExtList'] = $fileExtList;
|
||||
|
||||
$assigns['moduleUrlTceFile'] = (string)$uriBuilder->buildUriFromRoute('tce_file');
|
||||
$assigns['cshFileNewFile'] = BackendUtility::cshItem('xMOD_csh_corebe', 'file_newfile');
|
||||
// Create a list of allowed file extensions with a text format "*.txt, *.css" etc.
|
||||
$fileExtList = [];
|
||||
$textFileExt = GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['SYS']['textfile_ext'], true);
|
||||
foreach ($textFileExt as $fileExt) {
|
||||
if (GeneralUtility::verifyFilenameAgainstDenyPattern('.' . $fileExt)) {
|
||||
$fileExtList[] = strtoupper(htmlspecialchars($fileExt));
|
||||
}
|
||||
}
|
||||
$assigns['txtFileExtList'] = $fileExtList;
|
||||
}
|
||||
|
||||
$buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
|
||||
// CSH button
|
||||
$helpButton = $buttonBar->makeHelpButton()
|
||||
->setFieldName('file_new')
|
||||
->setModuleName('xMOD_csh_corebe');
|
||||
$buttonBar->addButton($helpButton);
|
||||
|
||||
// Back
|
||||
if ($this->returnUrl) {
|
||||
$backButton = $buttonBar->makeLinkButton()
|
||||
->setHref($this->returnUrl)
|
||||
->setTitle($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.goBack'))
|
||||
->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-view-go-back', Icon::SIZE_SMALL));
|
||||
$buttonBar->addButton($backButton);
|
||||
}
|
||||
|
||||
// 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/File/CreateFolder.html'
|
||||
));
|
||||
$view->assignMultiple($assigns);
|
||||
$this->moduleTemplate->setContent($view->render());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns LanguageService
|
||||
*
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,425 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller\File;
|
||||
|
||||
/*
|
||||
* 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\Form\FormResultCompiler;
|
||||
use TYPO3\CMS\Backend\Form\NodeFactory;
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
|
||||
use TYPO3\CMS\Backend\Template\DocumentTemplate;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplate;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Compatibility\PublicMethodDeprecationTrait;
|
||||
use TYPO3\CMS\Core\Compatibility\PublicPropertyDeprecationTrait;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Http\RedirectResponse;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageService;
|
||||
use TYPO3\CMS\Core\Resource\Exception\InsufficientFileAccessPermissionsException;
|
||||
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\HttpUtility;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
|
||||
/**
|
||||
* Script Class for rendering the file editing screen
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class EditFileController
|
||||
{
|
||||
use PublicMethodDeprecationTrait;
|
||||
use PublicPropertyDeprecationTrait;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $deprecatedPublicMethods = [
|
||||
'main' => 'Using EditFileController::main() is deprecated and will not be possible anymore in TYPO3 v10.0.',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $deprecatedPublicProperties = [
|
||||
'origTarget' => 'Using $origTarget of class EditFileController from outside is discouraged, as this variable is only used for internal storage.',
|
||||
'target' => 'Using $target of class EditFileController from outside is discouraged, as this variable is only used for internal storage.',
|
||||
'returnUrl' => 'Using $returnUrl of class EditFileController from outside is discouraged, as this variable is only used for internal storage.',
|
||||
'content' => 'Using $content of class EditFileController from outside is discouraged, as this variable is only used for internal storage.',
|
||||
'title' => 'Using $title of class EditFileController from outside is discouraged, as this variable is only used for internal storage.',
|
||||
'doc' => 'Using $doc of class EditFileController from outside is discouraged, as this variable is only used for internal storage.',
|
||||
];
|
||||
/**
|
||||
* Module content accumulated.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $content;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $title;
|
||||
|
||||
/**
|
||||
* Document template object
|
||||
*
|
||||
* @var DocumentTemplate
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, unused
|
||||
*/
|
||||
protected $doc;
|
||||
|
||||
/**
|
||||
* Original input target
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $origTarget;
|
||||
|
||||
/**
|
||||
* The original target, but validated.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $target;
|
||||
|
||||
/**
|
||||
* Return URL of list module.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $returnUrl;
|
||||
|
||||
/**
|
||||
* the file that is being edited on
|
||||
*
|
||||
* @var \TYPO3\CMS\Core\Resource\AbstractFile
|
||||
*/
|
||||
protected $fileObject;
|
||||
|
||||
/**
|
||||
* ModuleTemplate object
|
||||
*
|
||||
* @var ModuleTemplate
|
||||
*/
|
||||
protected $moduleTemplate;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
|
||||
// @deprecated since TYPO3 v9, will be moved out of __construct() in TYPO3 v10.0
|
||||
$this->init($GLOBALS['TYPO3_REQUEST']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the request, currently everything is handled and put together via "main()"
|
||||
*
|
||||
* @param ServerRequestInterface $request the current request
|
||||
* @return ResponseInterface the response with the content
|
||||
*/
|
||||
public function mainAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
if ($response = $this->process()) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
return new HtmlResponse($this->moduleTemplate->renderContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function, rendering the actual content of the editing page
|
||||
*/
|
||||
protected function main()
|
||||
{
|
||||
$response = $this->process();
|
||||
|
||||
if ($response instanceof RedirectResponse) {
|
||||
HttpUtility::redirect($response->getHeaderLine('location'), $response->getStatusCode());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize script class
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
*
|
||||
* @throws InsufficientFileAccessPermissionsException
|
||||
*/
|
||||
protected function init(ServerRequestInterface $request): void
|
||||
{
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$queryParams = $request->getQueryParams();
|
||||
|
||||
// Setting target, which must be a file reference to a file within the mounts.
|
||||
$this->target = $this->origTarget = $parsedBody['target'] ?? $queryParams['target'] ?? '';
|
||||
$this->returnUrl = GeneralUtility::sanitizeLocalUrl($parsedBody['returnUrl'] ?? $queryParams['returnUrl'] ?? '');
|
||||
// create the file object
|
||||
if ($this->target) {
|
||||
$this->fileObject = ResourceFactory::getInstance()
|
||||
->retrieveFileOrFolderObject($this->target);
|
||||
}
|
||||
// Cleaning and checking target directory
|
||||
if (!$this->fileObject) {
|
||||
$title = $this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:paramError');
|
||||
$message = $this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:targetNoDir');
|
||||
throw new \RuntimeException($title . ': ' . $message, 1294586841);
|
||||
}
|
||||
if ($this->fileObject->getStorage()->getUid() === 0) {
|
||||
throw new InsufficientFileAccessPermissionsException(
|
||||
'You are not allowed to access files outside your storages',
|
||||
1375889832
|
||||
);
|
||||
}
|
||||
|
||||
// Setting the title and the icon
|
||||
$icon = $this->moduleTemplate->getIconFactory()->getIcon('apps-filetree-root', Icon::SIZE_SMALL)->render();
|
||||
$this->title = $icon
|
||||
. htmlspecialchars(
|
||||
$this->fileObject->getStorage()->getName()
|
||||
) . ': ' . htmlspecialchars(
|
||||
$this->fileObject->getIdentifier()
|
||||
);
|
||||
|
||||
// Setting template object
|
||||
$this->moduleTemplate->addJavaScriptCode(
|
||||
'FileEditBackToList',
|
||||
'function backToList() {
|
||||
top.goToModule("file_FilelistList");
|
||||
}'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function, rendering the actual content of the editing page
|
||||
*
|
||||
* @return ResponseInterface|null Possible redirect response
|
||||
*/
|
||||
protected function process(): ?ResponseInterface
|
||||
{
|
||||
$dataColumnDefinition = [
|
||||
'label' => htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:file'))
|
||||
. ' ' . htmlspecialchars($this->target),
|
||||
'config' => [
|
||||
'type' => 'text',
|
||||
'cols' => 48,
|
||||
'wrap' => 'OFF',
|
||||
],
|
||||
'defaultExtras' => 'fixed-font: enable-tab'
|
||||
];
|
||||
|
||||
$this->getButtonsInternal();
|
||||
// Hook: before compiling the output
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/file_edit.php']['preOutputProcessingHook'] ?? [] as $hookFunction) {
|
||||
$hookParameters = [
|
||||
'content' => &$this->content,
|
||||
'target' => &$this->target,
|
||||
'dataColumnDefinition' => &$dataColumnDefinition,
|
||||
];
|
||||
GeneralUtility::callUserFunction($hookFunction, $hookParameters, $this);
|
||||
}
|
||||
|
||||
$assigns = [];
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
$assigns['moduleUrlTceFile'] = (string)$uriBuilder->buildUriFromRoute('tce_file');
|
||||
$assigns['fileName'] = $this->fileObject->getName();
|
||||
|
||||
$extList = $GLOBALS['TYPO3_CONF_VARS']['SYS']['textfile_ext'];
|
||||
try {
|
||||
if (!$extList || !GeneralUtility::inList($extList, $this->fileObject->getExtension())) {
|
||||
// @todo throw a minor exception here, not the global one
|
||||
throw new \Exception('Files with that extension are not editable. Allowed extensions are: ' . $extList, 1476050135);
|
||||
}
|
||||
|
||||
// Making the formfields
|
||||
$hValue = (string)$uriBuilder->buildUriFromRoute('file_edit', [
|
||||
'target' => $this->origTarget,
|
||||
'returnUrl' => $this->returnUrl
|
||||
]);
|
||||
|
||||
$formData = [
|
||||
'databaseRow' => [
|
||||
'uid' => 0,
|
||||
'data' => $this->fileObject->getContents(),
|
||||
'target' => $this->fileObject->getUid(),
|
||||
'redirect' => $hValue,
|
||||
],
|
||||
'tableName' => 'editfile',
|
||||
'processedTca' => [
|
||||
'columns' => [
|
||||
'data' => $dataColumnDefinition,
|
||||
'target' => [
|
||||
'config' => [
|
||||
'type' => 'input',
|
||||
'renderType' => 'hidden',
|
||||
],
|
||||
],
|
||||
'redirect' => [
|
||||
'config' => [
|
||||
'type' => 'input',
|
||||
'renderType' => 'hidden',
|
||||
],
|
||||
],
|
||||
],
|
||||
'types' => [
|
||||
1 => [
|
||||
'showitem' => 'data,target,redirect',
|
||||
],
|
||||
],
|
||||
],
|
||||
'recordTypeValue' => 1,
|
||||
'inlineStructure' => [],
|
||||
'renderType' => 'fullRecordContainer',
|
||||
];
|
||||
|
||||
$resultArray = GeneralUtility::makeInstance(NodeFactory::class)->create($formData)->render();
|
||||
$formResultCompiler = GeneralUtility::makeInstance(FormResultCompiler::class);
|
||||
$formResultCompiler->mergeResult($resultArray);
|
||||
|
||||
$form = $formResultCompiler->addCssFiles()
|
||||
. $resultArray['html']
|
||||
. $formResultCompiler->printNeededJSFunctions();
|
||||
|
||||
$assigns['form'] = $form;
|
||||
} catch (\Exception $e) {
|
||||
// @todo catch dedicated exceptions, not the global one, if possible
|
||||
$flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $e->getMessage(), '', FlashMessage::ERROR, true);
|
||||
|
||||
$flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
|
||||
$defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
|
||||
$defaultFlashMessageQueue->enqueue($flashMessage);
|
||||
|
||||
return new RedirectResponse($this->returnUrl, 500);
|
||||
}
|
||||
|
||||
// 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/File/EditFile.html'
|
||||
));
|
||||
$view->assignMultiple($assigns);
|
||||
$pageContent = $view->render();
|
||||
|
||||
// Hook: after compiling the output
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/file_edit.php']['postOutputProcessingHook'] ?? [] as $hookFunction) {
|
||||
$hookParameters = [
|
||||
'pageContent' => &$pageContent,
|
||||
'target' => &$this->target
|
||||
];
|
||||
GeneralUtility::callUserFunction($hookFunction, $hookParameters, $this);
|
||||
}
|
||||
|
||||
$this->content .= $pageContent;
|
||||
$this->moduleTemplate->setContent($this->content);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the buttons for the docheader and returns them as an
|
||||
*
|
||||
* @deprecated since TYPO3 v9, will be set protected in TYPO3 v10.0
|
||||
*/
|
||||
public function getButtons()
|
||||
{
|
||||
trigger_error('EditFileController->getButtons() will be replaced by protected method getButtonsInternal() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
|
||||
$this->getButtonsInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the buttons for the docheader
|
||||
*/
|
||||
protected function getButtonsInternal(): void
|
||||
{
|
||||
$buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
|
||||
|
||||
$lang = $this->getLanguageService();
|
||||
// CSH button
|
||||
$helpButton = $buttonBar->makeHelpButton()
|
||||
->setFieldName('file_edit')
|
||||
->setModuleName('xMOD_csh_corebe');
|
||||
$buttonBar->addButton($helpButton);
|
||||
|
||||
// Save button
|
||||
$saveButton = $buttonBar->makeInputButton()
|
||||
->setName('_save')
|
||||
->setValue('1')
|
||||
->setForm('EditFileController')
|
||||
->setTitle($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:file_edit.php.submit'))
|
||||
->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-save', Icon::SIZE_SMALL));
|
||||
|
||||
// Save and Close button
|
||||
$saveAndCloseButton = $buttonBar->makeInputButton()
|
||||
->setName('_saveandclosedok')
|
||||
->setValue('1')
|
||||
->setForm('EditFileController')
|
||||
->setOnClick(
|
||||
'document.editform.elements.namedItem("data[editfile][0][redirect]").value='
|
||||
. GeneralUtility::quoteJSvalue($this->returnUrl)
|
||||
. ';'
|
||||
)
|
||||
->setTitle($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:file_edit.php.saveAndClose'))
|
||||
->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
|
||||
'actions-document-save-close',
|
||||
Icon::SIZE_SMALL
|
||||
));
|
||||
|
||||
$splitButton = $buttonBar->makeSplitButton()
|
||||
->addItem($saveButton)
|
||||
->addItem($saveAndCloseButton);
|
||||
$buttonBar->addButton($splitButton, ButtonBar::BUTTON_POSITION_LEFT, 20);
|
||||
|
||||
// Cancel button
|
||||
$closeButton = $buttonBar->makeLinkButton()
|
||||
->setHref('#')
|
||||
->setOnClick('backToList(); return false;')
|
||||
->setTitle($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.cancel'))
|
||||
->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-close', Icon::SIZE_SMALL));
|
||||
$buttonBar->addButton($closeButton, ButtonBar::BUTTON_POSITION_LEFT, 10);
|
||||
|
||||
// Make shortcut:
|
||||
$shortButton = $buttonBar->makeShortcutButton()
|
||||
->setModuleName('file_edit')
|
||||
->setGetVariables(['target']);
|
||||
$buttonBar->addButton($shortButton);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns LanguageService
|
||||
*
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current BE user.
|
||||
*
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller\File;
|
||||
|
||||
/*
|
||||
* 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\Clipboard\Clipboard;
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Compatibility\PublicMethodDeprecationTrait;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Http\RedirectResponse;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Resource\DuplicationBehavior;
|
||||
use TYPO3\CMS\Core\Resource\File;
|
||||
use TYPO3\CMS\Core\Resource\Folder;
|
||||
use TYPO3\CMS\Core\Resource\ProcessedFile;
|
||||
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||
use TYPO3\CMS\Core\Utility\File\ExtendedFileUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\HttpUtility;
|
||||
|
||||
/**
|
||||
* Gateway for TCE (TYPO3 Core Engine) file-handling through POST forms.
|
||||
* This script serves as the file administration part of the TYPO3 Core Engine.
|
||||
* Basically it includes two libraries which are used to manipulate files on the server.
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class FileController
|
||||
{
|
||||
use PublicMethodDeprecationTrait;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $deprecatedPublicMethods = [
|
||||
'initClipboard' => 'Using FileController::initClipboard() is deprecated and will not be possible anymore in TYPO3 v10.0.',
|
||||
'main' => 'Using FileController::main() is deprecated and will not be possible anymore in TYPO3 v10.0.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Array of file-operations.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $file;
|
||||
|
||||
/**
|
||||
* Clipboard operations array
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $CB;
|
||||
|
||||
/**
|
||||
* Defines behaviour when uploading files with names that already exist; possible values are
|
||||
* the values of the \TYPO3\CMS\Core\Resource\DuplicationBehavior enumeration
|
||||
*
|
||||
* @var \TYPO3\CMS\Core\Resource\DuplicationBehavior
|
||||
*/
|
||||
protected $overwriteExistingFiles;
|
||||
|
||||
/**
|
||||
* The page where the user should be redirected after everything is done
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $redirect;
|
||||
|
||||
/**
|
||||
* Internal, dynamic:
|
||||
* File processor object
|
||||
*
|
||||
* @var ExtendedFileUtility
|
||||
*/
|
||||
protected $fileProcessor;
|
||||
|
||||
/**
|
||||
* The result array from the file processor
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fileData;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// @deprecated since TYPO3 v9, will be moved out of __construct() in TYPO3 v10.0
|
||||
$this->init($GLOBALS['TYPO3_REQUEST']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects the request object for the current request or subrequest
|
||||
* As this controller goes only through the main() method, it just redirects to the given URL afterwards.
|
||||
*
|
||||
* @param ServerRequestInterface $request the current request
|
||||
* @return ResponseInterface the response with the content
|
||||
*/
|
||||
public function mainAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->main();
|
||||
|
||||
BackendUtility::setUpdateSignal('updateFolderTree');
|
||||
|
||||
// go and edit the new created file
|
||||
if ($request->getParsedBody()['edit'] ?? '') {
|
||||
/** @var File $file */
|
||||
$file = $this->fileData['newfile'][0];
|
||||
if ($file !== null) {
|
||||
$this->redirect = $this->getFileEditRedirect($file) ?? $this->redirect;
|
||||
}
|
||||
}
|
||||
if ($this->redirect) {
|
||||
return new RedirectResponse(
|
||||
GeneralUtility::locationHeaderUrl($this->redirect),
|
||||
303
|
||||
);
|
||||
}
|
||||
// empty response
|
||||
return new HtmlResponse('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the actual process from within the ajaxExec function
|
||||
* therefore, it does exactly the same as the real typo3/tce_file.php
|
||||
* but without calling the "finish" method, thus makes it simpler to deal with the
|
||||
* actual return value
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function processAjaxRequest(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->main();
|
||||
$errors = $this->fileProcessor->getErrorMessages();
|
||||
if (!empty($errors)) {
|
||||
return (new HtmlResponse('<t3err>' . implode(',', $errors) . '</t3err>'))->withStatus(500, '(AJAX)');
|
||||
}
|
||||
$flatResult = [];
|
||||
foreach ($this->fileData as $action => $results) {
|
||||
foreach ($results as $result) {
|
||||
if (is_array($result)) {
|
||||
foreach ($result as $subResult) {
|
||||
$flatResult[$action][] = $this->flattenResultDataValue($subResult);
|
||||
}
|
||||
} else {
|
||||
$flatResult[$action][] = $this->flattenResultDataValue($result);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (new JsonResponse())->setPayload($flatResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax entry point to check if a file exists in a folder
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function fileExistsInFolderAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$fileName = $request->getParsedBody()['fileName'] ?? $request->getQueryParams()['fileName'] ?? null;
|
||||
$fileTarget = $request->getParsedBody()['fileTarget'] ?? $request->getQueryParams()['fileTarget'] ?? null;
|
||||
|
||||
$fileFactory = GeneralUtility::makeInstance(ResourceFactory::class);
|
||||
$fileTargetObject = $fileFactory->retrieveFileOrFolderObject($fileTarget);
|
||||
$processedFileName = $fileTargetObject->getStorage()->sanitizeFileName($fileName, $fileTargetObject);
|
||||
|
||||
$result = [];
|
||||
if ($fileTargetObject->hasFile($processedFileName)) {
|
||||
$result = $this->flattenResultDataValue($fileTargetObject->getStorage()->getFileInFolder($processedFileName, $fileTargetObject));
|
||||
}
|
||||
return (new JsonResponse())->setPayload($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registering incoming data
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
*/
|
||||
protected function init(ServerRequestInterface $request): void
|
||||
{
|
||||
// Set the GPvars from outside
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$queryParams = $request->getQueryParams();
|
||||
$this->file = $parsedBody['data'] ?? $queryParams['data'] ?? null;
|
||||
if ($this->file === null) {
|
||||
// This happens in clipboard mode only
|
||||
$this->redirect = GeneralUtility::sanitizeLocalUrl($parsedBody['redirect'] ?? $queryParams['redirect'] ?? '');
|
||||
} else {
|
||||
$mode = key($this->file);
|
||||
$elementKey = key($this->file[$mode]);
|
||||
$this->redirect = GeneralUtility::sanitizeLocalUrl($this->file[$mode][$elementKey]['redirect']);
|
||||
}
|
||||
$this->CB = $parsedBody['CB'] ?? $queryParams['CB'] ?? null;
|
||||
|
||||
if (isset($this->file['rename'][0]['conflictMode'])) {
|
||||
$conflictMode = $this->file['rename'][0]['conflictMode'];
|
||||
unset($this->file['rename'][0]['conflictMode']);
|
||||
$this->overwriteExistingFiles = DuplicationBehavior::cast($conflictMode);
|
||||
} else {
|
||||
$this->overwriteExistingFiles = DuplicationBehavior::cast($parsedBody['overwriteExistingFiles'] ?? $queryParams['overwriteExistingFiles'] ?? null);
|
||||
}
|
||||
$this->initClipboard();
|
||||
$this->fileProcessor = GeneralUtility::makeInstance(ExtendedFileUtility::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the Clipboard. This will fetch the data about files to paste/delete if such an action has been sent.
|
||||
*/
|
||||
protected function initClipboard(): void
|
||||
{
|
||||
if (is_array($this->CB)) {
|
||||
$clipObj = GeneralUtility::makeInstance(Clipboard::class);
|
||||
$clipObj->initializeClipboard();
|
||||
if ($this->CB['paste']) {
|
||||
$clipObj->setCurrentPad($this->CB['pad']);
|
||||
$this->file = $clipObj->makePasteCmdArray_file($this->CB['paste'], $this->file);
|
||||
}
|
||||
if ($this->CB['delete']) {
|
||||
$clipObj->setCurrentPad($this->CB['pad']);
|
||||
$this->file = $clipObj->makeDeleteCmdArray_file($this->file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performing the file admin action:
|
||||
* Initializes the objects, setting permissions, sending data to object.
|
||||
*/
|
||||
protected function main(): void
|
||||
{
|
||||
// Initializing:
|
||||
$this->fileProcessor->setActionPermissions();
|
||||
$this->fileProcessor->setExistingFilesConflictMode($this->overwriteExistingFiles);
|
||||
$this->fileProcessor->start($this->file);
|
||||
$this->fileData = $this->fileProcessor->processData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets URI to be used for editing given file (if file extension is defined in textfile_ext)
|
||||
*
|
||||
* @param File $file to be edited
|
||||
* @return string|null URI to be redirected to
|
||||
* @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
|
||||
*/
|
||||
protected function getFileEditRedirect(File $file): ?string
|
||||
{
|
||||
$textFileExtensionList = $GLOBALS['TYPO3_CONF_VARS']['SYS']['textfile_ext'] ?? '';
|
||||
if (!GeneralUtility::inList($textFileExtensionList, $file->getExtension())) {
|
||||
return null;
|
||||
}
|
||||
$properties = $file->getProperties();
|
||||
$urlParameters = [
|
||||
'target' => $properties['storage'] . ':' . $properties['identifier']
|
||||
];
|
||||
if ($this->redirect) {
|
||||
$urlParameters['returnUrl'] = $this->redirect;
|
||||
}
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
return (string)$uriBuilder->buildUriFromRoute('file_edit', $urlParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirecting the user after the processing has been done.
|
||||
* Might also display error messages directly, if any.
|
||||
*
|
||||
* @deprecated since TYPO3 v9. Will be removed in TYPO3 v10.0.
|
||||
*/
|
||||
public function finish()
|
||||
{
|
||||
trigger_error('FileController->finish() will be removed in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
|
||||
BackendUtility::setUpdateSignal('updateFolderTree');
|
||||
if ($this->redirect) {
|
||||
HttpUtility::redirect($this->redirect);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flatten result value from FileProcessor
|
||||
*
|
||||
* The value can be a File, Folder or boolean
|
||||
*
|
||||
* @param bool|File|Folder $result
|
||||
*
|
||||
* @return bool|string|array
|
||||
*/
|
||||
protected function flattenResultDataValue($result)
|
||||
{
|
||||
if ($result instanceof File) {
|
||||
$thumbUrl = '';
|
||||
if (GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], $result->getExtension())) {
|
||||
$processedFile = $result->process(ProcessedFile::CONTEXT_IMAGEPREVIEW, []);
|
||||
if ($processedFile) {
|
||||
$thumbUrl = $processedFile->getPublicUrl(true);
|
||||
}
|
||||
}
|
||||
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
$result = array_merge(
|
||||
$result->toArray(),
|
||||
[
|
||||
'date' => BackendUtility::date($result->getModificationTime()),
|
||||
'icon' => $iconFactory->getIconForFileExtension($result->getExtension(), Icon::SIZE_SMALL)->render(),
|
||||
'thumbUrl' => $thumbUrl
|
||||
]
|
||||
);
|
||||
} elseif ($result instanceof Folder) {
|
||||
$result = $result->getIdentifier();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current BE user.
|
||||
*
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller\File;
|
||||
|
||||
/*
|
||||
* 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\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\Resource\Exception\InsufficientFolderAccessPermissionsException;
|
||||
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Script Class for display up to 10 upload fields
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class FileUploadController
|
||||
{
|
||||
use PublicPropertyDeprecationTrait;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $deprecatedPublicProperties = [
|
||||
'title' => 'Using $title of class FileUploadController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'target' => 'Using $target of class FileUploadController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'returnUrl' => 'Using $returnUrl of class FileUploadController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'content' => 'Using $content of class FileUploadController from outside is discouraged as this variable is only used for internal storage.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Name of the filemount
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $title;
|
||||
|
||||
/**
|
||||
* Set with the target path inputted in &target
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $target;
|
||||
|
||||
/**
|
||||
* Return URL of list module.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $returnUrl;
|
||||
|
||||
/**
|
||||
* Accumulating content
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $content;
|
||||
|
||||
/**
|
||||
* The folder object which is the target directory for the upload
|
||||
*
|
||||
* @var \TYPO3\CMS\Core\Resource\Folder $folderObject
|
||||
*/
|
||||
protected $folderObject;
|
||||
|
||||
/**
|
||||
* 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 moved out of __construct() in TYPO3 v10.0
|
||||
$this->init($GLOBALS['TYPO3_REQUEST']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the request, currently everything is handled and put together via "renderContent()"
|
||||
*
|
||||
* @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->moduleTemplate->renderContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function, rendering the upload file form fields
|
||||
*
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function main()
|
||||
{
|
||||
trigger_error('FileUploadController->main() will be replaced by protected method renderContent() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
|
||||
$this->renderContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* This function renders the upload form
|
||||
*
|
||||
* @return string The HTML form as a string, ready for outputting
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function renderUploadForm()
|
||||
{
|
||||
trigger_error('FileUploadController->renderUploadForm() will be replaced by protected method renderUploadFormInternal() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
|
||||
return $this->renderUploadFormInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @throws InsufficientFolderAccessPermissionsException
|
||||
*/
|
||||
protected function init(ServerRequestInterface $request): void
|
||||
{
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$queryParams = $request->getQueryParams();
|
||||
|
||||
/** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
|
||||
$uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
|
||||
// Initialize GPvars:
|
||||
$this->target = $parsedBody['target'] ?? $queryParams['target'] ?? null;
|
||||
$this->returnUrl = GeneralUtility::sanitizeLocalUrl($parsedBody['returnUrl'] ?? $queryParams['returnUrl'] ?? '');
|
||||
if (!$this->returnUrl) {
|
||||
$this->returnUrl = (string)$uriBuilder->buildUriFromRoute('file_list', [
|
||||
'id' => rawurlencode($this->target)
|
||||
]);
|
||||
}
|
||||
// Create the folder object
|
||||
if ($this->target) {
|
||||
$this->folderObject = ResourceFactory::getInstance()
|
||||
->retrieveFileOrFolderObject($this->target);
|
||||
}
|
||||
if ($this->folderObject->getStorage()->getUid() === 0) {
|
||||
throw new InsufficientFolderAccessPermissionsException(
|
||||
'You are not allowed to access folders outside your storages',
|
||||
1375889834
|
||||
);
|
||||
}
|
||||
|
||||
// Cleaning and checking target directory
|
||||
if (!$this->folderObject) {
|
||||
$title = $this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:paramError');
|
||||
$message = $this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:targetNoDir');
|
||||
throw new \RuntimeException($title . ': ' . $message, 1294586843);
|
||||
}
|
||||
|
||||
// Setting up the context sensitive menu
|
||||
$this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
|
||||
|
||||
// building pathInfo for metaInformation
|
||||
$pathInfo = [
|
||||
'combined_identifier' => $this->folderObject->getCombinedIdentifier(),
|
||||
];
|
||||
$this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($pathInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render module content
|
||||
*/
|
||||
protected function renderContent(): void
|
||||
{
|
||||
$lang = $this->getLanguageService();
|
||||
/** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
|
||||
$uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
|
||||
|
||||
// set page title
|
||||
$this->moduleTemplate->setTitle($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:file_upload.php.pagetitle'));
|
||||
|
||||
$pageContent = '<form action="'
|
||||
. htmlspecialchars((string)$uriBuilder->buildUriFromRoute('tce_file'))
|
||||
. '" method="post" id="FileUploadController" name="editform" enctype="multipart/form-data">';
|
||||
// Make page header:
|
||||
$pageContent .= '<h1>' . $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:file_upload.php.pagetitle') . '</h1>';
|
||||
$pageContent .= $this->renderUploadFormInternal();
|
||||
|
||||
// Header Buttons
|
||||
$buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
|
||||
|
||||
// csh button
|
||||
$cshButton = $buttonBar->makeHelpButton()
|
||||
->setModuleName('xMOD_csh_corebe')
|
||||
->setFieldName('file_upload');
|
||||
$buttonBar->addButton($cshButton);
|
||||
|
||||
// back button
|
||||
if ($this->returnUrl) {
|
||||
$backButton = $buttonBar->makeLinkButton()
|
||||
->setHref($this->returnUrl)
|
||||
->setTitle($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.goBack'))
|
||||
->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-view-go-back', Icon::SIZE_SMALL));
|
||||
$buttonBar->addButton($backButton);
|
||||
}
|
||||
|
||||
$pageContent .= '</form>';
|
||||
$this->content .= '<div>' . $pageContent . '</div>';
|
||||
$this->moduleTemplate->setContent($this->content);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function renders the upload form
|
||||
*
|
||||
* @return string The HTML form as a string, ready for outputting
|
||||
*/
|
||||
protected function renderUploadFormInternal(): string
|
||||
{
|
||||
$content = '
|
||||
<div class="form-group">
|
||||
<input type="file" multiple="multiple" class="form-control" name="upload_1[]" />
|
||||
<input type="hidden" name="data[upload][1][target]" value="' . htmlspecialchars($this->folderObject->getCombinedIdentifier()) . '" />
|
||||
<input type="hidden" name="data[upload][1][data]" value="1" />
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label for="overwriteExistingFiles">
|
||||
<input type="checkbox" name="overwriteExistingFiles" id="overwriteExistingFiles" value="replace" /> ' . htmlspecialchars($this->getLanguageService()->getLL('overwriteExistingFiles')) . '</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="hidden" name="data[upload][1][redirect]" value="' . $this->returnUrl . '" />
|
||||
<input class="btn btn-primary" type="submit" value="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:file_upload.php.submit')) . '" />
|
||||
</div>
|
||||
<div class="callout callout-warning">
|
||||
' . htmlspecialchars($this->getLanguageService()->getLL('uploadMultipleFilesInfo')) . '
|
||||
</div>
|
||||
';
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller\File;
|
||||
|
||||
/*
|
||||
* 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\Components\ButtonBar;
|
||||
use TYPO3\CMS\Backend\Template\DocumentTemplate;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplate;
|
||||
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\Resource\DuplicationBehavior;
|
||||
use TYPO3\CMS\Core\Resource\Exception\InsufficientFileAccessPermissionsException;
|
||||
use TYPO3\CMS\Core\Resource\File;
|
||||
use TYPO3\CMS\Core\Resource\Folder;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
|
||||
/**
|
||||
* Script Class for the rename-file form.
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class RenameFileController
|
||||
{
|
||||
use PublicPropertyDeprecationTrait;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $deprecatedPublicProperties = [
|
||||
'title' => 'Using $title of class RenameFileController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'target' => 'Using $target of class RenameFileController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'returnUrl' => 'Using $returnUrl of class RenameFileController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'content' => 'Using $content of class RenameFileController from outside is discouraged as this variable is only used for internal storage.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Name of the filemount
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $title;
|
||||
|
||||
/**
|
||||
* Target path
|
||||
*
|
||||
* @var string
|
||||
* @internal
|
||||
*/
|
||||
protected $target;
|
||||
|
||||
/**
|
||||
* The file or folder object that should be renamed
|
||||
*
|
||||
* @var File|Folder $fileOrFolderObject
|
||||
*/
|
||||
protected $fileOrFolderObject;
|
||||
|
||||
/**
|
||||
* Return URL of list module.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $returnUrl;
|
||||
|
||||
/**
|
||||
* Accumulating content
|
||||
*
|
||||
* @var string
|
||||
* @internal
|
||||
*/
|
||||
protected $content;
|
||||
|
||||
/**
|
||||
* ModuleTemplate object
|
||||
*
|
||||
* @var ModuleTemplate
|
||||
*/
|
||||
protected $moduleTemplate;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
|
||||
|
||||
// @deprecated since TYPO3 v9, will be moved out of __construct() in TYPO3 v10.0
|
||||
$this->init($GLOBALS['TYPO3_REQUEST']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the request, currently everything is handled and put together via "renderContent()"
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface the response with the content
|
||||
*/
|
||||
public function mainAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->renderContent();
|
||||
|
||||
return new HtmlResponse($this->moduleTemplate->renderContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function, rendering the content of the rename form
|
||||
*
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function main()
|
||||
{
|
||||
trigger_error('RenameFileController->main() will be replaced by protected method renderContent() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
|
||||
$this->renderContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @throws InsufficientFileAccessPermissionsException
|
||||
*/
|
||||
protected function init(ServerRequestInterface $request): void
|
||||
{
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$queryParams = $request->getQueryParams();
|
||||
|
||||
// Initialize GPvars:
|
||||
$this->target = $parsedBody['target'] ?? $queryParams['target'] ?? null;
|
||||
$this->returnUrl = GeneralUtility::sanitizeLocalUrl($parsedBody['returnUrl'] ?? $queryParams['returnUrl'] ?? '');
|
||||
// Cleaning and checking target
|
||||
if ($this->target) {
|
||||
$this->fileOrFolderObject = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance()->retrieveFileOrFolderObject($this->target);
|
||||
}
|
||||
if (!$this->fileOrFolderObject) {
|
||||
$title = $this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:paramError');
|
||||
$message = $this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:targetNoDir');
|
||||
throw new \RuntimeException($title . ': ' . $message, 1294586844);
|
||||
}
|
||||
if ($this->fileOrFolderObject->getStorage()->getUid() === 0) {
|
||||
throw new InsufficientFileAccessPermissionsException('You are not allowed to access files outside your storages', 1375889840);
|
||||
}
|
||||
|
||||
// If a folder should be renamed, AND the returnURL should go to the old directory name, the redirect is forced
|
||||
// so the redirect will NOT end in an error message
|
||||
// this case only happens if you select the folder itself in the foldertree and then use the clickmenu to
|
||||
// rename the folder
|
||||
if ($this->fileOrFolderObject instanceof Folder) {
|
||||
$parsedUrl = parse_url($this->returnUrl);
|
||||
$queryParts = GeneralUtility::explodeUrl2Array(urldecode($parsedUrl['query']));
|
||||
if ($queryParts['id'] === $this->fileOrFolderObject->getCombinedIdentifier()) {
|
||||
$this->returnUrl = str_replace(
|
||||
urlencode($queryParts['id']),
|
||||
urlencode($this->fileOrFolderObject->getStorage()->getRootLevelFolder()->getCombinedIdentifier()),
|
||||
$this->returnUrl
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// building pathInfo for metaInformation
|
||||
$pathInfo = [
|
||||
'combined_identifier' => $this->fileOrFolderObject->getCombinedIdentifier(),
|
||||
];
|
||||
$this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($pathInfo);
|
||||
|
||||
// Setting up the context sensitive menu
|
||||
$this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
|
||||
$this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/RenameFile');
|
||||
|
||||
// Add javaScript
|
||||
$this->moduleTemplate->addJavaScriptCode(
|
||||
'RenameFileInlineJavaScript',
|
||||
'function backToList() {top.goToModule("file_FilelistList");}'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render module content
|
||||
*/
|
||||
protected function renderContent(): void
|
||||
{
|
||||
$assigns = [];
|
||||
/** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
|
||||
$uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
|
||||
$assigns['moduleUrlTceFile'] = (string)$uriBuilder->buildUriFromRoute('tce_file');
|
||||
$assigns['returnUrl'] = $this->returnUrl;
|
||||
|
||||
if ($this->fileOrFolderObject instanceof Folder) {
|
||||
$fileIdentifier = $this->fileOrFolderObject->getCombinedIdentifier();
|
||||
$targetLabel = 'file_rename.php.label.target.folder';
|
||||
} else {
|
||||
$fileIdentifier = $this->fileOrFolderObject->getUid();
|
||||
$targetLabel = 'file_rename.php.label.target.file';
|
||||
$assigns['conflictMode'] = DuplicationBehavior::cast(DuplicationBehavior::RENAME);
|
||||
$assigns['destination'] = $this->fileOrFolderObject->getParentFolder()->getCombinedIdentifier();
|
||||
}
|
||||
|
||||
$assigns['fileName'] = $this->fileOrFolderObject->getName();
|
||||
$assigns['fileIdentifier'] = $fileIdentifier;
|
||||
$assigns['fieldLabel'] = $targetLabel;
|
||||
|
||||
// Create buttons
|
||||
$buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
|
||||
|
||||
// csh button
|
||||
$cshButton = $buttonBar->makeHelpButton()
|
||||
->setModuleName('xMOD_csh_corebe')
|
||||
->setFieldName('file_rename');
|
||||
$buttonBar->addButton($cshButton);
|
||||
|
||||
// back button
|
||||
if ($this->returnUrl) {
|
||||
$backButton = $buttonBar->makeLinkButton()
|
||||
->setHref($this->returnUrl)
|
||||
->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.goBack'))
|
||||
->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-close', Icon::SIZE_SMALL));
|
||||
$buttonBar->addButton($backButton);
|
||||
}
|
||||
|
||||
// Save and Close button
|
||||
$saveAndCloseButton = $buttonBar->makeInputButton()
|
||||
->setName('_saveandclose')
|
||||
->setValue('1')
|
||||
->setShowLabelText(true)
|
||||
->setClasses('t3js-submit-file-rename')
|
||||
->setForm('RenameFileController')
|
||||
->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:file_edit.php.saveAndClose'))
|
||||
->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-save-close', Icon::SIZE_SMALL));
|
||||
|
||||
$buttonBar->addButton($saveAndCloseButton, ButtonBar::BUTTON_POSITION_LEFT, 20);
|
||||
|
||||
$this->moduleTemplate->getPageRenderer()->addInlineLanguageLabelArray([
|
||||
'file_rename.actions.cancel' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:file_rename.actions.cancel'),
|
||||
'file_rename.actions.rename' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:file_rename.actions.rename'),
|
||||
'file_rename.actions.override' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:file_rename.actions.override'),
|
||||
'file_rename.exists.title' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:file_rename.exists.title'),
|
||||
'file_rename.exists.description' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:file_rename.exists.description'),
|
||||
]);
|
||||
|
||||
// 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/File/RenameFile.html'
|
||||
));
|
||||
$view->assignMultiple($assigns);
|
||||
$this->content = $view->render();
|
||||
$this->moduleTemplate->setContent($this->content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DocumentTemplate
|
||||
*/
|
||||
protected function getDocumentTemplate(): DocumentTemplate
|
||||
{
|
||||
return $GLOBALS['TBE_TEMPLATE'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller\File;
|
||||
|
||||
/*
|
||||
* 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\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplate;
|
||||
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\Resource\Exception\InsufficientFileAccessPermissionsException;
|
||||
use TYPO3\CMS\Core\Resource\Folder;
|
||||
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
|
||||
/**
|
||||
* Script Class for the replace-file form
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class ReplaceFileController
|
||||
{
|
||||
use PublicPropertyDeprecationTrait;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $deprecatedPublicProperties = [
|
||||
'doc' => 'Using $doc of class ReplaceFileController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'title' => 'Using $title of class ReplaceFileController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'uid' => 'Using $uid of class ReplaceFileController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'returnUrl' => 'Using $returnUrl of class ReplaceFileController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'content' => 'Using $content of class ReplaceFileController from outside is discouraged as this variable is only used for internal storage.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Document template object
|
||||
*
|
||||
* @var \TYPO3\CMS\Backend\Template\DocumentTemplate
|
||||
*/
|
||||
protected $doc;
|
||||
|
||||
/**
|
||||
* Name of the filemount
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $title;
|
||||
|
||||
/**
|
||||
* sys_file uid
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $uid;
|
||||
|
||||
/**
|
||||
* The file or folder object that should be renamed
|
||||
*
|
||||
* @var \TYPO3\CMS\Core\Resource\ResourceInterface $fileOrFolderObject
|
||||
*/
|
||||
protected $fileOrFolderObject;
|
||||
|
||||
/**
|
||||
* Return URL of list module.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $returnUrl;
|
||||
|
||||
/**
|
||||
* Accumulating content
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $content;
|
||||
|
||||
/**
|
||||
* ModuleTemplate object
|
||||
*
|
||||
* @var ModuleTemplate
|
||||
*/
|
||||
protected $moduleTemplate;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
|
||||
|
||||
// @deprecated since TYPO3 v9, will be moved out of __construct() in TYPO3 v10.0
|
||||
$this->init($GLOBALS['TYPO3_REQUEST']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the request, currently everything is handled and put together via "main()"
|
||||
*
|
||||
* @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->moduleTemplate->renderContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function, rendering the content of the replace form
|
||||
*/
|
||||
public function main()
|
||||
{
|
||||
trigger_error('ReplaceFileController->main() will be replaced by protected method renderContent() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
|
||||
$this->renderContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @throws \RuntimeException
|
||||
* @throws InsufficientFileAccessPermissionsException
|
||||
*/
|
||||
protected function init(ServerRequestInterface $request): void
|
||||
{
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$queryParams = $request->getQueryParams();
|
||||
$lang = $this->getLanguageService();
|
||||
|
||||
// Initialize GPvars:
|
||||
$this->uid = (int)($parsedBody['uid'] ?? $queryParams['uid'] ?? 0);
|
||||
$this->returnUrl = GeneralUtility::sanitizeLocalUrl($parsedBody['returnUrl'] ?? $queryParams['returnUrl'] ?? '');
|
||||
|
||||
// Cleaning and checking uid
|
||||
if ($this->uid > 0) {
|
||||
$this->fileOrFolderObject = ResourceFactory::getInstance()
|
||||
->retrieveFileOrFolderObject('file:' . $this->uid);
|
||||
}
|
||||
if (!$this->fileOrFolderObject) {
|
||||
$title = $lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:paramError');
|
||||
$message = $lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:targetNoDir');
|
||||
throw new \RuntimeException($title . ': ' . $message, 1436895930);
|
||||
}
|
||||
if ($this->fileOrFolderObject->getStorage()->getUid() === 0) {
|
||||
throw new InsufficientFileAccessPermissionsException(
|
||||
'You are not allowed to access files outside your storages',
|
||||
1436895931
|
||||
);
|
||||
}
|
||||
|
||||
// If a folder should be renamed, AND the returnURL should go to the old directory name, the redirect is forced
|
||||
// so the redirect will NOT end in an error message
|
||||
// this case only happens if you select the folder itself in the foldertree and then use the clickmenu to
|
||||
// rename the folder
|
||||
if ($this->fileOrFolderObject instanceof Folder) {
|
||||
$parsedUrl = parse_url($this->returnUrl);
|
||||
$queryParts = GeneralUtility::explodeUrl2Array(urldecode($parsedUrl['query']));
|
||||
if ($queryParts['id'] === $this->fileOrFolderObject->getCombinedIdentifier()) {
|
||||
$this->returnUrl = str_replace(
|
||||
urlencode($queryParts['id']),
|
||||
urlencode($this->fileOrFolderObject->getStorage()->getRootLevelFolder()->getCombinedIdentifier()),
|
||||
$this->returnUrl
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$pathInfo = [
|
||||
'combined_identifier' => $this->fileOrFolderObject->getCombinedIdentifier(),
|
||||
];
|
||||
$this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($pathInfo);
|
||||
$this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
|
||||
$this->moduleTemplate->addJavaScriptCode(
|
||||
'ReplaceFileOnlineJavaScript',
|
||||
'function backToList() {top.goToModule("file_FilelistList");}'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render module content
|
||||
*/
|
||||
protected function renderContent(): void
|
||||
{
|
||||
// Assign variables used by the fluid template
|
||||
$assigns = [];
|
||||
/** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
$assigns['moduleUrlTceFile'] = (string)$uriBuilder->buildUriFromRoute('tce_file');
|
||||
$assigns['uid'] = $this->uid;
|
||||
$assigns['returnUrl'] = $this->returnUrl;
|
||||
|
||||
$buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
|
||||
// csh button
|
||||
$cshButton = $buttonBar->makeHelpButton()
|
||||
->setModuleName('xMOD_csh_corebe')
|
||||
->setFieldName('file_rename');
|
||||
$buttonBar->addButton($cshButton);
|
||||
|
||||
// Back button
|
||||
if ($this->returnUrl) {
|
||||
$returnButton = $buttonBar->makeLinkButton()
|
||||
->setHref($this->returnUrl)
|
||||
->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.goBack'))
|
||||
->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-view-go-back', Icon::SIZE_SMALL));
|
||||
$buttonBar->addButton($returnButton);
|
||||
}
|
||||
|
||||
// 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/File/ReplaceFile.html'
|
||||
));
|
||||
$view->assignMultiple($assigns);
|
||||
$this->content = $view->render();
|
||||
|
||||
$this->moduleTemplate->setContent($this->content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller\File;
|
||||
|
||||
/*
|
||||
* 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\Core\Http\Response;
|
||||
use TYPO3\CMS\Core\Resource\ProcessedFile;
|
||||
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class ThumbnailController
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultConfiguration = [
|
||||
'width' => 64,
|
||||
'height' => 64,
|
||||
'crop' => null,
|
||||
];
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function render(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
try {
|
||||
$parameters = $this->extractParameters($request->getQueryParams());
|
||||
$response = $this->generateThumbnail(
|
||||
$parameters['fileId'] ?? null,
|
||||
$parameters['configuration'] ?? []
|
||||
);
|
||||
} catch (\TYPO3\CMS\Core\Resource\Exception $exception) {
|
||||
// catch and handle only resource related exceptions
|
||||
$response = $this->generateNotFoundResponse();
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $queryParameters
|
||||
* @return array|null
|
||||
*/
|
||||
protected function extractParameters(array $queryParameters)
|
||||
{
|
||||
$expectedHash = GeneralUtility::hmac(
|
||||
$queryParameters['parameters'] ?? '',
|
||||
ThumbnailController::class
|
||||
);
|
||||
if (!hash_equals($expectedHash, $queryParameters['hmac'] ?? '')) {
|
||||
throw new \InvalidArgumentException(
|
||||
'HMAC could not be verified',
|
||||
1534484203
|
||||
);
|
||||
}
|
||||
|
||||
return json_decode($queryParameters['parameters'] ?? null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed|int $fileId
|
||||
* @param array $configuration
|
||||
* @return Response
|
||||
* @throws \TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException
|
||||
*/
|
||||
protected function generateThumbnail($fileId, array $configuration): ResponseInterface
|
||||
{
|
||||
$file = ResourceFactory::getInstance()->getFileObject($fileId);
|
||||
if (empty($file) || $file->isMissing()) {
|
||||
return $this->generateNotFoundResponse();
|
||||
}
|
||||
|
||||
$processingConfiguration = $this->defaultConfiguration;
|
||||
ArrayUtility::mergeRecursiveWithOverrule(
|
||||
$processingConfiguration,
|
||||
$configuration
|
||||
);
|
||||
|
||||
$processedImage = $file->process(
|
||||
ProcessedFile::CONTEXT_IMAGECROPSCALEMASK,
|
||||
$processingConfiguration
|
||||
);
|
||||
$filePath = $processedImage->getForLocalProcessing(false);
|
||||
return new Response($filePath, 200, [
|
||||
'Content-Type' => $processedImage->getMimeType()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function generateNotFoundResponse(): ResponseInterface
|
||||
{
|
||||
return new Response('', 404);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller;
|
||||
|
||||
/*
|
||||
* 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\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplate;
|
||||
use TYPO3\CMS\Backend\Tree\View\ElementBrowserFolderTreeView;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Compatibility\PublicPropertyDeprecationTrait;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
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\Filelist\FileListFolderTree;
|
||||
use TYPO3\CMS\Recordlist\Tree\View\DummyLinkParameterProvider;
|
||||
|
||||
/**
|
||||
* Main script class for rendering of the folder tree
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class FileSystemNavigationFrameController
|
||||
{
|
||||
use PublicPropertyDeprecationTrait;
|
||||
|
||||
/**
|
||||
* Properties which have been moved to protected status from public
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $deprecatedPublicProperties = [
|
||||
'content' => 'Using $content of class FileSystemNavigationFrameController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'foldertree' => 'Using $foldertree of class FileSystemNavigationFrameController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'currentSubScript' => 'Using $currentSubScript of class FileSystemNavigationFrameController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'cMR' => 'Using $cMR of class FileSystemNavigationFrameController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Content accumulates in this variable.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $content;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Backend\Tree\View\FolderTreeView
|
||||
*/
|
||||
protected $foldertree;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $currentSubScript;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $cMR;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $scopeData;
|
||||
|
||||
/**
|
||||
* ModuleTemplate Container
|
||||
*
|
||||
* @var ModuleTemplate
|
||||
*/
|
||||
protected $moduleTemplate;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// @deprecated since TYPO3 v9, will be obsolete in TYPO3 v10.0 with removal of init()
|
||||
$request = $GLOBALS['TYPO3_REQUEST'];
|
||||
$GLOBALS['SOBE'] = $this;
|
||||
// @deprecated since TYPO3 v9, will be moved out of __construct() in TYPO3 v10.0
|
||||
$this->init($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request the current request
|
||||
* @return ResponseInterface the response with the content
|
||||
*/
|
||||
public function mainAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->initializePageTemplate();
|
||||
$this->renderFolderTree($request);
|
||||
return new HtmlResponse($this->content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the AJAX call to expand or collapse the foldertree.
|
||||
* Called by an AJAX Route, see AjaxRequestHandler
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function ajaxExpandCollapse(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$tree = $this->foldertree->getBrowsableTree();
|
||||
if ($this->foldertree->getAjaxStatus() === false) {
|
||||
return new JsonResponse(null, 500);
|
||||
}
|
||||
return new JsonResponse([$tree]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization of the script class
|
||||
*
|
||||
* @param ServerRequestInterface $request the current request
|
||||
*/
|
||||
protected function init(ServerRequestInterface $request = null)
|
||||
{
|
||||
if ($request === null) {
|
||||
// Method signature in TYPO3 v10.0: protected function init(ServerRequestInterface $request)
|
||||
trigger_error('FileSystemNavigationFrameController->init() will be set to protected in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
|
||||
$request = $GLOBALS['TYPO3_REQUEST'];
|
||||
}
|
||||
|
||||
$this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
|
||||
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$queryParams = $request->getQueryParams();
|
||||
|
||||
$this->currentSubScript = $parsedBody['currentSubScript'] ?? $queryParams['currentSubScript'] ?? null;
|
||||
$this->cMR = (bool)($parsedBody['cMR'] ?? $queryParams['cMR'] ?? false);
|
||||
$scopeData = $parsedBody['scopeData'] ?? $queryParams['scopeData'] ?? '';
|
||||
$scopeHash = $parsedBody['scopeHash'] ?? $queryParams['scopeHash'] ?? '';
|
||||
|
||||
if (!empty($scopeData) && hash_equals(GeneralUtility::hmac($scopeData), $scopeHash)) {
|
||||
$this->scopeData = json_decode($scopeData, true);
|
||||
}
|
||||
|
||||
// Create folder tree object:
|
||||
if (!empty($this->scopeData)) {
|
||||
$this->foldertree = GeneralUtility::makeInstance($this->scopeData['class']);
|
||||
$this->foldertree->thisScript = $this->scopeData['script'];
|
||||
if ($this->foldertree instanceof ElementBrowserFolderTreeView) {
|
||||
// create a fake provider to pass link data along properly
|
||||
$linkParamProvider = GeneralUtility::makeInstance(
|
||||
DummyLinkParameterProvider::class,
|
||||
$this->scopeData['browser'],
|
||||
$this->scopeData['script']
|
||||
);
|
||||
$this->foldertree->setLinkParameterProvider($linkParamProvider);
|
||||
}
|
||||
} else {
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
$this->foldertree = GeneralUtility::makeInstance(FileListFolderTree::class);
|
||||
$this->foldertree->thisScript = (string)$uriBuilder->buildUriFromRoute('file_navframe');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* initialization for the visual parts of the class
|
||||
* Use template rendering only if this is a non-AJAX call
|
||||
*
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function initPage()
|
||||
{
|
||||
trigger_error('FileSystemNavigationFrameController->initPage() will be replaced by protected method initializePageTemplate() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
|
||||
$this->initializePageTemplate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization for the visual parts of the class
|
||||
* Use template rendering only if this is a non-AJAX call
|
||||
*/
|
||||
protected function initializePageTemplate(): void
|
||||
{
|
||||
$this->moduleTemplate->setBodyTag('<body id="ext-backend-Modules-FileSystemNavigationFrame-index-php">');
|
||||
|
||||
// Adding javascript code for drag&drop and the file tree as well as the click menu code
|
||||
$hlClass = $this->getBackendUser()->workspace === 0 ? 'active' : 'active active-ws wsver' . $GLOBALS['BE_USER']->workspace;
|
||||
$dragDropCode = '
|
||||
Tree.highlightClass = "' . $hlClass . '";
|
||||
Tree.highlightActiveItem("", top.fsMod.navFrameHighlightedID["file"]);
|
||||
';
|
||||
|
||||
// Adding javascript for drag & drop activation and highlighting
|
||||
$pageRenderer = $this->moduleTemplate->getPageRenderer();
|
||||
$pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
|
||||
$pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/LegacyTree', 'function() {
|
||||
DragDrop.table = "folders";
|
||||
Tree.registerDragDropHandlers();
|
||||
' . $dragDropCode . '
|
||||
}');
|
||||
|
||||
// Setting JavaScript for menu.
|
||||
$inlineJs = ($this->currentSubScript ? 'top.currentSubScript=unescape("' . rawurlencode($this->currentSubScript) . '");' : '') . '
|
||||
// Function, loading the list frame from navigation tree:
|
||||
function jumpTo(id, linkObj, highlightID, bank) {
|
||||
var theUrl = top.currentSubScript;
|
||||
if (theUrl.indexOf("?") != -1) {
|
||||
theUrl += "&id=" + id
|
||||
} else {
|
||||
theUrl += "?id=" + id
|
||||
}
|
||||
top.fsMod.currentBank = bank;
|
||||
top.TYPO3.Backend.ContentContainer.setUrl(theUrl);
|
||||
|
||||
Tree.highlightActiveItem("file", highlightID + "_" + bank);
|
||||
if (linkObj) { linkObj.blur(); }
|
||||
return false;
|
||||
}
|
||||
' . ($this->cMR ? ' jumpTo(top.fsMod.recentIds[\'file\'],\'\');' : '');
|
||||
|
||||
$this->moduleTemplate->getPageRenderer()->addJsInlineCode(
|
||||
'FileSystemNavigationFrame',
|
||||
$inlineJs
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function, rendering the folder tree
|
||||
*
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function main()
|
||||
{
|
||||
trigger_error('FileSystemNavigationFrameController->main() will be replaced by protected method renderFolderTree() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
|
||||
$this->renderFolderTree($GLOBALS['TYPO3_REQUEST']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function, rendering the folder tree
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
*/
|
||||
protected function renderFolderTree(ServerRequestInterface $request): void
|
||||
{
|
||||
// Produce browse-tree:
|
||||
$tree = $this->foldertree->getBrowsableTree();
|
||||
// Outputting page tree:
|
||||
$this->moduleTemplate->setContent($tree);
|
||||
// Setting up the buttons
|
||||
$this->getButtons($request);
|
||||
// Build the <body> for the module
|
||||
$this->moduleTemplate->setTitle('TYPO3 Folder Tree');
|
||||
$this->content = $this->moduleTemplate->renderContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register docHeader buttons
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
*/
|
||||
protected function getButtons(ServerRequestInterface $request): void
|
||||
{
|
||||
/** @var ButtonBar $buttonBar */
|
||||
$buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
|
||||
/** @var IconFactory $iconFactory */
|
||||
$iconFactory = $this->moduleTemplate->getIconFactory();
|
||||
/** @var \TYPO3\CMS\Core\Http\NormalizedParams */
|
||||
$normalizedParams = $request->getAttribute('normalizedParams');
|
||||
|
||||
// Refresh
|
||||
$refreshButton = $buttonBar->makeLinkButton()
|
||||
->setHref($normalizedParams->getRequestUri())
|
||||
->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.reload'))
|
||||
->setIcon($iconFactory->getIcon('actions-refresh', Icon::SIZE_SMALL));
|
||||
$buttonBar->addButton($refreshButton, ButtonBar::BUTTON_POSITION_RIGHT);
|
||||
|
||||
// CSH
|
||||
$cshButton = $buttonBar->makeHelpButton()
|
||||
->setModuleName('xMOD_csh_corebe')
|
||||
->setFieldName('filetree');
|
||||
$buttonBar->addButton($cshButton);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of LanguageService
|
||||
*
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller;
|
||||
|
||||
/*
|
||||
* 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 TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Render FlashMessages
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class FlashMessageController
|
||||
{
|
||||
/**
|
||||
* Renders the FlashMessages from queue and returns them as JSON.
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function getQueuedFlashMessagesAction(): ResponseInterface
|
||||
{
|
||||
$flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
|
||||
$defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
|
||||
$flashMessages = $defaultFlashMessageQueue->getAllMessagesAndFlush();
|
||||
|
||||
$messages = [];
|
||||
foreach ($flashMessages as $flashMessage) {
|
||||
$messages[] = [
|
||||
'title' => $flashMessage->getTitle(),
|
||||
'message' => $flashMessage->getMessage(),
|
||||
'severity' => $flashMessage->getSeverity()
|
||||
];
|
||||
}
|
||||
|
||||
return (new JsonResponse())->setPayload($messages);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller;
|
||||
|
||||
/*
|
||||
* 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\Form\FormDataCompiler;
|
||||
use TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord;
|
||||
use TYPO3\CMS\Backend\Form\NodeFactory;
|
||||
use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
|
||||
/**
|
||||
* Handle FormEngine flex field ajax calls
|
||||
*/
|
||||
class FormFlexAjaxController extends AbstractFormEngineAjaxController
|
||||
{
|
||||
/**
|
||||
* Render a single flex form section container to add it to the DOM
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function containerAdd(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$queryParameters = $request->getParsedBody();
|
||||
|
||||
$vanillaUid = (int)$queryParameters['vanillaUid'];
|
||||
$databaseRowUid = $queryParameters['databaseRowUid'];
|
||||
$command = $queryParameters['command'];
|
||||
$tableName = $queryParameters['tableName'];
|
||||
$fieldName = $queryParameters['fieldName'];
|
||||
$recordTypeValue = $queryParameters['recordTypeValue'];
|
||||
$dataStructureIdentifier = json_encode($queryParameters['dataStructureIdentifier']);
|
||||
$flexFormSheetName = $queryParameters['flexFormSheetName'];
|
||||
$flexFormFieldName = $queryParameters['flexFormFieldName'];
|
||||
$flexFormContainerName = $queryParameters['flexFormContainerName'];
|
||||
|
||||
// Prepare TCA and data values for a new section container using data providers
|
||||
$processedTca = $GLOBALS['TCA'][$tableName];
|
||||
$flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
|
||||
$dataStructure = $flexFormTools->parseDataStructureByIdentifier($dataStructureIdentifier);
|
||||
$processedTca['columns'][$fieldName]['config']['ds'] = $dataStructure;
|
||||
$processedTca['columns'][$fieldName]['config']['dataStructureIdentifier'] = $dataStructureIdentifier;
|
||||
// Get a new unique id for this container.
|
||||
$flexFormContainerIdentifier = StringUtility::getUniqueId();
|
||||
$flexSectionContainerPreparation = [
|
||||
'flexFormSheetName' => $flexFormSheetName,
|
||||
'flexFormFieldName' => $flexFormFieldName,
|
||||
'flexFormContainerName' => $flexFormContainerName,
|
||||
'flexFormContainerIdentifier' => $flexFormContainerIdentifier,
|
||||
];
|
||||
|
||||
$formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
|
||||
$formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
|
||||
$formDataCompilerInput = [
|
||||
'tableName' => $tableName,
|
||||
'vanillaUid' => (int)$vanillaUid,
|
||||
'command' => $command,
|
||||
'recordTypeValue' => $recordTypeValue,
|
||||
'processedTca' => $processedTca,
|
||||
'flexSectionContainerPreparation' => $flexSectionContainerPreparation,
|
||||
];
|
||||
// A new container on a new record needs the 'NEW123' uid here, see comment
|
||||
// in DatabaseUniqueUidNewRow for more information on that.
|
||||
// @todo: Resolve, maybe with a redifinition of vanillaUid to transport the information more clean through this var?
|
||||
// @see issue #80100 for a series of changes in this area
|
||||
if ($command === 'new') {
|
||||
$formDataCompilerInput['databaseRow']['uid'] = $databaseRowUid;
|
||||
}
|
||||
$formData = $formDataCompiler->compile($formDataCompilerInput);
|
||||
|
||||
$dataStructure = $formData['processedTca']['columns'][$fieldName]['config']['ds'];
|
||||
$formData['fieldName'] = $fieldName;
|
||||
$formData['flexFormDataStructureArray'] = $dataStructure['sheets'][$flexFormSheetName]['ROOT']['el'][$flexFormFieldName]['children'][$flexFormContainerIdentifier];
|
||||
$formData['flexFormDataStructureIdentifier'] = $dataStructureIdentifier;
|
||||
$formData['flexFormFieldName'] = $flexFormFieldName;
|
||||
$formData['flexFormSheetName'] = $flexFormSheetName;
|
||||
$formData['flexFormContainerName'] = $flexFormContainerName;
|
||||
$formData['flexFormContainerIdentifier'] = $flexFormContainerIdentifier;
|
||||
$formData['flexFormContainerElementCollapsed'] = false;
|
||||
|
||||
$formData['flexFormFormPrefix'] = '[data][' . $flexFormSheetName . '][lDEF]' . '[' . $flexFormFieldName . ']' . '[el]';
|
||||
|
||||
// Set initialized data of that section container from compiler to the array part used
|
||||
// by flexFormElementContainer which prepares parameterArray. Important for initialized
|
||||
// values of group element.
|
||||
if (isset($formData['databaseRow'][$fieldName]
|
||||
['data'][$flexFormSheetName]
|
||||
['lDEF'][$flexFormFieldName]
|
||||
['el'][$flexFormContainerIdentifier][$flexFormContainerName]['el']
|
||||
)
|
||||
&& is_array(
|
||||
$formData['databaseRow'][$fieldName]
|
||||
['data'][$flexFormSheetName]
|
||||
['lDEF'][$flexFormFieldName]
|
||||
['el'][$flexFormContainerIdentifier][$flexFormContainerName]['el']
|
||||
)
|
||||
) {
|
||||
$formData['flexFormRowData'] = $formData['databaseRow'][$fieldName]
|
||||
['data'][$flexFormSheetName]
|
||||
['lDEF'][$flexFormFieldName]
|
||||
['el'][$flexFormContainerIdentifier][$flexFormContainerName]['el'];
|
||||
}
|
||||
|
||||
$formData['parameterArray']['itemFormElName'] = 'data[' . $tableName . '][' . $formData['databaseRow']['uid'] . '][' . $fieldName . ']';
|
||||
|
||||
// JavaScript code for event handlers:
|
||||
// @todo: see if we can get rid of this - used in group elements, and also for the "reload" on type field changes
|
||||
$formData['parameterArray']['fieldChangeFunc'] = [];
|
||||
$formData['parameterArray']['fieldChangeFunc']['TBE_EDITOR_fieldChanged'] = 'TBE_EDITOR.fieldChanged('
|
||||
. GeneralUtility::quoteJSvalue($tableName)
|
||||
. ',' . GeneralUtility::quoteJSvalue($formData['databaseRow']['uid'])
|
||||
. ',' . GeneralUtility::quoteJSvalue($fieldName)
|
||||
. ',' . GeneralUtility::quoteJSvalue($formData['parameterArray']['itemFormElName'])
|
||||
. ');';
|
||||
|
||||
// @todo: check GroupElement for usage of elementBaseName ... maybe kick that thing?
|
||||
|
||||
// Feed resulting form data to container structure to render HTML and other result data
|
||||
$nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
|
||||
$formData['renderType'] = 'flexFormContainerContainer';
|
||||
$newContainerResult = $nodeFactory->create($formData)->render();
|
||||
|
||||
$jsonResult = [
|
||||
'html' => $newContainerResult['html'],
|
||||
'stylesheetFiles' => [],
|
||||
'scriptCall' => [],
|
||||
];
|
||||
|
||||
if (!empty($newContainerResult['additionalJavaScriptSubmit'])) {
|
||||
$additionalJavaScriptSubmit = implode('', $newContainerResult['additionalJavaScriptSubmit']);
|
||||
$additionalJavaScriptSubmit = str_replace([CR, LF], '', $additionalJavaScriptSubmit);
|
||||
$jsonResult['scriptCall'][] = 'TBE_EDITOR.addActionChecks("submit", "' . addslashes($additionalJavaScriptSubmit) . '");';
|
||||
}
|
||||
foreach ($newContainerResult['additionalJavaScriptPost'] as $singleAdditionalJavaScriptPost) {
|
||||
$jsonResult['scriptCall'][] = $singleAdditionalJavaScriptPost;
|
||||
}
|
||||
foreach ($newContainerResult['stylesheetFiles'] as $stylesheetFile) {
|
||||
$jsonResult['stylesheetFiles'][] = $this->getRelativePathToStylesheetFile($stylesheetFile);
|
||||
}
|
||||
if (!empty($newContainerResult['additionalInlineLanguageLabelFiles'])) {
|
||||
$labels = [];
|
||||
foreach ($newContainerResult['additionalInlineLanguageLabelFiles'] as $additionalInlineLanguageLabelFile) {
|
||||
ArrayUtility::mergeRecursiveWithOverrule(
|
||||
$labels,
|
||||
$this->getLabelsFromLocalizationFile($additionalInlineLanguageLabelFile)
|
||||
);
|
||||
}
|
||||
$javaScriptCode = [];
|
||||
$javaScriptCode[] = 'if (typeof TYPO3 === \'undefined\' || typeof TYPO3.lang === \'undefined\') {';
|
||||
$javaScriptCode[] = ' TYPO3.lang = {}';
|
||||
$javaScriptCode[] = '}';
|
||||
$javaScriptCode[] = 'var additionalInlineLanguageLabels = ' . json_encode($labels) . ';';
|
||||
$javaScriptCode[] = 'for (var attributeName in additionalInlineLanguageLabels) {';
|
||||
$javaScriptCode[] = ' if (typeof TYPO3.lang[attributeName] === \'undefined\') {';
|
||||
$javaScriptCode[] = ' TYPO3.lang[attributeName] = additionalInlineLanguageLabels[attributeName]';
|
||||
$javaScriptCode[] = ' }';
|
||||
$javaScriptCode[] = '}';
|
||||
|
||||
$jsonResult['scriptCall'][] = implode(LF, $javaScriptCode);
|
||||
}
|
||||
|
||||
$requireJsModule = $this->createExecutableStringRepresentationOfRegisteredRequireJsModules($newContainerResult);
|
||||
$jsonResult['scriptCall'] = array_merge($requireJsModule, $jsonResult['scriptCall']);
|
||||
|
||||
return new JsonResponse($jsonResult);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,766 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller;
|
||||
|
||||
/*
|
||||
* 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\Form\FormDataCompiler;
|
||||
use TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord;
|
||||
use TYPO3\CMS\Backend\Form\InlineStackProcessor;
|
||||
use TYPO3\CMS\Backend\Form\NodeFactory;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\DataHandling\DataHandler;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Messaging\AbstractMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageService;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* Handle FormEngine inline ajax calls
|
||||
*/
|
||||
class FormInlineAjaxController extends AbstractFormEngineAjaxController
|
||||
{
|
||||
/**
|
||||
* Create a new inline child via AJAX.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function createAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$ajaxArguments = $request->getParsedBody()['ajax'] ?? $request->getQueryParams()['ajax'];
|
||||
$parentConfig = $this->extractSignedParentConfigFromRequest((string)$ajaxArguments['context']);
|
||||
|
||||
$domObjectId = $ajaxArguments[0];
|
||||
$inlineFirstPid = $this->getInlineFirstPidFromDomObjectId($domObjectId);
|
||||
if (!MathUtility::canBeInterpretedAsInteger($inlineFirstPid) && strpos($inlineFirstPid, 'NEW') !== 0) {
|
||||
throw new \RuntimeException(
|
||||
'inlineFirstPid should either be an integer or a "NEW..." string',
|
||||
1521220491
|
||||
);
|
||||
}
|
||||
$childChildUid = null;
|
||||
if (isset($ajaxArguments[1]) && MathUtility::canBeInterpretedAsInteger($ajaxArguments[1])) {
|
||||
$childChildUid = (int)$ajaxArguments[1];
|
||||
}
|
||||
|
||||
// Parse the DOM identifier, add the levels to the structure stack
|
||||
/** @var InlineStackProcessor $inlineStackProcessor */
|
||||
$inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
|
||||
$inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
|
||||
$inlineStackProcessor->injectAjaxConfiguration($parentConfig);
|
||||
$inlineTopMostParent = $inlineStackProcessor->getStructureLevel(0);
|
||||
|
||||
// Parent, this table embeds the child table
|
||||
$parent = $inlineStackProcessor->getStructureLevel(-1);
|
||||
|
||||
// Child, a record from this table should be rendered
|
||||
$child = $inlineStackProcessor->getUnstableStructure();
|
||||
if (MathUtility::canBeInterpretedAsInteger($child['uid'])) {
|
||||
// If uid comes in, it is the id of the record neighbor record "create after"
|
||||
$childVanillaUid = -1 * abs((int)$child['uid']);
|
||||
} else {
|
||||
// Else inline first Pid is the storage pid of new inline records
|
||||
$childVanillaUid = $inlineFirstPid;
|
||||
}
|
||||
|
||||
$childTableName = $parentConfig['foreign_table'];
|
||||
|
||||
/** @var TcaDatabaseRecord $formDataGroup */
|
||||
$formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
|
||||
/** @var FormDataCompiler $formDataCompiler */
|
||||
$formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
|
||||
$formDataCompilerInput = [
|
||||
'command' => 'new',
|
||||
'tableName' => $childTableName,
|
||||
'vanillaUid' => $childVanillaUid,
|
||||
'isInlineChild' => true,
|
||||
'inlineStructure' => $inlineStackProcessor->getStructure(),
|
||||
'inlineFirstPid' => $inlineFirstPid,
|
||||
'inlineParentUid' => $parent['uid'],
|
||||
'inlineParentTableName' => $parent['table'],
|
||||
'inlineParentFieldName' => $parent['field'],
|
||||
'inlineParentConfig' => $parentConfig,
|
||||
'inlineTopMostParentUid' => $inlineTopMostParent['uid'],
|
||||
'inlineTopMostParentTableName' => $inlineTopMostParent['table'],
|
||||
'inlineTopMostParentFieldName' => $inlineTopMostParent['field'],
|
||||
];
|
||||
if ($childChildUid) {
|
||||
$formDataCompilerInput['inlineChildChildUid'] = $childChildUid;
|
||||
}
|
||||
$childData = $formDataCompiler->compile($formDataCompilerInput);
|
||||
|
||||
if ($parentConfig['foreign_selector'] && $parentConfig['appearance']['useCombination']) {
|
||||
// We have a foreign_selector. So, we just created a new record on an intermediate table in $childData.
|
||||
// Now, if a valid id is given as second ajax parameter, the intermediate row should be connected to an
|
||||
// existing record of the child-child table specified by the given uid. If there is no such id, user
|
||||
// clicked on "created new" and a new child-child should be created, too.
|
||||
if ($childChildUid) {
|
||||
// Fetch existing child child
|
||||
$childData['databaseRow'][$parentConfig['foreign_selector']] = [
|
||||
$childChildUid,
|
||||
];
|
||||
$childData['combinationChild'] = $this->compileChildChild($childData, $parentConfig, $inlineStackProcessor->getStructure());
|
||||
} else {
|
||||
/** @var TcaDatabaseRecord $formDataGroup */
|
||||
$formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
|
||||
/** @var FormDataCompiler $formDataCompiler */
|
||||
$formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
|
||||
$formDataCompilerInput = [
|
||||
'command' => 'new',
|
||||
'tableName' => $childData['processedTca']['columns'][$parentConfig['foreign_selector']]['config']['foreign_table'],
|
||||
'vanillaUid' => $inlineFirstPid,
|
||||
'isInlineChild' => true,
|
||||
'isInlineAjaxOpeningContext' => true,
|
||||
'inlineStructure' => $inlineStackProcessor->getStructure(),
|
||||
'inlineFirstPid' => $inlineFirstPid,
|
||||
];
|
||||
$childData['combinationChild'] = $formDataCompiler->compile($formDataCompilerInput);
|
||||
}
|
||||
}
|
||||
|
||||
$childData['inlineParentUid'] = $parent['uid'];
|
||||
$childData['renderType'] = 'inlineRecordContainer';
|
||||
$nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
|
||||
$childResult = $nodeFactory->create($childData)->render();
|
||||
|
||||
$jsonArray = [
|
||||
'data' => '',
|
||||
'stylesheetFiles' => [],
|
||||
'scriptCall' => [],
|
||||
];
|
||||
|
||||
// The HTML-object-id's prefix of the dynamically created record
|
||||
$objectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
|
||||
$objectPrefix = $objectName . '-' . $child['table'];
|
||||
$objectId = $objectPrefix . '-' . $childData['databaseRow']['uid'];
|
||||
$expandSingle = $parentConfig['appearance']['expandSingle'];
|
||||
if (!$child['uid']) {
|
||||
$jsonArray['scriptCall'][] = 'inline.domAddNewRecord(\'bottom\',' . GeneralUtility::quoteJSvalue($objectName . '_records') . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',json.data);';
|
||||
$jsonArray['scriptCall'][] = 'inline.memorizeAddRecord(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($childData['databaseRow']['uid']) . ',null,' . GeneralUtility::quoteJSvalue($childChildUid) . ');';
|
||||
} else {
|
||||
$jsonArray['scriptCall'][] = 'inline.domAddNewRecord(\'after\',' . GeneralUtility::quoteJSvalue($domObjectId . '_div') . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',json.data);';
|
||||
$jsonArray['scriptCall'][] = 'inline.memorizeAddRecord(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($childData['databaseRow']['uid']) . ',' . GeneralUtility::quoteJSvalue($child['uid']) . ',' . GeneralUtility::quoteJSvalue($childChildUid) . ');';
|
||||
}
|
||||
$jsonArray = $this->mergeChildResultIntoJsonResult($jsonArray, $childResult);
|
||||
if ($parentConfig['appearance']['useSortable']) {
|
||||
$inlineObjectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
|
||||
$jsonArray['scriptCall'][] = 'inline.createDragAndDropSorting(' . GeneralUtility::quoteJSvalue($inlineObjectName . '_records') . ');';
|
||||
}
|
||||
if (!$parentConfig['appearance']['collapseAll'] && $expandSingle) {
|
||||
$jsonArray['scriptCall'][] = 'inline.collapseAllRecords(' . GeneralUtility::quoteJSvalue($objectId) . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($childData['databaseRow']['uid']) . ');';
|
||||
}
|
||||
// Fade out and fade in the new record in the browser view to catch the user's eye
|
||||
$jsonArray['scriptCall'][] = 'inline.fadeOutFadeIn(' . GeneralUtility::quoteJSvalue($objectId . '_div') . ');';
|
||||
|
||||
return new JsonResponse($jsonArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the details of a child record.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function detailsAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$ajaxArguments = $request->getParsedBody()['ajax'] ?? $request->getQueryParams()['ajax'];
|
||||
|
||||
$domObjectId = $ajaxArguments[0] ?? null;
|
||||
$inlineFirstPid = $this->getInlineFirstPidFromDomObjectId($domObjectId);
|
||||
$parentConfig = $this->extractSignedParentConfigFromRequest((string)$ajaxArguments['context']);
|
||||
|
||||
// Parse the DOM identifier, add the levels to the structure stack
|
||||
/** @var InlineStackProcessor $inlineStackProcessor */
|
||||
$inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
|
||||
$inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
|
||||
$inlineStackProcessor->injectAjaxConfiguration($parentConfig);
|
||||
|
||||
// Parent, this table embeds the child table
|
||||
$parent = $inlineStackProcessor->getStructureLevel(-1);
|
||||
$parentFieldName = $parent['field'];
|
||||
|
||||
// Set flag in config so that only the fields are rendered
|
||||
// @todo: Solve differently / rename / whatever
|
||||
$parentConfig['renderFieldsOnly'] = true;
|
||||
|
||||
$parentData = [
|
||||
'processedTca' => [
|
||||
'columns' => [
|
||||
$parentFieldName => [
|
||||
'config' => $parentConfig,
|
||||
],
|
||||
],
|
||||
],
|
||||
'uid' => $parent['uid'],
|
||||
'tableName' => $parent['table'],
|
||||
'inlineFirstPid' => $inlineFirstPid,
|
||||
// Hand over given original return url to compile stack. Needed if inline children compile links to
|
||||
// another view (eg. edit metadata in a nested inline situation like news with inline content element image),
|
||||
// so the back link is still the link from the original request. See issue #82525. This is additionally
|
||||
// given down in TcaInline data provider to compiled children data.
|
||||
'returnUrl' => $parentConfig['originalReturnUrl'],
|
||||
];
|
||||
|
||||
// Child, a record from this table should be rendered
|
||||
$child = $inlineStackProcessor->getUnstableStructure();
|
||||
|
||||
$childData = $this->compileChild($parentData, $parentFieldName, (int)$child['uid'], $inlineStackProcessor->getStructure());
|
||||
|
||||
$childData['inlineParentUid'] = (int)$parent['uid'];
|
||||
$childData['renderType'] = 'inlineRecordContainer';
|
||||
$nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
|
||||
$childResult = $nodeFactory->create($childData)->render();
|
||||
|
||||
$jsonArray = [
|
||||
'data' => '',
|
||||
'stylesheetFiles' => [],
|
||||
'scriptCall' => [],
|
||||
];
|
||||
|
||||
// The HTML-object-id's prefix of the dynamically created record
|
||||
$objectPrefix = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid) . '-' . $child['table'];
|
||||
$objectId = $objectPrefix . '-' . (int)$child['uid'];
|
||||
$expandSingle = $parentConfig['appearance']['expandSingle'];
|
||||
$jsonArray['scriptCall'][] = 'inline.domAddRecordDetails(' . GeneralUtility::quoteJSvalue($domObjectId) . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . ($expandSingle ? '1' : '0') . ',json.data);';
|
||||
if ($parentConfig['foreign_unique']) {
|
||||
$jsonArray['scriptCall'][] = 'inline.removeUsed(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',\'' . (int)$child['uid'] . '\');';
|
||||
}
|
||||
$jsonArray = $this->mergeChildResultIntoJsonResult($jsonArray, $childResult);
|
||||
if ($parentConfig['appearance']['useSortable']) {
|
||||
$inlineObjectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
|
||||
$jsonArray['scriptCall'][] = 'inline.createDragAndDropSorting(' . GeneralUtility::quoteJSvalue($inlineObjectName . '_records') . ');';
|
||||
}
|
||||
if (!$parentConfig['appearance']['collapseAll'] && $expandSingle) {
|
||||
$jsonArray['scriptCall'][] = 'inline.collapseAllRecords(' . GeneralUtility::quoteJSvalue($objectId) . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',\'' . (int)$child['uid'] . '\');';
|
||||
}
|
||||
|
||||
return new JsonResponse($jsonArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds localizations or synchronizes the locations of all child records.
|
||||
* Handle AJAX calls to localize all records of a parent, localize a single record or to synchronize with the original language parent.
|
||||
*
|
||||
* @param ServerRequestInterface $request the incoming request
|
||||
* @return ResponseInterface the filled response
|
||||
*/
|
||||
public function synchronizeLocalizeAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$ajaxArguments = $request->getParsedBody()['ajax'] ?? $request->getQueryParams()['ajax'];
|
||||
$domObjectId = $ajaxArguments[0] ?? null;
|
||||
$type = $ajaxArguments[1] ?? null;
|
||||
$parentConfig = $this->extractSignedParentConfigFromRequest((string)$ajaxArguments['context']);
|
||||
|
||||
/** @var InlineStackProcessor $inlineStackProcessor */
|
||||
$inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
|
||||
// Parse the DOM identifier (string), add the levels to the structure stack (array), load the TCA config:
|
||||
$inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
|
||||
$inlineStackProcessor->injectAjaxConfiguration($parentConfig);
|
||||
$inlineFirstPid = $this->getInlineFirstPidFromDomObjectId($domObjectId);
|
||||
|
||||
$jsonArray = [
|
||||
'data' => '',
|
||||
'stylesheetFiles' => [],
|
||||
'scriptCall' => [],
|
||||
];
|
||||
if ($type === 'localize' || $type === 'synchronize' || MathUtility::canBeInterpretedAsInteger($type)) {
|
||||
// Parent, this table embeds the child table
|
||||
$parent = $inlineStackProcessor->getStructureLevel(-1);
|
||||
$parentFieldName = $parent['field'];
|
||||
|
||||
$processedTca = $GLOBALS['TCA'][$parent['table']];
|
||||
$processedTca['columns'][$parentFieldName]['config'] = $parentConfig;
|
||||
|
||||
// Child, a record from this table should be rendered
|
||||
$child = $inlineStackProcessor->getUnstableStructure();
|
||||
|
||||
$formDataCompilerInputForParent = [
|
||||
'vanillaUid' => (int)$parent['uid'],
|
||||
'command' => 'edit',
|
||||
'tableName' => $parent['table'],
|
||||
'processedTca' => $processedTca,
|
||||
'inlineFirstPid' => $inlineFirstPid,
|
||||
'columnsToProcess' => [
|
||||
$parentFieldName
|
||||
],
|
||||
// @todo: still needed? NO!
|
||||
'inlineStructure' => $inlineStackProcessor->getStructure(),
|
||||
// Do not compile existing children, we don't need them now
|
||||
'inlineCompileExistingChildren' => false,
|
||||
];
|
||||
// Full TcaDatabaseRecord is required here to have the list of connected uids $oldItemList
|
||||
/** @var TcaDatabaseRecord $formDataGroup */
|
||||
$formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
|
||||
/** @var FormDataCompiler $formDataCompiler */
|
||||
$formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
|
||||
$parentData = $formDataCompiler->compile($formDataCompilerInputForParent);
|
||||
$parentConfig = $parentData['processedTca']['columns'][$parentFieldName]['config'];
|
||||
$parentLanguageField = $parentData['processedTca']['ctrl']['languageField'];
|
||||
$parentLanguage = $parentData['databaseRow'][$parentLanguageField];
|
||||
$oldItemList = $parentData['databaseRow'][$parentFieldName];
|
||||
|
||||
// DataHandler cannot handle arrays as field value
|
||||
if (is_array($parentLanguage)) {
|
||||
$parentLanguage = implode(',', $parentLanguage);
|
||||
}
|
||||
|
||||
$cmd = [];
|
||||
// Localize a single child element from default language of the parent element
|
||||
if (MathUtility::canBeInterpretedAsInteger($type)) {
|
||||
$cmd[$parent['table']][$parent['uid']]['inlineLocalizeSynchronize'] = [
|
||||
'field' => $parent['field'],
|
||||
'language' => $parentLanguage,
|
||||
'ids' => [$type],
|
||||
];
|
||||
} else {
|
||||
// Either localize or synchronize all child elements from default language of the parent element
|
||||
$cmd[$parent['table']][$parent['uid']]['inlineLocalizeSynchronize'] = [
|
||||
'field' => $parent['field'],
|
||||
'language' => $parentLanguage,
|
||||
'action' => $type,
|
||||
];
|
||||
}
|
||||
|
||||
/** @var DataHandler $tce */
|
||||
$tce = GeneralUtility::makeInstance(DataHandler::class);
|
||||
$tce->start([], $cmd);
|
||||
$tce->process_cmdmap();
|
||||
|
||||
$newItemList = $tce->registerDBList[$parent['table']][$parent['uid']][$parentFieldName];
|
||||
|
||||
$nameObject = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
|
||||
$nameObjectForeignTable = $nameObject . '-' . $child['table'];
|
||||
|
||||
$oldItems = $this->getInlineRelatedRecordsUidArray($oldItemList);
|
||||
$newItems = $this->getInlineRelatedRecordsUidArray($newItemList);
|
||||
|
||||
// Render error messages from DataHandler
|
||||
$tce->printLogErrorMessages();
|
||||
$flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
|
||||
$messages = $flashMessageService->getMessageQueueByIdentifier()->getAllMessagesAndFlush();
|
||||
if (!empty($messages)) {
|
||||
foreach ($messages as $message) {
|
||||
$jsonArray['messages'][] = [
|
||||
'title' => $message->getTitle(),
|
||||
'message' => $message->getMessage(),
|
||||
'severity' => $message->getSeverity()
|
||||
];
|
||||
if ($message->getSeverity() === AbstractMessage::ERROR) {
|
||||
$jsonArray['hasErrors'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the items that should be removed in the forms view:
|
||||
$removedItems = array_diff($oldItems, $newItems);
|
||||
foreach ($removedItems as $childUid) {
|
||||
$jsonArray['scriptCall'][] = 'inline.deleteRecord(' . GeneralUtility::quoteJSvalue($nameObjectForeignTable . '-' . $childUid) . ', {forceDirectRemoval: true});';
|
||||
}
|
||||
|
||||
$localizedItems = array_diff($newItems, $oldItems);
|
||||
foreach ($localizedItems as $childUid) {
|
||||
$childData = $this->compileChild($parentData, $parentFieldName, (int)$childUid, $inlineStackProcessor->getStructure());
|
||||
|
||||
$childData['inlineParentUid'] = (int)$parent['uid'];
|
||||
$childData['renderType'] = 'inlineRecordContainer';
|
||||
$nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
|
||||
$childResult = $nodeFactory->create($childData)->render();
|
||||
|
||||
$jsonArray = $this->mergeChildResultIntoJsonResult($jsonArray, $childResult);
|
||||
|
||||
// Get the name of the field used as foreign selector (if any):
|
||||
$foreignSelector = isset($parentConfig['foreign_selector']) && $parentConfig['foreign_selector'] ? $parentConfig['foreign_selector'] : false;
|
||||
$selectedValue = $foreignSelector ? GeneralUtility::quoteJSvalue($childData['databaseRow'][$foreignSelector]) : 'null';
|
||||
if (is_array($selectedValue)) {
|
||||
$selectedValue = $selectedValue[0];
|
||||
}
|
||||
$jsonArray['scriptCall'][] = 'inline.memorizeAddRecord(' . GeneralUtility::quoteJSvalue($nameObjectForeignTable) . ', ' . GeneralUtility::quoteJSvalue($childUid) . ', null, ' . $selectedValue . ');';
|
||||
// Remove possible virtual records in the form which showed that a child records could be localized:
|
||||
$transOrigPointerFieldName = $childData['processedTca']['ctrl']['transOrigPointerField'];
|
||||
if (isset($childData['databaseRow'][$transOrigPointerFieldName]) && $childData['databaseRow'][$transOrigPointerFieldName]) {
|
||||
$transOrigPointerField = $childData['databaseRow'][$transOrigPointerFieldName];
|
||||
if (is_array($transOrigPointerField)) {
|
||||
$transOrigPointerField = $transOrigPointerField[0];
|
||||
}
|
||||
$jsonArray['scriptCall'][] = 'inline.fadeAndRemove(' . GeneralUtility::quoteJSvalue($nameObjectForeignTable . '-' . $transOrigPointerField . '_div') . ');';
|
||||
}
|
||||
}
|
||||
// Tell JS to add new HTML of one or multiple (localize all) records to DOM
|
||||
if (!empty($jsonArray['data'])) {
|
||||
$jsonArray['scriptCall'][] = 'inline.domAddNewRecord(\'bottom\', ' . GeneralUtility::quoteJSvalue($nameObject . '_records')
|
||||
. ', ' . GeneralUtility::quoteJSvalue($nameObjectForeignTable)
|
||||
. ', json.data);';
|
||||
}
|
||||
}
|
||||
return new JsonResponse($jsonArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store status of inline children expand / collapse state in backend user uC.
|
||||
*
|
||||
* @param ServerRequestInterface $request the incoming request
|
||||
* @return ResponseInterface the filled response
|
||||
*/
|
||||
public function expandOrCollapseAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$ajaxArguments = $request->getParsedBody()['ajax'] ?? $request->getQueryParams()['ajax'];
|
||||
$domObjectId = $ajaxArguments[0];
|
||||
|
||||
/** @var InlineStackProcessor $inlineStackProcessor */
|
||||
$inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
|
||||
// Parse the DOM identifier (string), add the levels to the structure stack (array), don't load TCA config
|
||||
$inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
|
||||
$expand = $ajaxArguments[1];
|
||||
$collapse = $ajaxArguments[2];
|
||||
|
||||
$backendUser = $this->getBackendUserAuthentication();
|
||||
// The current table - for this table we should add/import records
|
||||
$currentTable = $inlineStackProcessor->getUnstableStructure();
|
||||
$currentTable = $currentTable['table'];
|
||||
// The top parent table - this table embeds the current table
|
||||
$top = $inlineStackProcessor->getStructureLevel(0);
|
||||
$topTable = $top['table'];
|
||||
$topUid = $top['uid'];
|
||||
$inlineView = $this->getInlineExpandCollapseStateArray();
|
||||
// Only do some action if the top record and the current record were saved before
|
||||
if (MathUtility::canBeInterpretedAsInteger($topUid)) {
|
||||
$expandUids = GeneralUtility::trimExplode(',', $expand);
|
||||
$collapseUids = GeneralUtility::trimExplode(',', $collapse);
|
||||
// Set records to be expanded
|
||||
foreach ($expandUids as $uid) {
|
||||
$inlineView[$topTable][$topUid][$currentTable][] = $uid;
|
||||
}
|
||||
// Set records to be collapsed
|
||||
foreach ($collapseUids as $uid) {
|
||||
$inlineView[$topTable][$topUid][$currentTable] = $this->removeFromArray($uid, $inlineView[$topTable][$topUid][$currentTable]);
|
||||
}
|
||||
// Save states back to database
|
||||
if (is_array($inlineView[$topTable][$topUid][$currentTable])) {
|
||||
$inlineView[$topTable][$topUid][$currentTable] = array_unique($inlineView[$topTable][$topUid][$currentTable]);
|
||||
$backendUser->uc['inlineView'] = serialize($inlineView);
|
||||
$backendUser->writeUC();
|
||||
}
|
||||
}
|
||||
return (new JsonResponse())->setPayload([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a full child record
|
||||
*
|
||||
* @param array $parentData Result array of parent
|
||||
* @param string $parentFieldName Name of parent field
|
||||
* @param int $childUid Uid of child to compile
|
||||
* @param array $inlineStructure Current inline structure
|
||||
* @return array Full result array
|
||||
*
|
||||
* @todo: This clones methods compileChild from TcaInline Provider. Find a better abstraction
|
||||
* @todo: to also encapsulate the more complex scenarios with combination child and friends.
|
||||
*/
|
||||
protected function compileChild(array $parentData, $parentFieldName, $childUid, array $inlineStructure)
|
||||
{
|
||||
$parentConfig = $parentData['processedTca']['columns'][$parentFieldName]['config'];
|
||||
|
||||
/** @var InlineStackProcessor $inlineStackProcessor */
|
||||
$inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
|
||||
$inlineStackProcessor->initializeByGivenStructure($inlineStructure);
|
||||
$inlineTopMostParent = $inlineStackProcessor->getStructureLevel(0);
|
||||
|
||||
// @todo: do not use stack processor here ...
|
||||
$child = $inlineStackProcessor->getUnstableStructure();
|
||||
$childTableName = $child['table'];
|
||||
|
||||
/** @var TcaDatabaseRecord $formDataGroup */
|
||||
$formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
|
||||
/** @var FormDataCompiler $formDataCompiler */
|
||||
$formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
|
||||
$formDataCompilerInput = [
|
||||
'command' => 'edit',
|
||||
'tableName' => $childTableName,
|
||||
'vanillaUid' => (int)$childUid,
|
||||
'returnUrl' => $parentData['returnUrl'],
|
||||
'isInlineChild' => true,
|
||||
'inlineStructure' => $inlineStructure,
|
||||
'inlineFirstPid' => $parentData['inlineFirstPid'],
|
||||
'inlineParentConfig' => $parentConfig,
|
||||
'isInlineAjaxOpeningContext' => true,
|
||||
|
||||
// values of the current parent element
|
||||
// it is always a string either an id or new...
|
||||
'inlineParentUid' => $parentData['databaseRow']['uid'] ?? $parentData['uid'],
|
||||
'inlineParentTableName' => $parentData['tableName'],
|
||||
'inlineParentFieldName' => $parentFieldName,
|
||||
|
||||
// values of the top most parent element set on first level and not overridden on following levels
|
||||
'inlineTopMostParentUid' => $inlineTopMostParent['uid'],
|
||||
'inlineTopMostParentTableName' => $inlineTopMostParent['table'],
|
||||
'inlineTopMostParentFieldName' => $inlineTopMostParent['field'],
|
||||
];
|
||||
// For foreign_selector with useCombination $mainChild is the mm record
|
||||
// and $combinationChild is the child-child. For "normal" relations, $mainChild
|
||||
// is just the normal child record and $combinationChild is empty.
|
||||
$mainChild = $formDataCompiler->compile($formDataCompilerInput);
|
||||
if ($parentConfig['foreign_selector'] && $parentConfig['appearance']['useCombination']) {
|
||||
// This kicks in if opening an existing mainChild that has a child-child set
|
||||
$mainChild['combinationChild'] = $this->compileChildChild($mainChild, $parentConfig, $inlineStructure);
|
||||
}
|
||||
return $mainChild;
|
||||
}
|
||||
|
||||
/**
|
||||
* With useCombination set, not only content of the intermediate table, but also
|
||||
* the connected child should be rendered in one go. Prepare this here.
|
||||
*
|
||||
* @param array $child Full data array of "mm" record
|
||||
* @param array $parentConfig TCA configuration of "parent"
|
||||
* @param array $inlineStructure Current inline structure
|
||||
* @return array Full data array of child
|
||||
*/
|
||||
protected function compileChildChild(array $child, array $parentConfig, array $inlineStructure)
|
||||
{
|
||||
// foreign_selector on intermediate is probably type=select, so data provider of this table resolved that to the uid already
|
||||
$childChildUid = $child['databaseRow'][$parentConfig['foreign_selector']][0];
|
||||
// child-child table name is set in child tca "the selector field" foreign_table
|
||||
$childChildTableName = $child['processedTca']['columns'][$parentConfig['foreign_selector']]['config']['foreign_table'];
|
||||
/** @var TcaDatabaseRecord $formDataGroup */
|
||||
$formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
|
||||
/** @var FormDataCompiler $formDataCompiler */
|
||||
$formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
|
||||
$formDataCompilerInput = [
|
||||
'command' => 'edit',
|
||||
'tableName' => $childChildTableName,
|
||||
'vanillaUid' => (int)$childChildUid,
|
||||
'isInlineChild' => true,
|
||||
'isInlineAjaxOpeningContext' => true,
|
||||
// @todo: this is the wrong inline structure, isn't it? Shouldn't contain it the part from child child, too?
|
||||
'inlineStructure' => $inlineStructure,
|
||||
'inlineFirstPid' => $child['inlineFirstPid'],
|
||||
// values of the top most parent element set on first level and not overridden on following levels
|
||||
'inlineTopMostParentUid' => $child['inlineTopMostParentUid'],
|
||||
'inlineTopMostParentTableName' => $child['inlineTopMostParentTableName'],
|
||||
'inlineTopMostParentFieldName' => $child['inlineTopMostParentFieldName'],
|
||||
];
|
||||
return $formDataCompiler->compile($formDataCompilerInput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge stuff from child array into json array.
|
||||
* This method is needed since ajax handling methods currently need to put scriptCalls before and after child code.
|
||||
*
|
||||
* @param array $jsonResult Given json result
|
||||
* @param array $childResult Given child result
|
||||
* @return array Merged json array
|
||||
*/
|
||||
protected function mergeChildResultIntoJsonResult(array $jsonResult, array $childResult)
|
||||
{
|
||||
$jsonResult['data'] .= $childResult['html'];
|
||||
$jsonResult['stylesheetFiles'] = [];
|
||||
foreach ($childResult['stylesheetFiles'] as $stylesheetFile) {
|
||||
$jsonResult['stylesheetFiles'][] = $this->getRelativePathToStylesheetFile($stylesheetFile);
|
||||
}
|
||||
if (!empty($childResult['inlineData'])) {
|
||||
$jsonResult['scriptCall'][] = 'inline.addToDataArray(' . json_encode($childResult['inlineData']) . ');';
|
||||
}
|
||||
if (!empty($childResult['additionalJavaScriptSubmit'])) {
|
||||
$additionalJavaScriptSubmit = implode('', $childResult['additionalJavaScriptSubmit']);
|
||||
$additionalJavaScriptSubmit = str_replace([CR, LF], '', $additionalJavaScriptSubmit);
|
||||
$jsonResult['scriptCall'][] = 'TBE_EDITOR.addActionChecks("submit", "' . addslashes($additionalJavaScriptSubmit) . '");';
|
||||
}
|
||||
foreach ($childResult['additionalJavaScriptPost'] as $singleAdditionalJavaScriptPost) {
|
||||
$jsonResult['scriptCall'][] = $singleAdditionalJavaScriptPost;
|
||||
}
|
||||
if (!empty($childResult['additionalInlineLanguageLabelFiles'])) {
|
||||
$labels = [];
|
||||
foreach ($childResult['additionalInlineLanguageLabelFiles'] as $additionalInlineLanguageLabelFile) {
|
||||
ArrayUtility::mergeRecursiveWithOverrule(
|
||||
$labels,
|
||||
$this->getLabelsFromLocalizationFile($additionalInlineLanguageLabelFile)
|
||||
);
|
||||
}
|
||||
$javaScriptCode = [];
|
||||
$javaScriptCode[] = 'if (typeof TYPO3 === \'undefined\' || typeof TYPO3.lang === \'undefined\') {';
|
||||
$javaScriptCode[] = ' TYPO3.lang = {}';
|
||||
$javaScriptCode[] = '}';
|
||||
$javaScriptCode[] = 'var additionalInlineLanguageLabels = ' . json_encode($labels) . ';';
|
||||
$javaScriptCode[] = 'for (var attributeName in additionalInlineLanguageLabels) {';
|
||||
$javaScriptCode[] = ' if (typeof TYPO3.lang[attributeName] === \'undefined\') {';
|
||||
$javaScriptCode[] = ' TYPO3.lang[attributeName] = additionalInlineLanguageLabels[attributeName]';
|
||||
$javaScriptCode[] = ' }';
|
||||
$javaScriptCode[] = '}';
|
||||
|
||||
$jsonResult['scriptCall'][] = implode(LF, $javaScriptCode);
|
||||
}
|
||||
$requireJsModule = $this->createExecutableStringRepresentationOfRegisteredRequireJsModules($childResult);
|
||||
$jsonResult['scriptCall'] = array_merge($requireJsModule, $jsonResult['scriptCall']);
|
||||
|
||||
return $jsonResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array with the uids of related records out of a list of items.
|
||||
* This list could contain more information than required. This methods just
|
||||
* extracts the uids.
|
||||
*
|
||||
* @param string $itemList The list of related child records
|
||||
* @return array An array with uids
|
||||
*/
|
||||
protected function getInlineRelatedRecordsUidArray($itemList)
|
||||
{
|
||||
$itemArray = GeneralUtility::trimExplode(',', $itemList, true);
|
||||
// Perform modification of the selected items array:
|
||||
foreach ($itemArray as &$value) {
|
||||
$parts = explode('|', $value, 2);
|
||||
$value = $parts[0];
|
||||
}
|
||||
unset($value);
|
||||
return $itemArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return expand / collapse state array for a given table / uid combination
|
||||
*
|
||||
* @param string $table Handled table
|
||||
* @param int $uid Handled uid
|
||||
* @return array
|
||||
*/
|
||||
protected function getInlineExpandCollapseStateArrayForTableUid($table, $uid)
|
||||
{
|
||||
$inlineView = $this->getInlineExpandCollapseStateArray();
|
||||
$result = [];
|
||||
if (MathUtility::canBeInterpretedAsInteger($uid)) {
|
||||
if (!empty($inlineView[$table][$uid])) {
|
||||
$result = $inlineView[$table][$uid];
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get expand / collapse state of inline items
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getInlineExpandCollapseStateArray()
|
||||
{
|
||||
$backendUser = $this->getBackendUserAuthentication();
|
||||
if (!$this->backendUserHasUcInlineView($backendUser)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$inlineView = unserialize($backendUser->uc['inlineView'], ['allowed_classes' => false]);
|
||||
if (!is_array($inlineView)) {
|
||||
$inlineView = [];
|
||||
}
|
||||
|
||||
return $inlineView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to check whether the backend user has the property inline view for the current IRRE item.
|
||||
* In existing or old IRRE items the attribute may not exist, then the unserialize will fail.
|
||||
*
|
||||
* @param BackendUserAuthentication $backendUser
|
||||
* @return bool
|
||||
*/
|
||||
protected function backendUserHasUcInlineView(BackendUserAuthentication $backendUser)
|
||||
{
|
||||
return !empty($backendUser->uc['inlineView']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an element from an array.
|
||||
*
|
||||
* @param mixed $needle The element to be removed.
|
||||
* @param array $haystack The array the element should be removed from.
|
||||
* @param bool $strict Search elements strictly.
|
||||
* @return array The array $haystack without the $needle
|
||||
*/
|
||||
protected function removeFromArray($needle, $haystack, $strict = false)
|
||||
{
|
||||
$pos = array_search($needle, $haystack, $strict);
|
||||
if ($pos !== false) {
|
||||
unset($haystack[$pos]);
|
||||
}
|
||||
return $haystack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an error message that transferred as JSON for AJAX calls
|
||||
*
|
||||
* @param string $message The error message to be shown
|
||||
* @return array The error message in a JSON array
|
||||
*/
|
||||
protected function getErrorMessageForAJAX($message)
|
||||
{
|
||||
return [
|
||||
'data' => $message,
|
||||
'scriptCall' => [
|
||||
'alert("' . $message . '");'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get inlineFirstPid from a given objectId string
|
||||
*
|
||||
* @param string $domObjectId The id attribute of an element
|
||||
* @return int|null Pid or null
|
||||
*/
|
||||
protected function getInlineFirstPidFromDomObjectId($domObjectId)
|
||||
{
|
||||
// Substitute FlexForm addition and make parsing a bit easier
|
||||
$domObjectId = str_replace('---', ':', $domObjectId);
|
||||
// The starting pattern of an object identifier (e.g. "data-<firstPidValue>-<anything>)
|
||||
$pattern = '/^data' . '-' . '(.+?)' . '-' . '(.+)$/';
|
||||
if (preg_match($pattern, $domObjectId, $match)) {
|
||||
return $match[1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the config that is transferred over the wire to provide the
|
||||
* correct TCA config for the parent table
|
||||
*
|
||||
* @param string $contextString
|
||||
* @throws \RuntimeException
|
||||
* @return array
|
||||
*/
|
||||
protected function extractSignedParentConfigFromRequest(string $contextString): array
|
||||
{
|
||||
if ($contextString === '') {
|
||||
throw new \RuntimeException('Empty context string given', 1489751361);
|
||||
}
|
||||
$context = json_decode($contextString, true);
|
||||
if (empty($context['config'])) {
|
||||
throw new \RuntimeException('Empty context config section given', 1489751362);
|
||||
}
|
||||
if (!hash_equals(GeneralUtility::hmac((string)$context['config'], 'InlineContext'), (string)$context['hmac'])) {
|
||||
throw new \RuntimeException('Hash does not validate', 1489751363);
|
||||
}
|
||||
return json_decode($context['config'], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUserAuthentication()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Controller;
|
||||
|
||||
/*
|
||||
* 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\Form\FormDataCompiler;
|
||||
use TYPO3\CMS\Backend\Form\FormDataGroup\TcaSelectTreeAjaxFieldData;
|
||||
use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Backend controller for selectTree ajax operations
|
||||
*/
|
||||
class FormSelectTreeAjaxController
|
||||
{
|
||||
/**
|
||||
* Returns json representing category tree
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @throws \RuntimeException
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function fetchDataAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$tableName = $request->getQueryParams()['tableName'] ?? '';
|
||||
$fieldName = $request->getQueryParams()['fieldName'] ?? '';
|
||||
|
||||
// Prepare processedTca: Remove all column definitions except the one that contains
|
||||
// our tree definition. This way only this field is calculated, everything else is ignored.
|
||||
if (!isset($GLOBALS['TCA'][$tableName]) || !is_array($GLOBALS['TCA'][$tableName])) {
|
||||
throw new \RuntimeException(
|
||||
'TCA for table ' . $tableName . ' not found',
|
||||
1479386729
|
||||
);
|
||||
}
|
||||
$processedTca = $GLOBALS['TCA'][$tableName];
|
||||
if (!isset($processedTca['columns'][$fieldName]) || !is_array($processedTca['columns'][$fieldName])) {
|
||||
throw new \RuntimeException(
|
||||
'TCA for table ' . $tableName . ' and field ' . $fieldName . ' not found',
|
||||
1479386990
|
||||
);
|
||||
}
|
||||
|
||||
// Force given record type and set showitem to our field only
|
||||
$recordTypeValue = $request->getQueryParams()['recordTypeValue'];
|
||||
$processedTca['types'][$recordTypeValue]['showitem'] = $fieldName;
|
||||
// Unset all columns except our field
|
||||
$processedTca['columns'] = [
|
||||
$fieldName => $processedTca['columns'][$fieldName],
|
||||
];
|
||||
|
||||
$dataStructureIdentifier = '';
|
||||
$flexFormSheetName = '';
|
||||
$flexFormFieldName = '';
|
||||
$flexFormContainerIdentifier = '';
|
||||
$flexFormContainerFieldName = '';
|
||||
$flexSectionContainerPreparation = [];
|
||||
if ($processedTca['columns'][$fieldName]['config']['type'] === 'flex') {
|
||||
if (!empty($request->getQueryParams()['dataStructureIdentifier'])) {
|
||||
$dataStructureIdentifier = json_encode($request->getQueryParams()['dataStructureIdentifier']);
|
||||
}
|
||||
$flexFormSheetName = $request->getQueryParams()['flexFormSheetName'];
|
||||
$flexFormFieldName = $request->getQueryParams()['flexFormFieldName'];
|
||||
$flexFormContainerName = $request->getQueryParams()['flexFormContainerName'];
|
||||
$flexFormContainerIdentifier = $request->getQueryParams()['flexFormContainerIdentifier'];
|
||||
$flexFormContainerFieldName = $request->getQueryParams()['flexFormContainerFieldName'];
|
||||
$flexFormSectionContainerIsNew = (bool)$request->getQueryParams()['flexFormSectionContainerIsNew'];
|
||||
|
||||
$flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
|
||||
$dataStructure = $flexFormTools->parseDataStructureByIdentifier($dataStructureIdentifier);
|
||||
|
||||
// Reduce given data structure down to the relevant element only
|
||||
if (empty($flexFormContainerFieldName)) {
|
||||
if (isset($dataStructure['sheets'][$flexFormSheetName]['ROOT']
|
||||
['el'][$flexFormFieldName])
|
||||
) {
|
||||
$dataStructure = [
|
||||
'sheets' => [
|
||||
$flexFormSheetName => [
|
||||
'ROOT' => [
|
||||
'type' => 'array',
|
||||
'el' => [
|
||||
$flexFormFieldName => $dataStructure['sheets'][$flexFormSheetName]['ROOT']
|
||||
['el'][$flexFormFieldName],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
} else {
|
||||
if (isset($dataStructure['sheets'][$flexFormSheetName]['ROOT']
|
||||
['el'][$flexFormFieldName]
|
||||
['el'][$flexFormContainerName]
|
||||
['el'][$flexFormContainerFieldName])
|
||||
) {
|
||||
// If this is a tree in a section container that has just been added by the FlexFormAjaxController
|
||||
// "new container" action, then this container is not yet persisted, so we need to trigger the
|
||||
// TcaFlexProcess data provider again to prepare the DS and databaseRow of that container.
|
||||
if ($flexFormSectionContainerIsNew) {
|
||||
$flexSectionContainerPreparation = [
|
||||
'flexFormSheetName' => $flexFormSheetName,
|
||||
'flexFormFieldName' => $flexFormFieldName,
|
||||
'flexFormContainerName' => $flexFormContainerName,
|
||||
'flexFormContainerIdentifier' => $flexFormContainerIdentifier,
|
||||
];
|
||||
}
|
||||
// Now restrict the data structure to our tree element only
|
||||
$dataStructure = [
|
||||
'sheets' => [
|
||||
$flexFormSheetName => [
|
||||
'ROOT' => [
|
||||
'type' => 'array',
|
||||
'el' => [
|
||||
$flexFormFieldName => [
|
||||
'section' => 1,
|
||||
'type' => 'array',
|
||||
'el' => [
|
||||
$flexFormContainerName => [
|
||||
'el' => [
|
||||
$flexFormContainerFieldName => $dataStructure['sheets'][$flexFormSheetName]['ROOT']
|
||||
['el'][$flexFormFieldName]
|
||||
['el'][$flexFormContainerName]
|
||||
['el'][$flexFormContainerFieldName]
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
$processedTca['columns'][$fieldName]['config']['ds'] = $dataStructure;
|
||||
$processedTca['columns'][$fieldName]['config']['dataStructureIdentifier'] = $dataStructureIdentifier;
|
||||
}
|
||||
|
||||
$formDataGroup = GeneralUtility::makeInstance(TcaSelectTreeAjaxFieldData::class);
|
||||
$formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
|
||||
$formDataCompilerInput = [
|
||||
'tableName' => $tableName,
|
||||
'vanillaUid' => (int)$request->getQueryParams()['uid'],
|
||||
'command' => $request->getQueryParams()['command'],
|
||||
'processedTca' => $processedTca,
|
||||
'recordTypeValue' => $recordTypeValue,
|
||||
'selectTreeCompileItems' => true,
|
||||
'flexSectionContainerPreparation' => $flexSectionContainerPreparation,
|
||||
];
|
||||
$formData = $formDataCompiler->compile($formDataCompilerInput);
|
||||
|
||||
if ($formData['processedTca']['columns'][$fieldName]['config']['type'] === 'flex') {
|
||||
if (empty($flexFormContainerFieldName)) {
|
||||
$treeData = $formData['processedTca']['columns'][$fieldName]['config']['ds']
|
||||
['sheets'][$flexFormSheetName]['ROOT']
|
||||
['el'][$flexFormFieldName]['config']['items'];
|
||||
} else {
|
||||
$treeData = $formData['processedTca']['columns'][$fieldName]['config']['ds']
|
||||
['sheets'][$flexFormSheetName]['ROOT']
|
||||
['el'][$flexFormFieldName]
|
||||
['children'][$flexFormContainerIdentifier]
|
||||
['el'][$flexFormContainerFieldName]['config']['items'];
|
||||
}
|
||||
} else {
|
||||
$treeData = $formData['processedTca']['columns'][$fieldName]['config']['items'];
|
||||
}
|
||||
return (new JsonResponse())->setPayload($treeData ?? []);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace TYPO3\CMS\Backend\Controller;
|
||||
|
||||
/*
|
||||
* 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\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\DataHandling\Model\RecordStateFactory;
|
||||
use TYPO3\CMS\Core\DataHandling\SlugHelper;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Handle FormEngine AJAX calls for Slug validation and sanitization
|
||||
*
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class FormSlugAjaxController extends AbstractFormEngineAjaxController
|
||||
{
|
||||
|
||||
/**
|
||||
* Validates a given slug against the site and give a suggestion when it's already in use
|
||||
*
|
||||
* For new records this will look like this:
|
||||
* - If "slug" field is empty, take the other fields, and generate the slug based on the sent fields.
|
||||
* - JS: adapt the "placeholder" value only, as on save the field will be filled with the value via DataHandler
|
||||
* - If "slug" field is not empty (= "unlocked" and manually typed in)
|
||||
* - sanitize the slug
|
||||
* - If 'uniqueInSite' is set check if it's unique for the site
|
||||
* - If not unique propose another slug and return this with the flag hasConflicts = true
|
||||
* - If 'uniqueInPid' is set check if it's unique for the pid
|
||||
* - If not unique propose another slug and return this with the flag hasConflicts = true
|
||||
*
|
||||
* For existing records:
|
||||
* - sanitize the slug
|
||||
* - If 'uniqueInSite' is set check if it's unique for the site
|
||||
* - If not unique propose another slug and return this with the flag hasConflicts = true
|
||||
* - If 'uniqueInPid' is set check if it's unique for the pid
|
||||
* - If not unique propose another slug and return this with the flag hasConflicts = true
|
||||
* - If the slug has changed from the existing database record (@todo)
|
||||
* - Show a message that the old URL will stop working (possibly add a redirect via checkbox)
|
||||
* - If the page has subpages, show a warning that the subpages WILL NOT BE MODIFIED and keep the OLD url
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function suggestAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->checkRequest($request);
|
||||
|
||||
$queryParameters = $request->getParsedBody() ?? [];
|
||||
$values = $queryParameters['values'];
|
||||
$mode = $queryParameters['mode'];
|
||||
$tableName = $queryParameters['tableName'];
|
||||
$pid = (int)$queryParameters['pageId'];
|
||||
$parentPageId = (int)$queryParameters['parentPageId'];
|
||||
$recordId = (int)$queryParameters['recordId'];
|
||||
$languageId = (int)$queryParameters['language'];
|
||||
$fieldName = $queryParameters['fieldName'];
|
||||
|
||||
$fieldConfig = $GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'] ?? [];
|
||||
$row = BackendUtility::getRecord($tableName, $recordId);
|
||||
$recordType = BackendUtility::getTCAtypeValue($tableName, $row);
|
||||
$columnsOverridesConfigOfField = $GLOBALS['TCA'][$tableName]['types'][$recordType]['columnsOverrides'][$fieldName]['config'] ?? null;
|
||||
if ($columnsOverridesConfigOfField) {
|
||||
ArrayUtility::mergeRecursiveWithOverrule($fieldConfig, $columnsOverridesConfigOfField);
|
||||
}
|
||||
if (empty($fieldConfig)) {
|
||||
throw new \RuntimeException(
|
||||
'No valid field configuration for table ' . $tableName . ' field name ' . $fieldName . ' found.',
|
||||
1535379534
|
||||
);
|
||||
}
|
||||
|
||||
$evalInfo = !empty($fieldConfig['eval']) ? GeneralUtility::trimExplode(',', $fieldConfig['eval'], true) : [];
|
||||
$hasToBeUniqueInDb = in_array('unique', $evalInfo, true);
|
||||
$hasToBeUniqueInSite = in_array('uniqueInSite', $evalInfo, true);
|
||||
$hasToBeUniqueInPid = in_array('uniqueInPid', $evalInfo, true);
|
||||
|
||||
$hasConflict = false;
|
||||
|
||||
$recordData = $values;
|
||||
if (!isset($recordData['uid'])) {
|
||||
$recordData['uid'] = $recordId;
|
||||
}
|
||||
$recordData['pid'] = $pid;
|
||||
if (!empty($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])) {
|
||||
$recordData[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']] = $languageId;
|
||||
}
|
||||
if ($tableName === 'pages' && empty($recordData['is_siteroot'])) {
|
||||
$recordData['is_siteroot'] = $row['is_siteroot'];
|
||||
}
|
||||
|
||||
$slug = GeneralUtility::makeInstance(SlugHelper::class, $tableName, $fieldName, $fieldConfig);
|
||||
if ($mode === 'auto') {
|
||||
// New page - Feed incoming values to generator
|
||||
$proposal = $slug->generate($recordData, $pid);
|
||||
} elseif ($mode === 'recreate') {
|
||||
$proposal = $slug->generate($recordData, $parentPageId);
|
||||
} elseif ($mode === 'manual') {
|
||||
// Existing record - Fetch full record and only validate against the new "slug" field.
|
||||
$proposal = $slug->sanitize($values['manual']);
|
||||
} else {
|
||||
throw new \RuntimeException('mode must be either "auto", "recreate" or "manual"', 1535835666);
|
||||
}
|
||||
|
||||
$state = RecordStateFactory::forName($tableName)
|
||||
->fromArray($recordData, $pid, $recordId);
|
||||
if ($hasToBeUniqueInDb && !$slug->isUniqueInTable($proposal, $state)) {
|
||||
$hasConflict = true;
|
||||
$proposal = $slug->buildSlugForUniqueInTable($proposal, $state);
|
||||
}
|
||||
if ($hasToBeUniqueInSite && !$slug->isUniqueInSite($proposal, $state)) {
|
||||
$hasConflict = true;
|
||||
$proposal = $slug->buildSlugForUniqueInSite($proposal, $state);
|
||||
}
|
||||
if ($hasToBeUniqueInPid && !$slug->isUniqueInPid($proposal, $state)) {
|
||||
$hasConflict = true;
|
||||
$proposal = $slug->buildSlugForUniqueInPid($proposal, $state);
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'hasConflicts' => !$mode && $hasConflict,
|
||||
'manual' => $values['manual'] ?? '',
|
||||
'proposal' => $proposal,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @return bool
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function checkRequest(ServerRequestInterface $request): bool
|
||||
{
|
||||
$queryParameters = $request->getParsedBody() ?? [];
|
||||
$expectedHash = GeneralUtility::hmac(
|
||||
implode(
|
||||
'',
|
||||
[
|
||||
$queryParameters['tableName'],
|
||||
$queryParameters['pageId'],
|
||||
$queryParameters['recordId'],
|
||||
$queryParameters['language'],
|
||||
$queryParameters['fieldName'],
|
||||
$queryParameters['command'],
|
||||
$queryParameters['parentPageId'],
|
||||
]
|
||||
),
|
||||
__CLASS__
|
||||
);
|
||||
if (!hash_equals($expectedHash, $queryParameters['signature'])) {
|
||||
throw new \InvalidArgumentException(
|
||||
'HMAC could not be verified',
|
||||
1535137045
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller;
|
||||
|
||||
/*
|
||||
* 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\Domain\Repository\TableManualRepository;
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplate;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Http\RedirectResponse;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
use TYPO3Fluid\Fluid\View\ViewInterface;
|
||||
|
||||
/**
|
||||
* Main "CSH help" module controller
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class HelpController
|
||||
{
|
||||
protected const ALLOWED_ACTIONS = ['index', 'all', 'detail'];
|
||||
|
||||
/**
|
||||
* Section identifiers
|
||||
*/
|
||||
const FULL = 0;
|
||||
|
||||
/**
|
||||
* Show only Table of contents
|
||||
*/
|
||||
const TOC_ONLY = 1;
|
||||
|
||||
/**
|
||||
* @var TableManualRepository
|
||||
*/
|
||||
protected $tableManualRepository;
|
||||
|
||||
/** @var ModuleTemplate */
|
||||
protected $moduleTemplate;
|
||||
|
||||
/** @var ViewInterface */
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
* Instantiate the report controller
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
|
||||
$this->tableManualRepository = GeneralUtility::makeInstance(TableManualRepository::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects the request object for the current request, and renders correct action
|
||||
*
|
||||
* @param ServerRequestInterface $request the current request
|
||||
* @return ResponseInterface the response with the content
|
||||
*/
|
||||
public function handleRequest(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$action = $request->getQueryParams()['action'] ?? $request->getParsedBody()['action'] ?? 'index';
|
||||
|
||||
if ($action === 'detail') {
|
||||
$table = $request->getQueryParams()['table'] ?? $request->getParsedBody()['table'];
|
||||
if (!$table) {
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
return new RedirectResponse((string)$uriBuilder->buildUriFromRoute('help_cshmanual', [
|
||||
'action' => 'index',
|
||||
]), 303);
|
||||
}
|
||||
}
|
||||
|
||||
if (!in_array($action, self::ALLOWED_ACTIONS, true)) {
|
||||
return new HtmlResponse('Action not allowed', 400);
|
||||
}
|
||||
|
||||
$this->initializeView($action);
|
||||
|
||||
$result = call_user_func_array([$this, $action . 'Action'], [$request]);
|
||||
if ($result instanceof ResponseInterface) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$this->registerDocheaderButtons($request);
|
||||
|
||||
$this->moduleTemplate->setContent($this->view->render());
|
||||
return new HtmlResponse($this->moduleTemplate->renderContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $templateName
|
||||
*/
|
||||
protected function initializeView(string $templateName)
|
||||
{
|
||||
$this->view = GeneralUtility::makeInstance(StandaloneView::class);
|
||||
$this->view->setTemplate($templateName);
|
||||
$this->view->setTemplateRootPaths(['EXT:backend/Resources/Private/Templates/ContextSensitiveHelp']);
|
||||
$this->view->setPartialRootPaths(['EXT:backend/Resources/Private/Partials']);
|
||||
$this->view->setLayoutRootPaths(['EXT:backend/Resources/Private/Layouts']);
|
||||
$this->view->getRequest()->setControllerExtensionName('Backend');
|
||||
$this->view->assign('copyright', BackendUtility::TYPO3_copyRightNotice());
|
||||
}
|
||||
|
||||
/**
|
||||
* Show table of contents
|
||||
*/
|
||||
public function indexAction()
|
||||
{
|
||||
$this->view->assign('toc', $this->tableManualRepository->getSections(self::TOC_ONLY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the table of contents and all manuals
|
||||
*/
|
||||
public function allAction()
|
||||
{
|
||||
$this->view->assign('all', $this->tableManualRepository->getSections(self::FULL));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a single manual
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
*/
|
||||
public function detailAction(ServerRequestInterface $request)
|
||||
{
|
||||
$table = $request->getQueryParams()['table'] ?? $request->getParsedBody()['table'];
|
||||
$field = $request->getQueryParams()['field'] ?? $request->getParsedBody()['field'] ?? '*';
|
||||
|
||||
$mainKey = $table;
|
||||
|
||||
$this->view->assignMultiple([
|
||||
'table' => $table,
|
||||
'key' => $mainKey,
|
||||
'field' => $field,
|
||||
'manuals' => $field === '*'
|
||||
? $this->tableManualRepository->getTableManual($mainKey)
|
||||
: [$this->tableManualRepository->getSingleManual($mainKey, $field)],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the Icons into the docheader
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
*/
|
||||
protected function registerDocheaderButtons(ServerRequestInterface $request)
|
||||
{
|
||||
$buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
|
||||
|
||||
if ($this->getBackendUser()->mayMakeShortcut()) {
|
||||
$shortcutButton = $buttonBar->makeShortcutButton()
|
||||
->setModuleName('help_cshmanual')
|
||||
->setGetVariables(['table', 'field', 'route']);
|
||||
$buttonBar->addButton($shortcutButton);
|
||||
}
|
||||
|
||||
$action = $request->getQueryParams()['action'] ?? $request->getParsedBody()['action'] ?? 'index';
|
||||
if ($action !== 'index') {
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
$backButton = $buttonBar->makeLinkButton()
|
||||
->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:back'))
|
||||
->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-view-go-up', Icon::SIZE_SMALL))
|
||||
->setHref((string)$uriBuilder->buildUriFromRoute('help_cshmanual'));
|
||||
$buttonBar->addButton($backButton);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently logged in BE user
|
||||
*
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the LanguageService
|
||||
*
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Controller;
|
||||
|
||||
/*
|
||||
* 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\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\LinkHandling\LinkService;
|
||||
use TYPO3\CMS\Core\Page\PageRenderer;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\HttpUtility;
|
||||
use TYPO3\CMS\Frontend\Service\TypoLinkCodecService;
|
||||
use TYPO3\CMS\Recordlist\Controller\AbstractLinkBrowserController;
|
||||
|
||||
/**
|
||||
* Extended controller for link browser
|
||||
*/
|
||||
class LinkBrowserController extends AbstractLinkBrowserController
|
||||
{
|
||||
/**
|
||||
* Initialize $this->currentLinkParts
|
||||
*/
|
||||
protected function initCurrentUrl()
|
||||
{
|
||||
$currentLink = isset($this->parameters['currentValue']) ? trim($this->parameters['currentValue']) : '';
|
||||
$currentLinkParts = GeneralUtility::makeInstance(TypoLinkCodecService::class)->decode($currentLink);
|
||||
$currentLinkParts['params'] = $currentLinkParts['additionalParams'];
|
||||
unset($currentLinkParts['additionalParams']);
|
||||
|
||||
if (!empty($currentLinkParts['url'])) {
|
||||
$linkService = GeneralUtility::makeInstance(LinkService::class);
|
||||
$data = $linkService->resolve($currentLinkParts['url']);
|
||||
$currentLinkParts['type'] = $data['type'];
|
||||
unset($data['type']);
|
||||
$currentLinkParts['url'] = $data;
|
||||
}
|
||||
|
||||
$this->currentLinkParts = $currentLinkParts;
|
||||
|
||||
parent::initCurrentUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize document template object
|
||||
*/
|
||||
protected function initDocumentTemplate()
|
||||
{
|
||||
parent::initDocumentTemplate();
|
||||
|
||||
if (!$this->areFieldChangeFunctionsValid() && !$this->areFieldChangeFunctionsValid(true)) {
|
||||
$this->parameters['fieldChangeFunc'] = [];
|
||||
}
|
||||
unset($this->parameters['fieldChangeFunc']['alert']);
|
||||
$update = [];
|
||||
foreach ($this->parameters['fieldChangeFunc'] as $v) {
|
||||
$update[] = 'parent.opener.' . $v;
|
||||
}
|
||||
$inlineJS = implode(LF, $update);
|
||||
|
||||
$pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
|
||||
$pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/FormEngineLinkBrowserAdapter', 'function(FormEngineLinkBrowserAdapter) {
|
||||
FormEngineLinkBrowserAdapter.updateFunctions = function() {' . $inlineJS . '};
|
||||
}');
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a typolink via ajax
|
||||
*
|
||||
* This avoids to implement the encoding functionality again in JS for the browser.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function encodeTypoLink(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$typoLinkParts = $request->getQueryParams();
|
||||
if (isset($typoLinkParts['params'])) {
|
||||
$typoLinkParts['additionalParams'] = $typoLinkParts['params'];
|
||||
unset($typoLinkParts['params']);
|
||||
}
|
||||
|
||||
$typoLink = GeneralUtility::makeInstance(TypoLinkCodecService::class)->encode($typoLinkParts);
|
||||
return new JsonResponse(['typoLink' => $typoLink]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether submitted field change functions are valid
|
||||
* and are coming from the system and not from an external abuse.
|
||||
*
|
||||
* @param bool $handleFlexformSections Whether to handle flexform sections differently
|
||||
* @return bool Whether the submitted field change functions are valid
|
||||
*/
|
||||
protected function areFieldChangeFunctionsValid($handleFlexformSections = false)
|
||||
{
|
||||
$result = false;
|
||||
if (isset($this->parameters['fieldChangeFunc']) && is_array($this->parameters['fieldChangeFunc']) && isset($this->parameters['fieldChangeFuncHash'])) {
|
||||
$matches = [];
|
||||
$pattern = '#\\[el\\]\\[(([^]-]+-[^]-]+-)(idx\\d+-)([^]]+))\\]#i';
|
||||
$fieldChangeFunctions = $this->parameters['fieldChangeFunc'];
|
||||
// Special handling of flexform sections:
|
||||
// Field change functions are modified in JavaScript, thus the hash is always invalid
|
||||
if ($handleFlexformSections && preg_match($pattern, $this->parameters['itemName'], $matches)) {
|
||||
$originalName = $matches[1];
|
||||
$cleanedName = $matches[2] . $matches[4];
|
||||
foreach ($fieldChangeFunctions as &$value) {
|
||||
$value = str_replace($originalName, $cleanedName, $value);
|
||||
}
|
||||
unset($value);
|
||||
}
|
||||
$result = hash_equals(GeneralUtility::hmac(serialize($fieldChangeFunctions), 'backend-link-browser'), $this->parameters['fieldChangeFuncHash']);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attributes for the body tag
|
||||
*
|
||||
* @return string[] Array of body-tag attributes
|
||||
*/
|
||||
protected function getBodyTagAttributes()
|
||||
{
|
||||
$parameters = parent::getBodyTagAttributes();
|
||||
|
||||
$formEngineParameters['fieldChangeFunc'] = $this->parameters['fieldChangeFunc'];
|
||||
$formEngineParameters['fieldChangeFuncHash'] = GeneralUtility::hmac(serialize($this->parameters['fieldChangeFunc']), 'backend-link-browser');
|
||||
|
||||
$parameters['data-add-on-params'] .= HttpUtility::buildQueryString(['P' => $formEngineParameters], '&');
|
||||
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ID of current page
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function getCurrentPageId()
|
||||
{
|
||||
$pageId = 0;
|
||||
$browserParameters = $this->parameters;
|
||||
if (isset($browserParameters['pid'])) {
|
||||
$pageId = $browserParameters['pid'];
|
||||
} elseif (isset($browserParameters['itemName'])) {
|
||||
// parse data[<table>][<uid>]
|
||||
if (preg_match('~data\[([^]]*)\]\[([^]]*)\]~', $browserParameters['itemName'], $matches)) {
|
||||
$recordArray = BackendUtility::getRecord($matches['1'], $matches['2']);
|
||||
if (is_array($recordArray)) {
|
||||
$pageId = $recordArray['pid'];
|
||||
}
|
||||
}
|
||||
}
|
||||
return (int)BackendUtility::getTSCpidCached($browserParameters['table'], $browserParameters['uid'], $pageId)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the configuration
|
||||
* @return array
|
||||
*/
|
||||
public function getConfiguration(): array
|
||||
{
|
||||
$tsConfig = BackendUtility::getPagesTSconfig($this->getCurrentPageId());
|
||||
return $tsConfig['TCEMAIN.']['linkHandler.']['page.']['configuration.'] ?? [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Controller;
|
||||
|
||||
/*
|
||||
* 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\Search\LiveSearch\LiveSearch;
|
||||
use TYPO3\CMS\Backend\Search\LiveSearch\QueryParser;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Returns the results for any live searches, e.g. in the toolbar
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class LiveSearchController
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $searchResults = [];
|
||||
|
||||
/**
|
||||
* Processes all AJAX calls and sends back a JSON object
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function liveSearchAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$queryString = trim($request->getQueryParams()['q']);
|
||||
$liveSearch = GeneralUtility::makeInstance(LiveSearch::class);
|
||||
$queryParser = GeneralUtility::makeInstance(QueryParser::class);
|
||||
|
||||
$searchResults = [];
|
||||
$liveSearch->setQueryString($queryString);
|
||||
// Jump & edit - find page and retrieve an edit link (this is only for pages
|
||||
if ($queryParser->isValidPageJump($queryString)) {
|
||||
$commandQuery = $queryParser->getCommandForPageJump($queryString);
|
||||
if ($commandQuery) {
|
||||
$queryString = $commandQuery;
|
||||
}
|
||||
}
|
||||
// Search through the database and find records who match to the given search string
|
||||
$resultArray = $liveSearch->find($queryString);
|
||||
foreach ($resultArray as $resultFromTable) {
|
||||
foreach ($resultFromTable as $item) {
|
||||
$searchResults[] = $item;
|
||||
}
|
||||
}
|
||||
return (new JsonResponse())->setPayload($searchResults);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,629 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller;
|
||||
|
||||
/*
|
||||
* 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 Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use Symfony\Component\HttpFoundation\Cookie;
|
||||
use TYPO3\CMS\Backend\Exception;
|
||||
use TYPO3\CMS\Backend\LoginProvider\LoginProviderInterface;
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Template\DocumentTemplate;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
|
||||
use TYPO3\CMS\Core\Configuration\Features;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\FormProtection\BackendFormProtection;
|
||||
use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Http\NormalizedParams;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Localization\Locales;
|
||||
use TYPO3\CMS\Core\Page\PageRenderer;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\HttpUtility;
|
||||
use TYPO3\CMS\Core\Utility\PathUtility;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
|
||||
/**
|
||||
* Script Class for rendering the login form
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class LoginController implements LoggerAwareInterface
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
/**
|
||||
* The URL to redirect to after login.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $redirectUrl;
|
||||
|
||||
/**
|
||||
* Set to the redirect URL of the form (may be redirect_url or "index.php?M=main")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $redirectToURL;
|
||||
|
||||
/**
|
||||
* the active login provider identifier
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $loginProviderIdentifier;
|
||||
|
||||
/**
|
||||
* List of registered and sorted login providers
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $loginProviders = [];
|
||||
|
||||
/**
|
||||
* Login-refresh bool; The backend will call this script
|
||||
* with this value set when the login is close to being expired
|
||||
* and the form needs to be redrawn.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $loginRefresh;
|
||||
|
||||
/**
|
||||
* Value of forms submit button for login.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $submitValue;
|
||||
|
||||
/**
|
||||
* @var StandaloneView
|
||||
*/
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
* Initialize the login box. Will also react on a &L=OUT flag and exit.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// @deprecated since TYPO3 v9, will be obsolete in TYPO3 v10.0
|
||||
$request = $GLOBALS['TYPO3_REQUEST'];
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$queryParams = $request->getQueryParams();
|
||||
$this->validateAndSortLoginProviders();
|
||||
|
||||
$this->redirectUrl = GeneralUtility::sanitizeLocalUrl($parsedBody['redirect_url'] ?? $queryParams['redirect_url'] ?? null);
|
||||
$this->loginProviderIdentifier = $this->detectLoginProvider($request);
|
||||
|
||||
$this->loginRefresh = (bool)($parsedBody['loginRefresh'] ?? $queryParams['loginRefresh'] ?? false);
|
||||
// Value of "Login" button. If set, the login button was pressed.
|
||||
$this->submitValue = $parsedBody['commandLI'] ?? $queryParams['commandLI'] ?? null;
|
||||
// Try to get the preferred browser language
|
||||
/** @var Locales $locales */
|
||||
$locales = GeneralUtility::makeInstance(Locales::class);
|
||||
$httpAcceptLanguage = $request->getServerParams()['HTTP_ACCEPT_LANGUAGE'];
|
||||
$preferredBrowserLanguage = $locales
|
||||
->getPreferredClientLanguage($httpAcceptLanguage);
|
||||
|
||||
// If we found a $preferredBrowserLanguage and it is not the default language and no be_user is logged in
|
||||
// initialize $this->getLanguageService() again with $preferredBrowserLanguage
|
||||
if ($preferredBrowserLanguage !== 'default' && empty($this->getBackendUserAuthentication()->user['uid'])) {
|
||||
$this->getLanguageService()->init($preferredBrowserLanguage);
|
||||
GeneralUtility::makeInstance(PageRenderer::class)->setLanguage($preferredBrowserLanguage);
|
||||
}
|
||||
|
||||
$this->getLanguageService()->includeLLFile('EXT:backend/Resources/Private/Language/locallang_login.xlf');
|
||||
|
||||
// Setting the redirect URL to "index.php?M=main" if no alternative input is given
|
||||
if ($this->redirectUrl) {
|
||||
$this->redirectToURL = $this->redirectUrl;
|
||||
} else {
|
||||
// (consolidate RouteDispatcher::evaluateReferrer() when changing 'main' to something different)
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
$this->redirectToURL = (string)$uriBuilder->buildUriFromRoute('main');
|
||||
}
|
||||
|
||||
// If "L" is "OUT", then any logged in is logged out. If redirect_url is given, we redirect to it
|
||||
if (($parsedBody['L'] ?? $queryParams['L'] ?? null) === 'OUT' && is_object($this->getBackendUserAuthentication())) {
|
||||
$this->getBackendUserAuthentication()->logoff();
|
||||
$this->redirectToUrl();
|
||||
}
|
||||
|
||||
$this->view = $this->getFluidTemplateObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects the request and response objects 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 finished response with the content
|
||||
*/
|
||||
public function formAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
return new HtmlResponse($this->createLoginLogoutForm($request));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the main function but with loginRefresh enabled at any time
|
||||
*
|
||||
* @param ServerRequestInterface $request the current request
|
||||
* @return ResponseInterface the finished response with the content
|
||||
*/
|
||||
public function refreshAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->loginRefresh = true;
|
||||
return new HtmlResponse($this->createLoginLogoutForm($request));
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function - creating the login/logout form
|
||||
*
|
||||
* @throws Exception
|
||||
* @return string The content to output
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function main(): string
|
||||
{
|
||||
trigger_error('LoginController->main() will be replaced by protected method createLoginLogoutForm() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
|
||||
return $this->createLoginLogoutForm($GLOBALS['TYPO3_REQUEST']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function - creating the login/logout form
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return string $content
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function createLoginLogoutForm(ServerRequestInterface $request): string
|
||||
{
|
||||
/** @var PageRenderer $pageRenderer */
|
||||
$pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
|
||||
$pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Login');
|
||||
|
||||
// Checking, if we should make a redirect.
|
||||
// Might set JavaScript in the header to close window.
|
||||
$this->checkRedirect($request);
|
||||
|
||||
// Extension Configuration
|
||||
$extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('backend');
|
||||
|
||||
// Background Image
|
||||
if (!empty($extConf['loginBackgroundImage'])) {
|
||||
$backgroundImage = $this->getUriForFileName($extConf['loginBackgroundImage']);
|
||||
if ($backgroundImage === '') {
|
||||
$this->logger->warning(
|
||||
'The configured TYPO3 backend login background image "' . htmlspecialchars($extConf['loginBackgroundImage']) .
|
||||
'" can\'t be resolved. Please check if the file exists and the extension is activated.'
|
||||
);
|
||||
}
|
||||
$this->getDocumentTemplate()->inDocStylesArray[] = '
|
||||
.typo3-login-carousel-control.right,
|
||||
.typo3-login-carousel-control.left,
|
||||
.panel-login { border: 0; }
|
||||
.typo3-login { background-image: url("' . $backgroundImage . '"); }
|
||||
.typo3-login-footnote { background-color: #000000; color: #ffffff; opacity: 0.5; }
|
||||
';
|
||||
}
|
||||
|
||||
// Login Footnote
|
||||
if (!empty($extConf['loginFootnote'])) {
|
||||
$this->view->assign('loginFootnote', strip_tags(trim($extConf['loginFootnote'])));
|
||||
}
|
||||
|
||||
// Add additional css to use the highlight color in the login screen
|
||||
if (!empty($extConf['loginHighlightColor'])) {
|
||||
$this->getDocumentTemplate()->inDocStylesArray[] = '
|
||||
.btn-login.disabled, .btn-login[disabled], fieldset[disabled] .btn-login,
|
||||
.btn-login.disabled:hover, .btn-login[disabled]:hover, fieldset[disabled] .btn-login:hover,
|
||||
.btn-login.disabled:focus, .btn-login[disabled]:focus, fieldset[disabled] .btn-login:focus,
|
||||
.btn-login.disabled.focus, .btn-login[disabled].focus, fieldset[disabled] .btn-login.focus,
|
||||
.btn-login.disabled:active, .btn-login[disabled]:active, fieldset[disabled] .btn-login:active,
|
||||
.btn-login.disabled.active, .btn-login[disabled].active, fieldset[disabled] .btn-login.active,
|
||||
.btn-login:hover, .btn-login:focus, .btn-login:active,
|
||||
.btn-login:active:hover, .btn-login:active:focus,
|
||||
.btn-login { background-color: ' . $extConf['loginHighlightColor'] . '; }
|
||||
.panel-login .panel-body { border-color: ' . $extConf['loginHighlightColor'] . '; }
|
||||
';
|
||||
}
|
||||
|
||||
// Logo
|
||||
if (!empty($extConf['loginLogo'])) {
|
||||
if ($this->getUriForFileName($extConf['loginLogo']) === '') {
|
||||
$this->logger->warning(
|
||||
'The configured TYPO3 backend login logo "' . htmlspecialchars($extConf['loginLogo']) .
|
||||
'" can\'t be resolved. Please check if the file exists and the extension is activated.'
|
||||
);
|
||||
}
|
||||
$logo = $extConf['loginLogo'];
|
||||
} else {
|
||||
// Use TYPO3 logo depending on highlight color
|
||||
if (!empty($extConf['loginHighlightColor'])) {
|
||||
$logo = 'EXT:backend/Resources/Public/Images/typo3_black.svg';
|
||||
} else {
|
||||
$logo = 'EXT:backend/Resources/Public/Images/typo3_orange.svg';
|
||||
}
|
||||
$this->getDocumentTemplate()->inDocStylesArray[] = '
|
||||
.typo3-login-logo .typo3-login-image { max-width: 150px; height:100%;}
|
||||
';
|
||||
}
|
||||
$logo = $this->getUriForFileName($logo);
|
||||
|
||||
// Start form
|
||||
$formType = empty($this->getBackendUserAuthentication()->user['uid']) ? 'LoginForm' : 'LogoutForm';
|
||||
$this->view->assignMultiple([
|
||||
'backendUser' => $this->getBackendUserAuthentication()->user,
|
||||
'hasLoginError' => $this->isLoginInProgress($request),
|
||||
'formType' => $formType,
|
||||
'logo' => $logo,
|
||||
'images' => [
|
||||
'capslock' => $this->getUriForFileName('EXT:backend/Resources/Public/Images/icon_capslock.svg'),
|
||||
'typo3' => $this->getUriForFileName('EXT:backend/Resources/Public/Images/typo3_orange.svg'),
|
||||
],
|
||||
'copyright' => BackendUtility::TYPO3_copyRightNotice(),
|
||||
'redirectUrl' => $this->redirectUrl,
|
||||
'loginRefresh' => $this->loginRefresh,
|
||||
'loginNewsItems' => $this->getSystemNews(),
|
||||
'referrerCheckEnabled' => GeneralUtility::makeInstance(Features::class)->isFeatureEnabled('security.backend.enforceReferrer'),
|
||||
'loginUrl' => (string)$request->getUri(),
|
||||
'loginProviderIdentifier' => $this->loginProviderIdentifier,
|
||||
'loginProviders' => $this->loginProviders
|
||||
]);
|
||||
|
||||
// Initialize interface selectors:
|
||||
$this->makeInterfaceSelector($request);
|
||||
|
||||
/** @var LoginProviderInterface $loginProvider */
|
||||
$loginProvider = GeneralUtility::makeInstance($this->loginProviders[$this->loginProviderIdentifier]['provider']);
|
||||
$loginProvider->render($this->view, $pageRenderer, $this);
|
||||
|
||||
$content = $this->getDocumentTemplate()->startPage('TYPO3 CMS Login: ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']);
|
||||
$content .= $this->view->render();
|
||||
$content .= $this->getDocumentTemplate()->endPage();
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checking, if we should perform some sort of redirection OR closing of windows.
|
||||
*
|
||||
* Do a redirect if a user is logged in
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @throws \RuntimeException
|
||||
* @throws \UnexpectedValueException
|
||||
* @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
|
||||
*/
|
||||
protected function checkRedirect(ServerRequestInterface $request): void
|
||||
{
|
||||
$backendUser = $this->getBackendUserAuthentication();
|
||||
if (empty($backendUser->user['uid'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* If no cookie has been set previously, we tell people that this is a problem.
|
||||
* This assumes that a cookie-setting script (like this one) has been hit at
|
||||
* least once prior to this instance.
|
||||
*/
|
||||
if (!isset($_COOKIE[BackendUserAuthentication::getCookieName()])) {
|
||||
if ($this->submitValue === 'setCookie') {
|
||||
/*
|
||||
* we tried it a second time but still no cookie
|
||||
* 26/4 2005: This does not work anymore, because the saving of challenge values
|
||||
* in $_SESSION means the system will act as if the password was wrong.
|
||||
*/
|
||||
throw new \RuntimeException('Login-error: Yeah, that\'s a classic. No cookies, no TYPO3. ' .
|
||||
'Please accept cookies from TYPO3 - otherwise you\'ll not be able to use the system.', 1294586846);
|
||||
}
|
||||
// try it once again - that might be needed for auto login
|
||||
$this->redirectToURL = 'index.php?commandLI=setCookie';
|
||||
}
|
||||
$redirectToUrl = (string)($backendUser->getTSConfig()['auth.']['BE.']['redirectToURL'] ?? '');
|
||||
if (empty($redirectToUrl)) {
|
||||
// Based on the interface we set the redirect script
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$queryParams = $request->getQueryParams();
|
||||
$interface = $parsedBody['interface'] ?? $queryParams['interface'] ?? '';
|
||||
switch ($interface) {
|
||||
case 'frontend':
|
||||
$this->redirectToURL = '../';
|
||||
break;
|
||||
case 'backend':
|
||||
// (consolidate RouteDispatcher::evaluateReferrer() when changing 'main' to something different)
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
$this->redirectToURL = (string)$uriBuilder->buildUriFromRoute('main');
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$this->redirectToURL = $redirectToUrl;
|
||||
$interface = '';
|
||||
}
|
||||
// store interface
|
||||
$backendUser->uc['interfaceSetup'] = $interface;
|
||||
$backendUser->writeUC();
|
||||
|
||||
$formProtection = FormProtectionFactory::get();
|
||||
if (!$formProtection instanceof BackendFormProtection) {
|
||||
throw new \RuntimeException('The Form Protection retrieved does not match the expected one.', 1432080411);
|
||||
}
|
||||
if ($this->loginRefresh) {
|
||||
$formProtection->setSessionTokenFromRegistry();
|
||||
$formProtection->persistSessionToken();
|
||||
$this->getDocumentTemplate()->JScode .= GeneralUtility::wrapJS('
|
||||
if (window.opener && window.opener.TYPO3 && window.opener.TYPO3.LoginRefresh) {
|
||||
window.opener.TYPO3.LoginRefresh.startTask();
|
||||
window.close();
|
||||
}
|
||||
');
|
||||
} else {
|
||||
$formProtection->storeSessionTokenInRegistry();
|
||||
$this->redirectToUrl();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Making interface selector
|
||||
*
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function makeInterfaceSelectorBox(): void
|
||||
{
|
||||
trigger_error('LoginController->makeInterfaceSelectorBox() will be replaced by protected method makeInterfaceSelector() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
|
||||
$this->makeInterfaceSelector($GLOBALS['TYPO3_REQUEST']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Making interface selector
|
||||
* @param ServerRequestInterface $request
|
||||
*/
|
||||
protected function makeInterfaceSelector(ServerRequestInterface $request): void
|
||||
{
|
||||
// If interfaces are defined AND no input redirect URL in GET vars:
|
||||
if ($GLOBALS['TYPO3_CONF_VARS']['BE']['interfaces'] && ($this->isLoginInProgress($request) || !$this->redirectUrl)) {
|
||||
$parts = GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['BE']['interfaces']);
|
||||
if (count($parts) > 1) {
|
||||
// Only if more than one interface is defined we will show the selector
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
$interfaces = [
|
||||
'backend' => [
|
||||
'label' => $this->getLanguageService()->getLL('interface.backend'),
|
||||
'jumpScript' => (string)$uriBuilder->buildUriFromRoute('main'),
|
||||
'interface' => 'backend'
|
||||
],
|
||||
'frontend' => [
|
||||
'label' => $this->getLanguageService()->getLL('interface.frontend'),
|
||||
'jumpScript' => '../',
|
||||
'interface' => 'frontend'
|
||||
]
|
||||
];
|
||||
|
||||
$this->view->assign('showInterfaceSelector', true);
|
||||
$this->view->assign('interfaces', $interfaces);
|
||||
} elseif (!$this->redirectUrl) {
|
||||
// If there is only ONE interface value set and no redirect_url is present
|
||||
$this->view->assign('showInterfaceSelector', false);
|
||||
$this->view->assign('interface', $parts[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets news from sys_news and converts them into a format suitable for
|
||||
* showing them at the login screen.
|
||||
*
|
||||
* @return array An array of login news.
|
||||
*/
|
||||
protected function getSystemNews(): array
|
||||
{
|
||||
$systemNewsTable = 'sys_news';
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable($systemNewsTable);
|
||||
$systemNews = [];
|
||||
$systemNewsRecords = $queryBuilder
|
||||
->select('title', 'content', 'crdate')
|
||||
->from($systemNewsTable)
|
||||
->orderBy('crdate', 'DESC')
|
||||
->execute()
|
||||
->fetchAll();
|
||||
foreach ($systemNewsRecords as $systemNewsRecord) {
|
||||
$systemNews[] = [
|
||||
'date' => date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], (int)$systemNewsRecord['crdate']),
|
||||
'header' => $systemNewsRecord['title'],
|
||||
'content' => $systemNewsRecord['content']
|
||||
];
|
||||
}
|
||||
return $systemNews;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the uri of a relative reference, resolves the "EXT:" prefix
|
||||
* (way of referring to files inside extensions) and checks that the file is inside
|
||||
* the project root of the TYPO3 installation
|
||||
*
|
||||
* @param string $filename The input filename/filepath to evaluate
|
||||
* @return string Returns the filename of $filename if valid, otherwise blank string.
|
||||
* @internal
|
||||
*/
|
||||
private function getUriForFileName($filename): string
|
||||
{
|
||||
// Check if it's already a URL
|
||||
if (preg_match('/^(https?:)?\/\//', $filename)) {
|
||||
return $filename;
|
||||
}
|
||||
$absoluteFilename = GeneralUtility::getFileAbsFileName(ltrim($filename, '/'));
|
||||
$filename = '';
|
||||
if ($absoluteFilename !== '' && @is_file($absoluteFilename)) {
|
||||
$filename = PathUtility::getAbsoluteWebPath($absoluteFilename);
|
||||
}
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if login credentials are currently submitted
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return bool
|
||||
*/
|
||||
protected function isLoginInProgress(ServerRequestInterface $request): bool
|
||||
{
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$queryParams = $request->getQueryParams();
|
||||
$username = $parsedBody['username'] ?? $queryParams['username'] ?? null;
|
||||
return !empty($username) || !empty($this->submitValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a new standalone view, shorthand function
|
||||
*
|
||||
* @return StandaloneView
|
||||
*/
|
||||
protected function getFluidTemplateObject()
|
||||
{
|
||||
/** @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->getRequest()->setControllerExtensionName('Backend');
|
||||
return $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper method to redirect to configured redirect URL
|
||||
*/
|
||||
protected function redirectToUrl(): void
|
||||
{
|
||||
HttpUtility::redirect($this->redirectToURL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the registered login providers
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function validateAndSortLoginProviders()
|
||||
{
|
||||
$providers = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['backend']['loginProviders'] ?? [];
|
||||
if (empty($providers) || !is_array($providers)) {
|
||||
throw new \RuntimeException('No login providers are registered in $GLOBALS[\'TYPO3_CONF_VARS\'][\'EXTCONF\'][\'backend\'][\'loginProviders\'].', 1433417281);
|
||||
}
|
||||
foreach ($providers as $identifier => $configuration) {
|
||||
if (empty($configuration) || !is_array($configuration)) {
|
||||
throw new \RuntimeException('Missing configuration for login provider "' . $identifier . '".', 1433416043);
|
||||
}
|
||||
if (!is_string($configuration['provider']) || empty($configuration['provider']) || !class_exists($configuration['provider']) || !is_subclass_of($configuration['provider'], LoginProviderInterface::class)) {
|
||||
throw new \RuntimeException('The login provider "' . $identifier . '" defines an invalid provider. Ensure the class exists and implements the "' . LoginProviderInterface::class . '".', 1460977275);
|
||||
}
|
||||
if (empty($configuration['label'])) {
|
||||
throw new \RuntimeException('Missing label definition for login provider "' . $identifier . '".', 1433416044);
|
||||
}
|
||||
if (empty($configuration['icon-class'])) {
|
||||
throw new \RuntimeException('Missing icon definition for login provider "' . $identifier . '".', 1433416045);
|
||||
}
|
||||
if (!isset($configuration['sorting'])) {
|
||||
throw new \RuntimeException('Missing sorting definition for login provider "' . $identifier . '".', 1433416046);
|
||||
}
|
||||
}
|
||||
// sort providers
|
||||
uasort($providers, function ($a, $b) {
|
||||
return $b['sorting'] - $a['sorting'];
|
||||
});
|
||||
$this->loginProviders = $providers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the login provider, get from request or choose the
|
||||
* first one as default
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return string
|
||||
*/
|
||||
protected function detectLoginProvider(ServerRequestInterface $request): string
|
||||
{
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$queryParams = $request->getQueryParams();
|
||||
$loginProvider = $parsedBody['loginProvider'] ?? $queryParams['loginProvider'] ?? '';
|
||||
if ((empty($loginProvider) || !isset($this->loginProviders[$loginProvider])) && !empty($_COOKIE['be_lastLoginProvider'])) {
|
||||
$loginProvider = $_COOKIE['be_lastLoginProvider'];
|
||||
}
|
||||
if (empty($loginProvider) || !isset($this->loginProviders[$loginProvider])) {
|
||||
reset($this->loginProviders);
|
||||
$loginProvider = key($this->loginProviders);
|
||||
}
|
||||
// Use the secure option when the current request is served by a secure connection
|
||||
/** @var NormalizedParams $normalizedParams */
|
||||
$normalizedParams = $request->getAttribute('normalizedParams');
|
||||
$isHttps = $normalizedParams->isHttps();
|
||||
$cookieSecure = (bool)$GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieSecure'] && $isHttps;
|
||||
$cookie = new Cookie(
|
||||
'be_lastLoginProvider',
|
||||
(string)$loginProvider,
|
||||
$GLOBALS['EXEC_TIME'] + 7776000, // 90 days
|
||||
$normalizedParams->getSitePath() . TYPO3_mainDir,
|
||||
'',
|
||||
$cookieSecure,
|
||||
true,
|
||||
false,
|
||||
Cookie::SAMESITE_STRICT
|
||||
);
|
||||
header('Set-Cookie: ' . $cookie->__toString(), false);
|
||||
|
||||
return (string)$loginProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLoginProviderIdentifier()
|
||||
{
|
||||
return $this->loginProviderIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns LanguageService
|
||||
*
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUserAuthentication(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of DocumentTemplate
|
||||
*
|
||||
* @return DocumentTemplate
|
||||
*/
|
||||
protected function getDocumentTemplate(): DocumentTemplate
|
||||
{
|
||||
return $GLOBALS['TBE_TEMPLATE'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller;
|
||||
|
||||
/*
|
||||
* 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\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Template\DocumentTemplate;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Page\PageRenderer;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Script Class, putting the frameset together.
|
||||
* @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0. All logic is moved into LoginController.
|
||||
*/
|
||||
class LoginFramesetController
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $content;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
trigger_error(__CLASS__ . ' will be removed in TYPO3 v10.0. Request "index.php?loginRefresh=1" directly to work without the frameset.', E_USER_DEPRECATED);
|
||||
$GLOBALS['SOBE'] = $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function mainAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->createFrameset();
|
||||
return new HtmlResponse($this->content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function.
|
||||
* Creates the header code and the frameset for the two frames.
|
||||
*/
|
||||
public function main()
|
||||
{
|
||||
$this->createFrameset();
|
||||
}
|
||||
/**
|
||||
* Main function.
|
||||
* Creates the header code and the frameset for the two frames.
|
||||
*/
|
||||
protected function createFrameset(): void
|
||||
{
|
||||
$title = 'TYPO3 Re-Login (' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . ')';
|
||||
$this->getDocumentTemplate()->startPage($title);
|
||||
/** @var UriBuilder $uriBuilder */
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
// Create the frameset for the window
|
||||
$this->content = $this->getPageRenderer()->render(PageRenderer::PART_HEADER) . '
|
||||
<frameset rows="*,1">
|
||||
<frame name="login" src="index.php?loginRefresh=1" marginwidth="0" marginheight="0" scrolling="no" noresize="noresize" />
|
||||
<frame name="dummy" src="' . htmlspecialchars((string)$uriBuilder->buildUriFromRoute('dummy')) . '" marginwidth="0" marginheight="0" scrolling="auto" noresize="noresize" />
|
||||
</frameset>
|
||||
</html>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of DocumentTemplate
|
||||
*
|
||||
* @return DocumentTemplate
|
||||
*/
|
||||
protected function getDocumentTemplate(): DocumentTemplate
|
||||
{
|
||||
return $GLOBALS['TBE_TEMPLATE'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PageRenderer
|
||||
*/
|
||||
protected function getPageRenderer(): PageRenderer
|
||||
{
|
||||
return GeneralUtility::makeInstance(PageRenderer::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller;
|
||||
|
||||
/*
|
||||
* 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\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
|
||||
use TYPO3\CMS\Core\Http\RedirectResponse;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Script Class for logging a user out.
|
||||
* Does not display any content, just calls the logout-function for the current user and then makes a redirect.
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class LogoutController
|
||||
{
|
||||
/**
|
||||
* 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
|
||||
* This will be split up in an abstract controller once proper routing/dispatcher is in place.
|
||||
*
|
||||
* @param ServerRequestInterface $request the current request
|
||||
* @return ResponseInterface the response with the content
|
||||
*/
|
||||
public function logoutAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->processLogout();
|
||||
|
||||
$redirectUrl = $request->getParsedBody()['redirect'] ?? $request->getQueryParams()['redirect'];
|
||||
$redirectUrl = GeneralUtility::sanitizeLocalUrl($redirectUrl);
|
||||
if (empty($redirectUrl)) {
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
$redirectUrl = (string)$uriBuilder->buildUriFromRoute('login', [], $uriBuilder::ABSOLUTE_URL);
|
||||
}
|
||||
return new RedirectResponse(GeneralUtility::locationHeaderUrl($redirectUrl), 303);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the logout processing
|
||||
*
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function logout()
|
||||
{
|
||||
trigger_error('LogoutController->logout() will be replaced by protected method processLogout() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
|
||||
$this->processLogout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the logout processing
|
||||
*/
|
||||
protected function processLogout(): void
|
||||
{
|
||||
if (empty($this->getBackendUser()->user['username'])) {
|
||||
return;
|
||||
}
|
||||
// Logout written to log
|
||||
$this->getBackendUser()->writelog(255, 2, 0, 1, 'User %s logged out from TYPO3 Backend', [$this->getBackendUser()->user['username']]);
|
||||
/** @var \TYPO3\CMS\Core\FormProtection\BackendFormProtection $backendFormProtection */
|
||||
$backendFormProtection = FormProtectionFactory::get();
|
||||
$backendFormProtection->removeSessionTokenFromRegistry();
|
||||
$this->getBackendUser()->logoff();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current BE user.
|
||||
*
|
||||
* @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,941 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller;
|
||||
|
||||
/*
|
||||
* 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\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplate;
|
||||
use TYPO3\CMS\Backend\Tree\View\NewRecordPageTreeView;
|
||||
use TYPO3\CMS\Backend\Tree\View\PagePositionMap;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Compatibility\PublicPropertyDeprecationTrait;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Http\RedirectResponse;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
||||
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\HttpUtility;
|
||||
use TYPO3\CMS\Core\Utility\PathUtility;
|
||||
use TYPO3\CMS\Frontend\Page\PageRepository;
|
||||
|
||||
/**
|
||||
* Script class for 'db_new'
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class NewRecordController
|
||||
{
|
||||
use PublicPropertyDeprecationTrait;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $deprecatedPublicProperties = [
|
||||
'pageinfo' => 'Using $pageinfo of class NewRecordController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'pidInfo' => 'Using $pidInfo of class NewRecordController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'newPagesInto' => 'Using $newPagesInto of class NewRecordController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'newContentInto' => 'Using $newContentInto of class NewRecordController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'newPagesAfter' => 'Using $newPagesAfter of class NewRecordController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'web_list_modTSconfig' => 'Using $web_list_modTSconfig of class NewRecordController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'allowedNewTables' => 'Using $allowedNewTables of class NewRecordController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'deniedNewTables' => 'Using $deniedNewTables of class NewRecordController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'web_list_modTSconfig_pid' => 'Using $web_list_modTSconfig_pid of class NewRecordController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'allowedNewTables_pid' => 'Using $allowedNewTables_pid of class NewRecordController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'deniedNewTables_pid' => 'Using $deniedNewTables_pid of class NewRecordController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'code' => 'Using $code of class NewRecordController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'R_URI' => 'Using $R_URI of class NewRecordController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'returnUrl' => 'Using $returnUrl of class NewRecordController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'pagesOnly' => 'Using $pagesOnly of class NewRecordController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'perms_clause' => 'Using $perms_clause of class NewRecordController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'content' => 'Using $content of class NewRecordController from outside is discouraged as this variable is only used for internal storage.',
|
||||
'tRows' => 'Using $tRows of class NewRecordController from outside is discouraged as this variable is only used for internal storage.',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $pageinfo = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $pidInfo = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $newRecordSortList;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $newPagesInto;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $newContentInto;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $newPagesAfter;
|
||||
|
||||
/**
|
||||
* Determines, whether "Select Position" for new page should be shown
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $newPagesSelectPosition = true;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $web_list_modTSconfig;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $allowedNewTables;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $deniedNewTables;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $web_list_modTSconfig_pid;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $allowedNewTables_pid;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $deniedNewTables_pid;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $code;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $R_URI;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*
|
||||
* @see \TYPO3\CMS\Backend\Tree\View\NewRecordPageTreeView::expandNext()
|
||||
* @internal
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $returnUrl;
|
||||
|
||||
/**
|
||||
* pagesOnly flag.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $pagesOnly;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $perms_clause;
|
||||
|
||||
/**
|
||||
* Accumulated HTML output
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $content;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $tRows;
|
||||
|
||||
/**
|
||||
* 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');
|
||||
|
||||
// @see \TYPO3\CMS\Backend\Tree\View\NewRecordPageTreeView::expandNext()
|
||||
$GLOBALS['SOBE'] = $this;
|
||||
|
||||
// @deprecated since TYPO3 v9, will be moved out of __construct() in TYPO3 v10.0
|
||||
$this->init($GLOBALS['TYPO3_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
|
||||
{
|
||||
$response = $this->renderContent($request);
|
||||
|
||||
if (empty($response)) {
|
||||
$response = new HtmlResponse($this->moduleTemplate->renderContent());
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main processing, creating the list of new record tables to select from
|
||||
*
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function main()
|
||||
{
|
||||
trigger_error('NewRecordController->main() will be replaced by protected method renderContent() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
|
||||
|
||||
$response = $this->renderContent($GLOBALS['TYPO3_REQUEST']);
|
||||
|
||||
if ($response instanceof RedirectResponse) {
|
||||
HttpUtility::redirect($response->getHeaders()['location'][0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the position map for pages wizard
|
||||
*
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function pagesOnly()
|
||||
{
|
||||
trigger_error('NewRecordController->pagesOnly() will be replaced by protected method renderPositionTree() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
|
||||
$this->renderPositionTree();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a regular new element (pages and records)
|
||||
*
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function regularNew()
|
||||
{
|
||||
trigger_error('NewRecordController->regularNew() will be replaced by protected method renderNewRecordControls() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
|
||||
$this->renderNewRecordControls($GLOBALS['TYPO3_REQUEST']);
|
||||
}
|
||||
|
||||
/**
|
||||
* User array sort function used by renderNewRecordControls
|
||||
*
|
||||
* @param string $a First array element for compare
|
||||
* @param string $b First array element for compare
|
||||
* @return int -1 for lower, 0 for equal, 1 for greater
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function sortNewRecordsByConfig($a, $b)
|
||||
{
|
||||
trigger_error('NewRecordController->sortNewRecordsByConfig() will be replaced by protected method sortTableRows() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
|
||||
return $this->sortTableRows($a, $b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Links the string $code to a create-new form for a record in $table created on page $pid
|
||||
*
|
||||
* @param string $linkText Link text
|
||||
* @param string $table Table name (in which to create new record)
|
||||
* @param int $pid PID value for the "&edit['.$table.']['.$pid.']=new" command (positive/negative)
|
||||
* @param bool $addContentTable If $addContentTable is set, then a new tt_content record is created together with pages
|
||||
* @return string The link.
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function linkWrap($linkText, $table, $pid, $addContentTable = false)
|
||||
{
|
||||
trigger_error('NewRecordController->linkWrap() will be replaced by protected method renderLink() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
|
||||
return $this->renderLink($linkText, $table, $pid, $addContentTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns TRUE if the tablename $checkTable is allowed to be created on the page with record $pid_row
|
||||
*
|
||||
* @param array $pid_row Record for parent page.
|
||||
* @param string $checkTable Table name to check
|
||||
* @return bool Returns TRUE if the tablename $checkTable is allowed to be created on the page with record $pid_row
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function isTableAllowedForThisPage($pid_row, $checkTable)
|
||||
{
|
||||
trigger_error('NewRecordController->isTableAllowedForThisPage() will be replaced by protected method isTableAllowedOnPage() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
|
||||
return $this->isTableAllowedOnPage($checkTable, $pid_row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns TRUE if:
|
||||
* - $allowedNewTables and $deniedNewTables are empty
|
||||
* - the table is not found in $deniedNewTables and $allowedNewTables is not set or the $table tablename is found in
|
||||
* $allowedNewTables
|
||||
*
|
||||
* If $table tablename is found in $allowedNewTables and $deniedNewTables, $deniedNewTables
|
||||
* has priority over $allowedNewTables.
|
||||
*
|
||||
* @param string $table Table name to test if in allowedTables
|
||||
* @param array $allowedNewTables Array of new tables that are allowed.
|
||||
* @param array $deniedNewTables Array of new tables that are not allowed.
|
||||
* @return bool Returns TRUE if a link for creating new records should be displayed for $table
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function showNewRecLink($table, array $allowedNewTables = [], array $deniedNewTables = [])
|
||||
{
|
||||
trigger_error('NewRecordController->showNewRecLink() will be replaced by protected method isRecordCreationAllowedForTable() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
|
||||
return $this->isRecordCreationAllowedForTable($table, $allowedNewTables, $deniedNewTables);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor function for the class
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
*/
|
||||
protected function init(ServerRequestInterface $request): void
|
||||
{
|
||||
$beUser = $this->getBackendUserAuthentication();
|
||||
// Page-selection permission clause (reading)
|
||||
$this->perms_clause = $beUser->getPagePermsClause(Permission::PAGE_SHOW);
|
||||
// This will hide records from display - it has nothing to do with user rights!!
|
||||
$pidList = $beUser->getTSConfig()['options.']['hideRecords.']['pages'] ?? '';
|
||||
if (!empty($pidList)) {
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('pages');
|
||||
$this->perms_clause .= ' AND ' . $queryBuilder->expr()->notIn(
|
||||
'pages.uid',
|
||||
GeneralUtility::intExplode(',', $pidList)
|
||||
);
|
||||
}
|
||||
// Setting GPvars:
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$queryParams = $request->getQueryParams();
|
||||
// The page id to operate from
|
||||
$this->id = (int)($parsedBody['id'] ?? $queryParams['id'] ?? 0);
|
||||
$this->returnUrl = GeneralUtility::sanitizeLocalUrl($parsedBody['returnUrl'] ?? $queryParams['returnUrl'] ?? '');
|
||||
$this->pagesOnly = $parsedBody['pagesOnly'] ?? $queryParams['pagesOnly'] ?? null;
|
||||
// Setting up the context sensitive menu:
|
||||
$this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
|
||||
$this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/Tooltip');
|
||||
$this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/PageActions');
|
||||
// Creating content
|
||||
$this->content = '';
|
||||
$this->content .= '<h1>'
|
||||
. $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:db_new.php.pagetitle')
|
||||
. '</h1>';
|
||||
// Id a positive id is supplied, ask for the page record with permission information contained:
|
||||
if ($this->id > 0) {
|
||||
$this->pageinfo = BackendUtility::readPageAccess($this->id, $this->perms_clause);
|
||||
}
|
||||
// If a page-record was returned, the user had read-access to the page.
|
||||
if ($this->pageinfo['uid']) {
|
||||
// Get record of parent page
|
||||
$this->pidInfo = BackendUtility::getRecord('pages', $this->pageinfo['pid']) ?: [];
|
||||
// Checking the permissions for the user with regard to the parent page: Can he create new pages, new
|
||||
// content record, new page after?
|
||||
if ($beUser->doesUserHaveAccess($this->pageinfo, 8)) {
|
||||
$this->newPagesInto = 1;
|
||||
}
|
||||
if ($beUser->doesUserHaveAccess($this->pageinfo, 16)) {
|
||||
$this->newContentInto = 1;
|
||||
}
|
||||
if (($beUser->isAdmin() || !empty($this->pidInfo)) && $beUser->doesUserHaveAccess($this->pidInfo, 8)) {
|
||||
$this->newPagesAfter = 1;
|
||||
}
|
||||
} elseif ($beUser->isAdmin()) {
|
||||
// Admins can do it all
|
||||
$this->newPagesInto = 1;
|
||||
$this->newContentInto = 1;
|
||||
$this->newPagesAfter = 0;
|
||||
} else {
|
||||
// People with no permission can do nothing
|
||||
$this->newPagesInto = 0;
|
||||
$this->newContentInto = 0;
|
||||
$this->newPagesAfter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main processing, creating the list of new record tables to select from
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface|null
|
||||
*/
|
||||
protected function renderContent(ServerRequestInterface $request): ?ResponseInterface
|
||||
{
|
||||
// If there was a page - or if the user is admin (admins has access to the root) we proceed:
|
||||
if (!empty($this->pageinfo['uid']) || $this->getBackendUserAuthentication()->isAdmin()) {
|
||||
if (empty($this->pageinfo)) {
|
||||
// Explicitly pass an empty array to the docHeader
|
||||
$this->moduleTemplate->getDocHeaderComponent()->setMetaInformation([]);
|
||||
} else {
|
||||
$this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($this->pageinfo);
|
||||
}
|
||||
// Acquiring TSconfig for this module/current page:
|
||||
$this->web_list_modTSconfig = BackendUtility::getPagesTSconfig($this->pageinfo['uid'])['mod.']['web_list.'] ?? [];
|
||||
$this->allowedNewTables = GeneralUtility::trimExplode(',', $this->web_list_modTSconfig['allowedNewTables'] ?? '', true);
|
||||
$this->deniedNewTables = GeneralUtility::trimExplode(',', $this->web_list_modTSconfig['deniedNewTables'] ?? '', true);
|
||||
// Acquiring TSconfig for this module/parent page:
|
||||
$this->web_list_modTSconfig_pid = BackendUtility::getPagesTSconfig($this->pageinfo['pid'])['mod.']['web_list.'] ?? [];
|
||||
$this->allowedNewTables_pid = GeneralUtility::trimExplode(',', $this->web_list_modTSconfig_pid['allowedNewTables'] ?? '', true);
|
||||
$this->deniedNewTables_pid = GeneralUtility::trimExplode(',', $this->web_list_modTSconfig_pid['deniedNewTables'] ?? '', true);
|
||||
// More init:
|
||||
if (!$this->isRecordCreationAllowedForTable('pages')) {
|
||||
$this->newPagesInto = 0;
|
||||
}
|
||||
if (!$this->isRecordCreationAllowedForTable('pages', $this->allowedNewTables_pid, $this->deniedNewTables_pid)) {
|
||||
$this->newPagesAfter = 0;
|
||||
}
|
||||
// Set header-HTML and return_url
|
||||
if (is_array($this->pageinfo) && $this->pageinfo['uid']) {
|
||||
$title = strip_tags($this->pageinfo[$GLOBALS['TCA']['pages']['ctrl']['label']]);
|
||||
} else {
|
||||
$title = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'];
|
||||
}
|
||||
$this->moduleTemplate->setTitle($title);
|
||||
// GENERATE the HTML-output depending on mode (pagesOnly is the page wizard)
|
||||
// Regular new element:
|
||||
if (!$this->pagesOnly) {
|
||||
$this->renderNewRecordControls($request);
|
||||
} elseif ($this->isRecordCreationAllowedForTable('pages')) {
|
||||
// Pages only wizard
|
||||
$response = $this->renderPositionTree();
|
||||
|
||||
if (!empty($response)) {
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
// Add all the content to an output section
|
||||
$this->content .= '<div>' . $this->code . '</div>';
|
||||
// Setting up the buttons and markers for docheader
|
||||
$this->getButtons();
|
||||
// Build the <body> for the module
|
||||
$this->moduleTemplate->setContent($this->content);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the panel of buttons for submitting the form or otherwise perform operations.
|
||||
*/
|
||||
protected function getButtons(): void
|
||||
{
|
||||
$lang = $this->getLanguageService();
|
||||
$buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
|
||||
// Regular new element:
|
||||
if (!$this->pagesOnly) {
|
||||
// New page
|
||||
if ($this->isRecordCreationAllowedForTable('pages')) {
|
||||
$newPageButton = $buttonBar->makeLinkButton()
|
||||
->setHref(GeneralUtility::linkThisScript(['pagesOnly' => '1']))
|
||||
->setTitle($lang->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:newPage'))
|
||||
->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-page-new', Icon::SIZE_SMALL));
|
||||
$buttonBar->addButton($newPageButton, ButtonBar::BUTTON_POSITION_LEFT, 20);
|
||||
}
|
||||
// CSH
|
||||
$cshButton = $buttonBar->makeHelpButton()->setModuleName('xMOD_csh_corebe')->setFieldName('new_regular');
|
||||
$buttonBar->addButton($cshButton);
|
||||
} elseif ($this->isRecordCreationAllowedForTable('pages')) {
|
||||
// Pages only wizard
|
||||
// CSH
|
||||
$buttons['csh'] = BackendUtility::cshItem('xMOD_csh_corebe', 'new_pages');
|
||||
$cshButton = $buttonBar->makeHelpButton()->setModuleName('xMOD_csh_corebe')->setFieldName('new_pages');
|
||||
$buttonBar->addButton($cshButton);
|
||||
}
|
||||
// Back
|
||||
if ($this->returnUrl) {
|
||||
$returnButton = $buttonBar->makeLinkButton()
|
||||
->setHref($this->returnUrl)
|
||||
->setTitle($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.goBack'))
|
||||
->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-view-go-back', Icon::SIZE_SMALL));
|
||||
$buttonBar->addButton($returnButton, ButtonBar::BUTTON_POSITION_LEFT, 10);
|
||||
}
|
||||
|
||||
if (is_array($this->pageinfo) && $this->pageinfo['uid']) {
|
||||
// View
|
||||
$pagesTSconfig = BackendUtility::getPagesTSconfig($this->pageinfo['uid']);
|
||||
if (isset($pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'])) {
|
||||
$excludeDokTypes = GeneralUtility::intExplode(
|
||||
',',
|
||||
$pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'],
|
||||
true
|
||||
);
|
||||
} else {
|
||||
// exclude sysfolders and recycler by default
|
||||
$excludeDokTypes = [
|
||||
PageRepository::DOKTYPE_RECYCLER,
|
||||
PageRepository::DOKTYPE_SYSFOLDER,
|
||||
PageRepository::DOKTYPE_SPACER
|
||||
];
|
||||
}
|
||||
if (!in_array((int)$this->pageinfo['doktype'], $excludeDokTypes, true)) {
|
||||
$viewButton = $buttonBar->makeLinkButton()
|
||||
->setHref('#')
|
||||
->setOnClick(BackendUtility::viewOnClick(
|
||||
$this->pageinfo['uid'],
|
||||
'',
|
||||
BackendUtility::BEgetRootLine($this->pageinfo['uid'])
|
||||
))
|
||||
->setTitle($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.showPage'))
|
||||
->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
|
||||
'actions-view-page',
|
||||
Icon::SIZE_SMALL
|
||||
));
|
||||
$buttonBar->addButton($viewButton, ButtonBar::BUTTON_POSITION_LEFT, 30);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the position map for pages wizard
|
||||
*
|
||||
* @return ResponseInterface|null
|
||||
*/
|
||||
protected function renderPositionTree(): ?ResponseInterface
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('pages');
|
||||
$queryBuilder->getRestrictions()
|
||||
->removeAll()
|
||||
->add(GeneralUtility::makeInstance(DeletedRestriction::class));
|
||||
$numberOfPages = $queryBuilder
|
||||
->count('*')
|
||||
->from('pages')
|
||||
->execute()
|
||||
->fetchColumn(0);
|
||||
|
||||
if ($numberOfPages > 0) {
|
||||
$this->code .= '<h3>' . htmlspecialchars($this->getLanguageService()->getLL('selectPosition')) . ':</h3>';
|
||||
/** @var \TYPO3\CMS\Backend\Tree\View\PagePositionMap $positionMap */
|
||||
$positionMap = GeneralUtility::makeInstance(PagePositionMap::class, NewRecordPageTreeView::class);
|
||||
$this->code .= $positionMap->positionTree(
|
||||
$this->id,
|
||||
$this->pageinfo,
|
||||
$this->perms_clause,
|
||||
$this->returnUrl
|
||||
);
|
||||
} else {
|
||||
/** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
|
||||
$uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
|
||||
// No pages yet, no need to prompt for position, redirect to page creation.
|
||||
$urlParameters = [
|
||||
'edit' => [
|
||||
'pages' => [
|
||||
0 => 'new'
|
||||
]
|
||||
],
|
||||
'returnNewPageId' => 1,
|
||||
'returnUrl' => (string)$uriBuilder->buildUriFromRoute('db_new', ['id' => $this->id, 'pagesOnly' => '1'])
|
||||
];
|
||||
$url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
|
||||
@ob_end_clean();
|
||||
|
||||
return new RedirectResponse($url);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render controls for creating a regular new element (pages or records)
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
*/
|
||||
protected function renderNewRecordControls(ServerRequestInterface $request): void
|
||||
{
|
||||
$lang = $this->getLanguageService();
|
||||
// Initialize array for accumulating table rows:
|
||||
$this->tRows = [];
|
||||
// Get TSconfig for current page
|
||||
$pageTS = BackendUtility::getPagesTSconfig($this->id);
|
||||
// Finish initializing new pages options with TSconfig
|
||||
// Each new page option may be hidden by TSconfig
|
||||
// Enabled option for the position of a new page
|
||||
$this->newPagesSelectPosition = !empty($pageTS['mod.']['wizards.']['newRecord.']['pages.']['show.']['pageSelectPosition']);
|
||||
// Pseudo-boolean (0/1) for backward compatibility
|
||||
$displayNewPagesIntoLink = $this->newPagesInto && !empty($pageTS['mod.']['wizards.']['newRecord.']['pages.']['show.']['pageInside']);
|
||||
$displayNewPagesAfterLink = $this->newPagesAfter && !empty($pageTS['mod.']['wizards.']['newRecord.']['pages.']['show.']['pageAfter']);
|
||||
// Slight spacer from header:
|
||||
$this->code .= '';
|
||||
// New Page
|
||||
$table = 'pages';
|
||||
$v = $GLOBALS['TCA'][$table];
|
||||
$pageIcon = $this->moduleTemplate->getIconFactory()->getIconForRecord(
|
||||
$table,
|
||||
[],
|
||||
Icon::SIZE_SMALL
|
||||
)->render();
|
||||
$newPageIcon = $this->moduleTemplate->getIconFactory()->getIcon('actions-page-new', Icon::SIZE_SMALL)->render();
|
||||
$rowContent = '';
|
||||
// New pages INSIDE this pages
|
||||
$newPageLinks = [];
|
||||
if ($displayNewPagesIntoLink
|
||||
&& $this->isTableAllowedOnPage('pages', $this->pageinfo)
|
||||
&& $this->getBackendUserAuthentication()->check('tables_modify', 'pages')
|
||||
&& $this->getBackendUserAuthentication()->workspaceCreateNewRecord(($this->pageinfo['_ORIG_uid'] ?: $this->id), 'pages')
|
||||
) {
|
||||
// Create link to new page inside:
|
||||
$recordIcon = $this->moduleTemplate->getIconFactory()->getIconForRecord($table, [], Icon::SIZE_SMALL)->render();
|
||||
$newPageLinks[] = $this->renderLink(
|
||||
$recordIcon . htmlspecialchars($lang->sL($v['ctrl']['title'])) . ' (' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:db_new.php.inside')) . ')',
|
||||
$table,
|
||||
$this->id
|
||||
);
|
||||
}
|
||||
// New pages AFTER this pages
|
||||
if ($displayNewPagesAfterLink
|
||||
&& $this->isTableAllowedOnPage('pages', $this->pidInfo)
|
||||
&& $this->getBackendUserAuthentication()->check('tables_modify', 'pages')
|
||||
&& $this->getBackendUserAuthentication()->workspaceCreateNewRecord($this->pidInfo['uid'], 'pages')
|
||||
) {
|
||||
$newPageLinks[] = $this->renderLink(
|
||||
$pageIcon . htmlspecialchars($lang->sL($v['ctrl']['title'])) . ' (' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:db_new.php.after')) . ')',
|
||||
'pages',
|
||||
-$this->id
|
||||
);
|
||||
}
|
||||
// New pages at selection position
|
||||
if ($this->newPagesSelectPosition && $this->isRecordCreationAllowedForTable('pages')) {
|
||||
// Link to page-wizard:
|
||||
$newPageLinks[] = '<a href="' . htmlspecialchars(GeneralUtility::linkThisScript(['pagesOnly' => 1])) . '">' . $pageIcon . htmlspecialchars($lang->getLL('pageSelectPosition')) . '</a>';
|
||||
}
|
||||
// Assemble all new page links
|
||||
$numPageLinks = count($newPageLinks);
|
||||
for ($i = 0; $i < $numPageLinks; $i++) {
|
||||
$rowContent .= '<li>' . $newPageLinks[$i] . '</li>';
|
||||
}
|
||||
if ($this->isRecordCreationAllowedForTable('pages')) {
|
||||
$rowContent = '<ul class="list-tree"><li>' . $newPageIcon . '<strong>' .
|
||||
$lang->getLL('createNewPage') . '</strong><ul>' . $rowContent . '</ul></li>';
|
||||
} else {
|
||||
$rowContent = '<ul class="list-tree"><li><ul>' . $rowContent . '</li></ul>';
|
||||
}
|
||||
/** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
// Compile table row
|
||||
$startRows = [$rowContent];
|
||||
$iconFile = [];
|
||||
// New tables (but not pages) INSIDE this pages
|
||||
$isAdmin = $this->getBackendUserAuthentication()->isAdmin();
|
||||
$newContentIcon = $this->moduleTemplate->getIconFactory()->getIcon('actions-document-new', Icon::SIZE_SMALL)->render();
|
||||
if ($this->newContentInto) {
|
||||
if (is_array($GLOBALS['TCA'])) {
|
||||
$groupName = '';
|
||||
foreach ($GLOBALS['TCA'] as $table => $v) {
|
||||
$rootLevelConfiguration = isset($v['ctrl']['rootLevel']) ? (int)$v['ctrl']['rootLevel'] : 0;
|
||||
if ($table !== 'pages'
|
||||
&& $this->isRecordCreationAllowedForTable($table)
|
||||
&& $this->isTableAllowedOnPage($table, $this->pageinfo)
|
||||
&& $this->getBackendUserAuthentication()->check('tables_modify', $table)
|
||||
&& ($rootLevelConfiguration === -1 || ($this->id xor $rootLevelConfiguration))
|
||||
&& $this->getBackendUserAuthentication()->workspaceCreateNewRecord(($this->pageinfo['_ORIG_uid'] ? $this->pageinfo['_ORIG_uid'] : $this->id), $table)
|
||||
) {
|
||||
$newRecordIcon = $this->moduleTemplate->getIconFactory()->getIconForRecord($table, [], Icon::SIZE_SMALL)->render();
|
||||
$rowContent = '';
|
||||
$thisTitle = '';
|
||||
// Create new link for record:
|
||||
$newLink = $this->renderLink(
|
||||
$newRecordIcon . htmlspecialchars($lang->sL($v['ctrl']['title'])),
|
||||
$table,
|
||||
$this->id
|
||||
);
|
||||
// If the table is 'tt_content', add link to wizard
|
||||
if ($table === 'tt_content') {
|
||||
$groupName = $lang->getLL('createNewContent');
|
||||
$rowContent = $newContentIcon
|
||||
. '<strong>' . $lang->getLL('createNewContent') . '</strong>'
|
||||
. '<ul>';
|
||||
// If mod.newContentElementWizard.override is set, use that extension's wizard instead:
|
||||
$moduleName = BackendUtility::getPagesTSconfig($this->id)['mod.']['newContentElementWizard.']['override']
|
||||
?? 'new_content_element_wizard';
|
||||
/** @var \TYPO3\CMS\Core\Http\NormalizedParams */
|
||||
$normalizedParams = $request->getAttribute('normalizedParams');
|
||||
$url = (string)$uriBuilder->buildUriFromRoute($moduleName, ['id' => $this->id, 'returnUrl' => $normalizedParams->getRequestUri()]);
|
||||
$rowContent .= '<li>' . $newLink . ' ' . BackendUtility::wrapInHelp($table, '') . '</li>'
|
||||
. '<li>'
|
||||
. '<a href="' . htmlspecialchars($url) . '" data-title="' . htmlspecialchars($this->getLanguageService()->getLL('newContentElement')) . '" class="t3js-toggle-new-content-element-wizard disabled">'
|
||||
. $newContentIcon . htmlspecialchars($lang->getLL('clickForWizard'))
|
||||
. '</a>'
|
||||
. '</li>'
|
||||
. '</ul>';
|
||||
} else {
|
||||
// Get the title
|
||||
if ($v['ctrl']['readOnly'] || $v['ctrl']['hideTable'] || $v['ctrl']['is_static']) {
|
||||
continue;
|
||||
}
|
||||
if ($v['ctrl']['adminOnly'] && !$isAdmin) {
|
||||
continue;
|
||||
}
|
||||
$nameParts = explode('_', $table);
|
||||
$thisTitle = '';
|
||||
$_EXTKEY = '';
|
||||
if ($nameParts[0] === 'tx' || $nameParts[0] === 'tt') {
|
||||
// Try to extract extension name
|
||||
$title = (string)($v['ctrl']['title'] ?? '');
|
||||
if (strpos($title, 'LLL:EXT:') === 0) {
|
||||
$_EXTKEY = substr($title, 8);
|
||||
$_EXTKEY = substr($_EXTKEY, 0, strpos($_EXTKEY, '/'));
|
||||
if ($_EXTKEY !== '') {
|
||||
// First try to get localisation of extension title
|
||||
$temp = explode(':', substr($title, 9 + strlen($_EXTKEY)));
|
||||
$langFile = $temp[0];
|
||||
$thisTitle = $lang->sL('LLL:EXT:' . $_EXTKEY . '/' . $langFile . ':extension.title');
|
||||
// If no localisation available, read title from ext_emconf.php
|
||||
$extPath = ExtensionManagementUtility::extPath($_EXTKEY);
|
||||
$extEmConfFile = $extPath . 'ext_emconf.php';
|
||||
if (!$thisTitle && is_file($extEmConfFile)) {
|
||||
$EM_CONF = [];
|
||||
include $extEmConfFile;
|
||||
$thisTitle = $EM_CONF[$_EXTKEY]['title'];
|
||||
}
|
||||
$iconFile[$_EXTKEY] = '<img src="' . PathUtility::getAbsoluteWebPath(ExtensionManagementUtility::getExtensionIcon($extPath, true)) . '" ' . 'width="16" height="16" ' . 'alt="' . $thisTitle . '" />';
|
||||
}
|
||||
}
|
||||
if (empty($thisTitle)) {
|
||||
$_EXTKEY = $nameParts[1];
|
||||
$thisTitle = $nameParts[1];
|
||||
$iconFile[$_EXTKEY] = '';
|
||||
}
|
||||
} else {
|
||||
$_EXTKEY = 'system';
|
||||
$thisTitle = $lang->getLL('system_records');
|
||||
$iconFile['system'] = $this->moduleTemplate->getIconFactory()->getIcon('apps-pagetree-root', Icon::SIZE_SMALL)->render();
|
||||
}
|
||||
|
||||
if ($groupName === '' || $groupName !== $_EXTKEY) {
|
||||
$groupName = empty($v['ctrl']['groupName']) ? $_EXTKEY : $v['ctrl']['groupName'];
|
||||
}
|
||||
$rowContent .= $newLink;
|
||||
}
|
||||
// Compile table row:
|
||||
if ($table === 'tt_content') {
|
||||
$startRows[] = '<li>' . $rowContent . '</li>';
|
||||
} else {
|
||||
$this->tRows[$groupName]['title'] = $thisTitle;
|
||||
$this->tRows[$groupName]['html'][] = $rowContent;
|
||||
$this->tRows[$groupName]['table'][] = $table;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// User sort
|
||||
if (isset($pageTS['mod.']['wizards.']['newRecord.']['order'])) {
|
||||
$this->newRecordSortList = GeneralUtility::trimExplode(',', $pageTS['mod.']['wizards.']['newRecord.']['order'], true);
|
||||
}
|
||||
uksort($this->tRows, [$this, 'sortTableRows']);
|
||||
// Compile table row:
|
||||
$finalRows = [];
|
||||
$finalRows[] = implode('', $startRows);
|
||||
foreach ($this->tRows as $key => $value) {
|
||||
$row = '<li>' . $iconFile[$key] . ' <strong>' . $value['title'] . '</strong><ul>';
|
||||
foreach ($value['html'] as $recordKey => $record) {
|
||||
$row .= '<li>' . $record . ' ' . BackendUtility::wrapInHelp($value['table'][$recordKey], '') . '</li>';
|
||||
}
|
||||
$row .= '</ul></li>';
|
||||
$finalRows[] = $row;
|
||||
}
|
||||
|
||||
$finalRows[] = '</ul>';
|
||||
// Make table:
|
||||
$this->code .= implode('', $finalRows);
|
||||
}
|
||||
|
||||
/**
|
||||
* User array sort function used by renderNewRecordControls
|
||||
*
|
||||
* @param string $a First array element for compare
|
||||
* @param string $b First array element for compare
|
||||
* @return int -1 for lower, 0 for equal, 1 for greater
|
||||
*/
|
||||
protected function sortTableRows(string $a, string $b): int
|
||||
{
|
||||
if (!empty($this->newRecordSortList)) {
|
||||
if (in_array($a, $this->newRecordSortList) && in_array($b, $this->newRecordSortList)) {
|
||||
// Both are in the list, return relative to position in array
|
||||
$sub = array_search($a, $this->newRecordSortList) - array_search($b, $this->newRecordSortList);
|
||||
$ret = ($sub < 0 ? -1 : $sub == 0) ? 0 : 1;
|
||||
} elseif (in_array($a, $this->newRecordSortList)) {
|
||||
// First element is in array, put to top
|
||||
$ret = -1;
|
||||
} elseif (in_array($b, $this->newRecordSortList)) {
|
||||
// Second element is in array, put first to bottom
|
||||
$ret = 1;
|
||||
} else {
|
||||
// No element is in array, return alphabetic order
|
||||
$ret = strnatcasecmp($this->tRows[$a]['title'], $this->tRows[$b]['title']);
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
// Return alphabetic order
|
||||
return strnatcasecmp($this->tRows[$a]['title'], $this->tRows[$b]['title']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Links the string $code to a create-new form for a record in $table created on page $pid
|
||||
*
|
||||
* @param string $linkText Link text
|
||||
* @param string $table Table name (in which to create new record)
|
||||
* @param int $pid PID value for the "&edit['.$table.']['.$pid.']=new" command (positive/negative)
|
||||
* @param bool $addContentTable If $addContentTable is set, then a new tt_content record is created together with pages
|
||||
* @return string The link.
|
||||
*/
|
||||
protected function renderLink(string $linkText, string $table, int $pid, bool $addContentTable = false): string
|
||||
{
|
||||
$urlParameters = [
|
||||
'edit' => [
|
||||
$table => [
|
||||
$pid => 'new'
|
||||
]
|
||||
],
|
||||
'returnUrl' => $this->returnUrl
|
||||
];
|
||||
|
||||
if ($table === 'pages' && $addContentTable) {
|
||||
$urlParameters['tt_content']['prev'] = 'new';
|
||||
$urlParameters['returnNewPageId'] = 1;
|
||||
}
|
||||
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
$url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
|
||||
|
||||
return '<a href="' . htmlspecialchars($url) . '">' . $linkText . '</a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns TRUE if the tablename $checkTable is allowed to be created on the page with record $pid_row
|
||||
*
|
||||
* @param string $table Table name to check
|
||||
* @param array $page Potential parent page
|
||||
* @return bool Returns TRUE if the tablename $table is allowed to be created on the $page
|
||||
*/
|
||||
protected function isTableAllowedOnPage(string $table, array $page): bool
|
||||
{
|
||||
if (empty($page)) {
|
||||
return $this->getBackendUserAuthentication()->isAdmin();
|
||||
}
|
||||
// be_users and be_groups may not be created anywhere but in the root.
|
||||
if ($table === 'be_users' || $table === 'be_groups') {
|
||||
return false;
|
||||
}
|
||||
// Checking doktype:
|
||||
$doktype = (int)$page['doktype'];
|
||||
if (!($allowedTableList = $GLOBALS['PAGES_TYPES'][$doktype]['allowedTables'])) {
|
||||
$allowedTableList = $GLOBALS['PAGES_TYPES']['default']['allowedTables'];
|
||||
}
|
||||
// If all tables or the table is listed as an allowed type, return TRUE
|
||||
if (strstr($allowedTableList, '*') || GeneralUtility::inList($allowedTableList, $table)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the record link should be shown for a table
|
||||
*
|
||||
* Returns TRUE if:
|
||||
* - $allowedNewTables and $deniedNewTables are empty
|
||||
* - the table is not found in $deniedNewTables and $allowedNewTables is not set or the $table tablename is found in
|
||||
* $allowedNewTables
|
||||
*
|
||||
* If $table tablename is found in $allowedNewTables and $deniedNewTables,
|
||||
* $deniedNewTables has priority over $allowedNewTables.
|
||||
*
|
||||
* @param string $table Table name to test if in allowedTables
|
||||
* @param array $allowedNewTables Array of new tables that are allowed.
|
||||
* @param array $deniedNewTables Array of new tables that are not allowed.
|
||||
* @return bool Returns TRUE if a link for creating new records should be displayed for $table
|
||||
*/
|
||||
protected function isRecordCreationAllowedForTable(string $table, array $allowedNewTables = [], array $deniedNewTables = []): bool
|
||||
{
|
||||
if (!$this->getBackendUserAuthentication()->check('tables_modify', $table)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$allowedNewTables = $allowedNewTables ?: $this->allowedNewTables;
|
||||
$deniedNewTables = $deniedNewTables ?: $this->deniedNewTables;
|
||||
// No deny/allow tables are set:
|
||||
if (empty($allowedNewTables) && empty($deniedNewTables)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !in_array($table, $deniedNewTables) && (empty($allowedNewTables) || in_array($table, $allowedNewTables));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if sys_language records are present
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkIfLanguagesExist(): bool
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('sys_language');
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
|
||||
$count = $queryBuilder
|
||||
->count('uid')
|
||||
->from('sys_language')
|
||||
->execute()
|
||||
->fetchColumn(0);
|
||||
return (bool)$count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUserAuthentication(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Controller;
|
||||
|
||||
/*
|
||||
* 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\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Http\RedirectResponse;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageService;
|
||||
use TYPO3\CMS\Core\Resource\File;
|
||||
use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry;
|
||||
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Class OnlineMediaController handles uploading online media
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class OnlineMediaController
|
||||
{
|
||||
/**
|
||||
* AJAX endpoint for storing the URL as a sys_file record
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function createAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$url = $request->getParsedBody()['url'];
|
||||
$targetFolderIdentifier = $request->getParsedBody()['targetFolder'];
|
||||
$allowedExtensions = GeneralUtility::trimExplode(',', $request->getParsedBody()['allowed'] ?: '');
|
||||
|
||||
if (!empty($url)) {
|
||||
$data = [];
|
||||
$file = $this->addMediaFromUrl($url, $targetFolderIdentifier, $allowedExtensions);
|
||||
if ($file !== null) {
|
||||
$data['file'] = $file->getUid();
|
||||
} else {
|
||||
$data['error'] = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:online_media.error.invalid_url');
|
||||
}
|
||||
return new JsonResponse($data);
|
||||
}
|
||||
return new JsonResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process add media request, and redirects to the previous page
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function mainAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$files = $request->getParsedBody()['data'];
|
||||
$redirect = $request->getParsedBody()['redirect'];
|
||||
$newMedia = [];
|
||||
if (isset($files['newMedia'])) {
|
||||
$newMedia = (array)$files['newMedia'];
|
||||
}
|
||||
|
||||
foreach ($newMedia as $media) {
|
||||
if (!empty($media['url']) && !empty($media['target'])) {
|
||||
$allowed = !empty($media['allowed']) ? GeneralUtility::trimExplode(',', $media['allowed']) : [];
|
||||
$file = $this->addMediaFromUrl($media['url'], $media['target'], $allowed);
|
||||
if ($file !== null) {
|
||||
$flashMessage = GeneralUtility::makeInstance(
|
||||
FlashMessage::class,
|
||||
$file->getName(),
|
||||
$this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:online_media.new_media.added'),
|
||||
FlashMessage::OK,
|
||||
true
|
||||
);
|
||||
} else {
|
||||
$flashMessage = GeneralUtility::makeInstance(
|
||||
FlashMessage::class,
|
||||
$this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:online_media.error.invalid_url'),
|
||||
$this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:online_media.error.new_media.failed'),
|
||||
FlashMessage::ERROR,
|
||||
true
|
||||
);
|
||||
}
|
||||
$this->addFlashMessage($flashMessage);
|
||||
if (empty($redirect) && $media['redirect']) {
|
||||
$redirect = $media['redirect'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$redirect = GeneralUtility::sanitizeLocalUrl($redirect);
|
||||
if ($redirect) {
|
||||
return new RedirectResponse($redirect, 303);
|
||||
}
|
||||
|
||||
throw new \RuntimeException('No redirect after uploading a media found, probably a mis-use of the template not sending the proper Return URL.', 1511945040);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param string $targetFolderIdentifier
|
||||
* @param string[] $allowedExtensions
|
||||
* @return File|null
|
||||
*/
|
||||
protected function addMediaFromUrl($url, $targetFolderIdentifier, array $allowedExtensions = [])
|
||||
{
|
||||
$targetFolder = null;
|
||||
if ($targetFolderIdentifier) {
|
||||
try {
|
||||
$targetFolder = ResourceFactory::getInstance()->getFolderObjectFromCombinedIdentifier($targetFolderIdentifier);
|
||||
} catch (\Exception $e) {
|
||||
$targetFolder = null;
|
||||
}
|
||||
}
|
||||
if ($targetFolder === null) {
|
||||
$targetFolder = $this->getBackendUser()->getDefaultUploadFolder();
|
||||
}
|
||||
return OnlineMediaHelperRegistry::getInstance()->transformUrlToFile($url, $targetFolder, $allowedExtensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add flash message to message queue
|
||||
*
|
||||
* @param FlashMessage $flashMessage
|
||||
*/
|
||||
protected function addFlashMessage(FlashMessage $flashMessage)
|
||||
{
|
||||
/** @var FlashMessageService $flashMessageService */
|
||||
$flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
|
||||
|
||||
/** @var \TYPO3\CMS\Core\Messaging\FlashMessageQueue $defaultFlashMessageQueue */
|
||||
$defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
|
||||
$defaultFlashMessageQueue->enqueue($flashMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace TYPO3\CMS\Backend\Controller\Page;
|
||||
|
||||
/*
|
||||
* 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\Configuration\TranslationConfigurationProvider;
|
||||
use TYPO3\CMS\Backend\Domain\Repository\Localization\LocalizationRepository;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Backend\View\BackendLayoutView;
|
||||
use TYPO3\CMS\Core\DataHandling\DataHandler;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Http\Response;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Versioning\VersionState;
|
||||
|
||||
/**
|
||||
* LocalizationController handles the AJAX requests for record localization
|
||||
*
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class LocalizationController
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
const ACTION_COPY = 'copyFromLanguage';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
const ACTION_LOCALIZE = 'localize';
|
||||
|
||||
/**
|
||||
* @var IconFactory
|
||||
*/
|
||||
protected $iconFactory;
|
||||
|
||||
/**
|
||||
* @var LocalizationRepository
|
||||
*/
|
||||
protected $localizationRepository;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
$this->localizationRepository = GeneralUtility::makeInstance(LocalizationRepository::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get used languages in a page
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function getUsedLanguagesInPage(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$params = $request->getQueryParams();
|
||||
if (!isset($params['pageId'], $params['languageId'])) {
|
||||
return new JsonResponse(null, 400);
|
||||
}
|
||||
|
||||
$pageId = (int)$params['pageId'];
|
||||
$languageId = (int)$params['languageId'];
|
||||
|
||||
/** @var TranslationConfigurationProvider $translationProvider */
|
||||
$translationProvider = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
|
||||
$systemLanguages = $translationProvider->getSystemLanguages($pageId);
|
||||
|
||||
$availableLanguages = [];
|
||||
|
||||
// First check whether column has localized records
|
||||
$elementsInColumnCount = $this->localizationRepository->getLocalizedRecordCount($pageId, $languageId);
|
||||
|
||||
if ($elementsInColumnCount === 0) {
|
||||
$fetchedAvailableLanguages = $this->localizationRepository->fetchAvailableLanguages($pageId, $languageId);
|
||||
foreach ($fetchedAvailableLanguages as $language) {
|
||||
if (isset($systemLanguages[$language['sys_language_uid']])) {
|
||||
$availableLanguages[] = $systemLanguages[$language['sys_language_uid']];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$result = $this->localizationRepository->fetchOriginLanguage($pageId, $languageId);
|
||||
$availableLanguages[] = $systemLanguages[$result['sys_language_uid']];
|
||||
}
|
||||
|
||||
// Pre-render all flag icons
|
||||
foreach ($availableLanguages as &$language) {
|
||||
if ($language['flagIcon'] === 'empty-empty') {
|
||||
$language['flagIcon'] = '';
|
||||
} else {
|
||||
$language['flagIcon'] = $this->iconFactory->getIcon($language['flagIcon'], Icon::SIZE_SMALL)->render();
|
||||
}
|
||||
}
|
||||
|
||||
return (new JsonResponse())->setPayload($availableLanguages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a prepared summary of records being translated
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function getRecordLocalizeSummary(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$params = $request->getQueryParams();
|
||||
if (!isset($params['pageId'], $params['destLanguageId'], $params['languageId'])) {
|
||||
return new JsonResponse(null, 400);
|
||||
}
|
||||
|
||||
$pageId = (int)$params['pageId'];
|
||||
$destLanguageId = (int)$params['destLanguageId'];
|
||||
$languageId = (int)$params['languageId'];
|
||||
|
||||
$records = [];
|
||||
$result = $this->localizationRepository->getRecordsToCopyDatabaseResult(
|
||||
$pageId,
|
||||
$destLanguageId,
|
||||
$languageId,
|
||||
'*'
|
||||
);
|
||||
|
||||
while ($row = $result->fetch()) {
|
||||
BackendUtility::workspaceOL('tt_content', $row, -99, true);
|
||||
if (!$row || VersionState::cast($row['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) {
|
||||
continue;
|
||||
}
|
||||
$colPos = $row['colPos'];
|
||||
if (!isset($records[$colPos])) {
|
||||
$records[$colPos] = [];
|
||||
}
|
||||
$records[$colPos][] = [
|
||||
'icon' => $this->iconFactory->getIconForRecord('tt_content', $row, Icon::SIZE_SMALL)->render(),
|
||||
'title' => $row[$GLOBALS['TCA']['tt_content']['ctrl']['label']],
|
||||
'uid' => $row['uid']
|
||||
];
|
||||
}
|
||||
|
||||
return (new JsonResponse())->setPayload([
|
||||
'records' => $records,
|
||||
'columns' => $this->getPageColumns($pageId),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function localizeRecords(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$params = $request->getQueryParams();
|
||||
if (!isset($params['pageId'], $params['srcLanguageId'], $params['destLanguageId'], $params['action'], $params['uidList'])) {
|
||||
return new JsonResponse(null, 400);
|
||||
}
|
||||
|
||||
if ($params['action'] !== static::ACTION_COPY && $params['action'] !== static::ACTION_LOCALIZE) {
|
||||
$response = new Response('php://temp', 400, ['Content-Type' => 'application/json; charset=utf-8']);
|
||||
$response->getBody()->write('Invalid action "' . $params['action'] . '" called.');
|
||||
return $response;
|
||||
}
|
||||
|
||||
// Filter transmitted but invalid uids
|
||||
$params['uidList'] = $this->filterInvalidUids(
|
||||
(int)$params['pageId'],
|
||||
(int)$params['destLanguageId'],
|
||||
(int)$params['srcLanguageId'],
|
||||
$params['uidList']
|
||||
);
|
||||
|
||||
$this->process($params);
|
||||
|
||||
return (new JsonResponse())->setPayload([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all possible UIDs of a page, colPos and language that might be processed and removes invalid UIDs that might
|
||||
* be smuggled in.
|
||||
*
|
||||
* @param int $pageId
|
||||
* @param int $destLanguageId
|
||||
* @param int $srcLanguageId
|
||||
* @param array $transmittedUidList
|
||||
* @return array
|
||||
*/
|
||||
protected function filterInvalidUids(
|
||||
int $pageId,
|
||||
int $destLanguageId,
|
||||
int $srcLanguageId,
|
||||
array $transmittedUidList
|
||||
): array {
|
||||
// Get all valid uids that can be processed
|
||||
$validUidList = $this->localizationRepository->getRecordsToCopyDatabaseResult(
|
||||
$pageId,
|
||||
$destLanguageId,
|
||||
$srcLanguageId,
|
||||
'uid'
|
||||
);
|
||||
|
||||
return array_intersect(array_unique($transmittedUidList), array_column($validUidList->fetchAll(), 'uid'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the localization actions
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
protected function process($params): void
|
||||
{
|
||||
$destLanguageId = (int)$params['destLanguageId'];
|
||||
|
||||
// Build command map
|
||||
$cmd = [
|
||||
'tt_content' => []
|
||||
];
|
||||
|
||||
if (isset($params['uidList']) && is_array($params['uidList'])) {
|
||||
foreach ($params['uidList'] as $currentUid) {
|
||||
if ($params['action'] === static::ACTION_LOCALIZE) {
|
||||
$cmd['tt_content'][$currentUid] = [
|
||||
'localize' => $destLanguageId
|
||||
];
|
||||
} else {
|
||||
$cmd['tt_content'][$currentUid] = [
|
||||
'copyToLanguage' => $destLanguageId,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
|
||||
$dataHandler->start([], $cmd);
|
||||
$dataHandler->process_cmdmap();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $pageId
|
||||
* @return array
|
||||
*/
|
||||
protected function getPageColumns(int $pageId): array
|
||||
{
|
||||
$columns = [];
|
||||
$backendLayoutView = GeneralUtility::makeInstance(BackendLayoutView::class);
|
||||
$backendLayouts = $backendLayoutView->getSelectedBackendLayout($pageId);
|
||||
|
||||
foreach ($backendLayouts['__items'] as $backendLayout) {
|
||||
$columns[(int)$backendLayout[1]] = $backendLayout[0];
|
||||
}
|
||||
|
||||
return [
|
||||
'columns' => $columns,
|
||||
'columnList' => $backendLayouts['__colPosList'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller\Page;
|
||||
|
||||
/*
|
||||
* 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\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
|
||||
use TYPO3\CMS\Core\DataHandling\DataHandler;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
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;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
|
||||
/**
|
||||
* "Create multiple pages" controller
|
||||
*
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class NewMultiplePagesController
|
||||
{
|
||||
/**
|
||||
* ModuleTemplate object
|
||||
*
|
||||
* @var ModuleTemplate
|
||||
*/
|
||||
protected $moduleTemplate;
|
||||
|
||||
/**
|
||||
* Constructor Method
|
||||
*
|
||||
* @var ModuleTemplate $moduleTemplate
|
||||
*/
|
||||
public function __construct(ModuleTemplate $moduleTemplate = null)
|
||||
{
|
||||
$this->moduleTemplate = $moduleTemplate ?? GeneralUtility::makeInstance(ModuleTemplate::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function Handling input variables and rendering main view
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface Response
|
||||
*/
|
||||
public function mainAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$backendUser = $this->getBackendUser();
|
||||
$pageUid = (int)$request->getQueryParams()['id'];
|
||||
|
||||
// Show only if there is a valid page and if this page may be viewed by the user
|
||||
$pageRecord = BackendUtility::readPageAccess($pageUid, $backendUser->getPagePermsClause(Permission::PAGE_SHOW));
|
||||
if (!is_array($pageRecord)) {
|
||||
// User has no permission on parent page, should not happen, just render an empty page
|
||||
$this->moduleTemplate->setContent('');
|
||||
return new HtmlResponse($this->moduleTemplate->renderContent());
|
||||
}
|
||||
|
||||
// Doc header handling
|
||||
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
$this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($pageRecord);
|
||||
$buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
|
||||
$cshButton = $buttonBar->makeHelpButton()
|
||||
->setModuleName('pages_new')
|
||||
->setFieldName('pages_new');
|
||||
$viewButton = $buttonBar->makeLinkButton()
|
||||
->setOnClick(BackendUtility::viewOnClick($pageUid, '', BackendUtility::BEgetRootLine($pageUid)))
|
||||
->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.showPage'))
|
||||
->setIcon($iconFactory->getIcon('actions-view-page', Icon::SIZE_SMALL))
|
||||
->setHref('#');
|
||||
$shortcutButton = $buttonBar->makeShortcutButton()
|
||||
->setModuleName('pages_new')
|
||||
->setGetVariables(['id']);
|
||||
$buttonBar->addButton($cshButton)->addButton($viewButton)->addButton($shortcutButton);
|
||||
|
||||
// Main view setup
|
||||
$view = GeneralUtility::makeInstance(StandaloneView::class);
|
||||
$view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName(
|
||||
'EXT:backend/Resources/Private/Templates/Page/NewPages.html'
|
||||
));
|
||||
|
||||
$calculatedPermissions = $backendUser->calcPerms($pageRecord);
|
||||
$canCreateNew = $backendUser->isAdmin() || $calculatedPermissions & Permission::PAGE_NEW;
|
||||
|
||||
$view->assign('canCreateNew', $canCreateNew);
|
||||
$view->assign('maxTitleLength', $backendUser->uc['titleLen'] ?? 20);
|
||||
$view->assign('pageUid', $pageUid);
|
||||
|
||||
if ($canCreateNew) {
|
||||
$newPagesData = (array)$request->getParsedBody()['pages'];
|
||||
if (!empty($newPagesData)) {
|
||||
$hasNewPagesData = true;
|
||||
$afterExisting = isset($request->getParsedBody()['createInListEnd']);
|
||||
$hidePages = isset($request->getParsedBody()['hidePages']);
|
||||
$hidePagesInMenu = isset($request->getParsedBody()['hidePagesInMenus']);
|
||||
$pagesCreated = $this->createPages($newPagesData, $pageUid, $afterExisting, $hidePages, $hidePagesInMenu);
|
||||
$view->assign('pagesCreated', $pagesCreated);
|
||||
$subPages = $this->getSubPagesOfPage($pageUid);
|
||||
$visiblePages = [];
|
||||
foreach ($subPages as $page) {
|
||||
$calculatedPermissions = $backendUser->calcPerms($page);
|
||||
if ($backendUser->isAdmin() || $calculatedPermissions & Permission::PAGE_SHOW) {
|
||||
$visiblePages[] = $page;
|
||||
}
|
||||
}
|
||||
$view->assign('visiblePages', $visiblePages);
|
||||
} else {
|
||||
$hasNewPagesData = false;
|
||||
$view->assign('pageTypes', $this->getTypeSelectData($pageUid));
|
||||
}
|
||||
$view->assign('hasNewPagesData', $hasNewPagesData);
|
||||
}
|
||||
|
||||
$this->moduleTemplate->setContent($view->render());
|
||||
return new HtmlResponse($this->moduleTemplate->renderContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist new pages in DB
|
||||
*
|
||||
* @param array $newPagesData Data array with title and page type
|
||||
* @param int $pageUid Uid of page new pages should be added in
|
||||
* @param bool $afterExisting True if new pages should be created after existing pages
|
||||
* @param bool $hidePages True if new pages should be set to hidden
|
||||
* @param bool $hidePagesInMenu True if new pages should be set to hidden in menu
|
||||
* @return bool TRUE if at least on pages has been added
|
||||
*/
|
||||
protected function createPages(array $newPagesData, int $pageUid, bool $afterExisting, bool $hidePages, bool $hidePagesInMenu): bool
|
||||
{
|
||||
$pagesCreated = false;
|
||||
|
||||
// Set first pid to "-1 * uid of last existing sub page" if pages should be created at end
|
||||
$firstPid = $pageUid;
|
||||
if ($afterExisting) {
|
||||
$subPages = $this->getSubPagesOfPage($pageUid);
|
||||
$lastPage = end($subPages);
|
||||
if (isset($lastPage['uid']) && MathUtility::canBeInterpretedAsInteger($lastPage['uid'])) {
|
||||
$firstPid = -(int)$lastPage['uid'];
|
||||
}
|
||||
}
|
||||
|
||||
$commandArray = [];
|
||||
$firstRecord = true;
|
||||
$previousIdentifier = '';
|
||||
foreach ($newPagesData as $identifier => $data) {
|
||||
if (!trim($data['title'])) {
|
||||
continue;
|
||||
}
|
||||
$commandArray['pages'][$identifier]['hidden'] = (int)$hidePages;
|
||||
$commandArray['pages'][$identifier]['nav_hide'] = (int)$hidePagesInMenu;
|
||||
$commandArray['pages'][$identifier]['title'] = $data['title'];
|
||||
$commandArray['pages'][$identifier]['doktype'] = $data['doktype'];
|
||||
if ($firstRecord) {
|
||||
$firstRecord = false;
|
||||
$commandArray['pages'][$identifier]['pid'] = $firstPid;
|
||||
} else {
|
||||
$commandArray['pages'][$identifier]['pid'] = '-' . $previousIdentifier;
|
||||
}
|
||||
$previousIdentifier = $identifier;
|
||||
}
|
||||
|
||||
if (!empty($commandArray)) {
|
||||
$pagesCreated = true;
|
||||
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
|
||||
$dataHandler->start($commandArray, []);
|
||||
$dataHandler->process_datamap();
|
||||
BackendUtility::setUpdateSignal('updatePageTree');
|
||||
}
|
||||
|
||||
return $pagesCreated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Page selector type data
|
||||
*
|
||||
* @param int $pageUid Page Uid
|
||||
* @return array
|
||||
*/
|
||||
protected function getTypeSelectData(int $pageUid)
|
||||
{
|
||||
$tsConfig = BackendUtility::getPagesTSconfig($pageUid);
|
||||
$pagesTsConfig = $tsConfig['TCEFORM.']['pages.'] ?? [];
|
||||
|
||||
// Find all available doktypes for the current user
|
||||
$types = $GLOBALS['PAGES_TYPES'];
|
||||
unset($types['default']);
|
||||
$types = array_keys($types);
|
||||
$types[] = 1; // default
|
||||
$types[] = 3; // link
|
||||
$types[] = 4; // shortcut
|
||||
$types[] = 7; // mount point
|
||||
$types[] = 199; // spacer
|
||||
|
||||
if (!$this->getBackendUser()->isAdmin() && isset($this->getBackendUser()->groupData['pagetypes_select'])) {
|
||||
$types = GeneralUtility::trimExplode(',', $this->getBackendUser()->groupData['pagetypes_select'], true);
|
||||
}
|
||||
$removeItems = isset($pagesTsConfig['doktype.']['removeItems']) ? GeneralUtility::trimExplode(',', $pagesTsConfig['doktype.']['removeItems'], true) : [];
|
||||
$allowedDoktypes = array_diff($types, $removeItems);
|
||||
|
||||
// All doktypes in the TCA
|
||||
$availableDoktypes = $GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'];
|
||||
|
||||
// Sort by group and allowedDoktypes
|
||||
$groupedData = [];
|
||||
$groupLabel = '';
|
||||
foreach ($availableDoktypes as $doktypeData) {
|
||||
// If it is a group, save the group label for the children underneath
|
||||
if ($doktypeData[1] === '--div--') {
|
||||
$groupLabel = $doktypeData[0];
|
||||
} else {
|
||||
if (in_array($doktypeData[1], $allowedDoktypes)) {
|
||||
$groupedData[$groupLabel][] = $doktypeData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $groupedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of sub pages with some all fields from given page.
|
||||
* Fetch all data fields for full page icon display
|
||||
*
|
||||
* @param int $pageUid Get sub pages from this pages
|
||||
* @return array
|
||||
*/
|
||||
protected function getSubPagesOfPage(int $pageUid): array
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
|
||||
$queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
|
||||
return $queryBuilder->select('*')
|
||||
->from('pages')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'pid',
|
||||
$queryBuilder->createNamedParameter($pageUid, \PDO::PARAM_INT)
|
||||
),
|
||||
$queryBuilder->expr()->eq(
|
||||
$GLOBALS['TCA']['pages']['ctrl']['languageField'],
|
||||
$queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->orderBy('sorting')
|
||||
->execute()
|
||||
->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns LanguageService
|
||||
*
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current BE user
|
||||
*
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller\Page;
|
||||
|
||||
/*
|
||||
* 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\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
|
||||
use TYPO3\CMS\Core\DataHandling\DataHandler;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
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\Fluid\View\StandaloneView;
|
||||
|
||||
/**
|
||||
* "Sort sub pages" controller - reachable from context menu "more" on page records
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class SortSubPagesController
|
||||
{
|
||||
/**
|
||||
* ModuleTemplate object
|
||||
*
|
||||
* @var ModuleTemplate
|
||||
*/
|
||||
protected $moduleTemplate;
|
||||
|
||||
/**
|
||||
* Constructor Method
|
||||
*
|
||||
* @var ModuleTemplate $moduleTemplate
|
||||
*/
|
||||
public function __construct(ModuleTemplate $moduleTemplate = null)
|
||||
{
|
||||
$this->moduleTemplate = $moduleTemplate ?? GeneralUtility::makeInstance(ModuleTemplate::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function Handling input variables and rendering main view
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface Response
|
||||
*/
|
||||
public function mainAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$backendUser = $this->getBackendUser();
|
||||
$parentPageUid = (int)$request->getQueryParams()['id'];
|
||||
|
||||
// Show only if there is a valid page and if this page may be viewed by the user
|
||||
$pageInformation = BackendUtility::readPageAccess($parentPageUid, $backendUser->getPagePermsClause(Permission::PAGE_SHOW));
|
||||
if (!is_array($pageInformation)) {
|
||||
// User has no permission on parent page, should not happen, just render an empty page
|
||||
$this->moduleTemplate->setContent('');
|
||||
return new HtmlResponse($this->moduleTemplate->renderContent());
|
||||
}
|
||||
|
||||
// Doc header handling
|
||||
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
$this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($pageInformation);
|
||||
$buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
|
||||
$cshButton = $buttonBar->makeHelpButton()
|
||||
->setModuleName('pages_sort')
|
||||
->setFieldName('pages_sort');
|
||||
$viewButton = $buttonBar->makeLinkButton()
|
||||
->setOnClick(BackendUtility::viewOnClick($parentPageUid, '', BackendUtility::BEgetRootLine($parentPageUid)))
|
||||
->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.showPage'))
|
||||
->setIcon($iconFactory->getIcon('actions-view-page', Icon::SIZE_SMALL))
|
||||
->setHref('#');
|
||||
$shortcutButton = $buttonBar->makeShortcutButton()
|
||||
->setModuleName('pages_sort')
|
||||
->setGetVariables(['id']);
|
||||
$buttonBar->addButton($cshButton)->addButton($viewButton)->addButton($shortcutButton);
|
||||
|
||||
// Main view setup
|
||||
$view = GeneralUtility::makeInstance(StandaloneView::class);
|
||||
$view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName(
|
||||
'EXT:backend/Resources/Private/Templates/Page/SortSubPages.html'
|
||||
));
|
||||
|
||||
$isInWorkspace = $backendUser->workspace !== 0;
|
||||
$view->assign('isInWorkspace', $isInWorkspace);
|
||||
$view->assign('maxTitleLength', $backendUser->uc['titleLen'] ?? 20);
|
||||
$view->assign('parentPageUid', $parentPageUid);
|
||||
$view->assign('dateFormat', $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy']);
|
||||
$view->assign('timeFormat', $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm']);
|
||||
|
||||
if (!$isInWorkspace) {
|
||||
// Apply new sorting if given
|
||||
$newSortBy = $request->getQueryParams()['newSortBy'] ?? null;
|
||||
if ($newSortBy && in_array($newSortBy, ['title', 'subtitle', 'nav_title', 'crdate', 'tstamp'], true)) {
|
||||
$this->sortSubPagesByField($parentPageUid, (string)$newSortBy);
|
||||
} elseif ($newSortBy && $newSortBy === 'reverseCurrentSorting') {
|
||||
$this->reverseSortingOfPages($parentPageUid);
|
||||
}
|
||||
|
||||
// Get sub pages, loop through them and add page/user specific permission details
|
||||
$pageRecords = $this->getSubPagesOfPage($parentPageUid);
|
||||
$hasInvisiblePage = false;
|
||||
$subPages = [];
|
||||
foreach ($pageRecords as $page) {
|
||||
$pageWithPermissions = [];
|
||||
$pageWithPermissions['record'] = $page;
|
||||
$calculatedPermissions = $backendUser->calcPerms($page);
|
||||
$pageWithPermissions['canEdit'] = $backendUser->isAdmin() || $calculatedPermissions & Permission::PAGE_EDIT;
|
||||
$canSeePage = $backendUser->isAdmin() || $calculatedPermissions & Permission::PAGE_SHOW;
|
||||
if ($canSeePage) {
|
||||
$subPages[] = $pageWithPermissions;
|
||||
} else {
|
||||
$hasInvisiblePage = true;
|
||||
}
|
||||
}
|
||||
$view->assign('subPages', $subPages);
|
||||
$view->assign('hasInvisiblePage', $hasInvisiblePage);
|
||||
}
|
||||
|
||||
$this->moduleTemplate->setContent($view->render());
|
||||
return new HtmlResponse($this->moduleTemplate->renderContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort sub pages of given uid by field name alphabetically
|
||||
*
|
||||
* @param int $parentPageUid Parent page uid
|
||||
* @param string $newSortBy Field name to sort by
|
||||
* @throws \RuntimeException If $newSortBy does not validate
|
||||
*/
|
||||
protected function sortSubPagesByField(int $parentPageUid, string $newSortBy)
|
||||
{
|
||||
if (!in_array($newSortBy, ['title', 'subtitle', 'nav_title', 'crdate', 'tstamp'], true)) {
|
||||
throw new \RuntimeException(
|
||||
'New sort by must be one of "title", "subtitle", "nav_title", "crdate" or tstamp',
|
||||
1498924810
|
||||
);
|
||||
}
|
||||
$subPages = $this->getSubPagesOfPage($parentPageUid, $newSortBy);
|
||||
if (!empty($subPages)) {
|
||||
$subPages = array_reverse($subPages);
|
||||
$this->persistNewSubPageOrder($parentPageUid, $subPages);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse current sorting of sub pages
|
||||
*
|
||||
* @param int $parentPageUid Parent page uid
|
||||
*/
|
||||
protected function reverseSortingOfPages(int $parentPageUid)
|
||||
{
|
||||
$subPages = $this->getSubPagesOfPage($parentPageUid);
|
||||
if (!empty($subPages)) {
|
||||
$this->persistNewSubPageOrder($parentPageUid, $subPages);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store new sub page order
|
||||
*
|
||||
* @param int $parentPageUid Parent page uid
|
||||
* @param array $subPages List of sub pages in new order
|
||||
*/
|
||||
protected function persistNewSubPageOrder(int $parentPageUid, array $subPages)
|
||||
{
|
||||
$commandArray = [];
|
||||
foreach ($subPages as $subPage) {
|
||||
$commandArray['pages'][$subPage['uid']]['move'] = $parentPageUid;
|
||||
}
|
||||
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
|
||||
$dataHandler->start([], $commandArray);
|
||||
$dataHandler->process_cmdmap();
|
||||
BackendUtility::setUpdateSignal('updatePageTree');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of sub pages with some all fields from given page.
|
||||
* Fetch all data fields for full page icon display
|
||||
*
|
||||
* @param int $parentPageUid Get sub pages from this pages
|
||||
* @param string $orderBy Order pages by this field
|
||||
* @return array
|
||||
*/
|
||||
protected function getSubPagesOfPage(int $parentPageUid, string $orderBy = 'sorting'): array
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
|
||||
$queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
|
||||
return $queryBuilder->select('*')
|
||||
->from('pages')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('sys_language_uid', 0),
|
||||
$queryBuilder->expr()->eq(
|
||||
'pid',
|
||||
$queryBuilder->createNamedParameter($parentPageUid, \PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->orderBy($orderBy)
|
||||
->execute()
|
||||
->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns LanguageService
|
||||
*
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current BE user
|
||||
*
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,657 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller\Page;
|
||||
|
||||
/*
|
||||
* 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\Configuration\BackendUserConfiguration;
|
||||
use TYPO3\CMS\Backend\Tree\Repository\PageTreeRepository;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\DocumentTypeExclusionRestriction;
|
||||
use TYPO3\CMS\Core\Exception\Page\RootLineException;
|
||||
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Site\PseudoSiteFinder;
|
||||
use TYPO3\CMS\Core\Site\SiteFinder;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
||||
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\RootlineUtility;
|
||||
use TYPO3\CMS\Workspaces\Service\WorkspaceService;
|
||||
|
||||
/**
|
||||
* Controller providing data to the page tree
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class TreeController
|
||||
{
|
||||
/**
|
||||
* Option to use the nav_title field for outputting in the tree items, set via userTS.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $useNavTitle = false;
|
||||
|
||||
/**
|
||||
* Option to prefix the page ID when outputting the tree items, set via userTS.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $addIdAsPrefix = false;
|
||||
|
||||
/**
|
||||
* Option to prefix the domain name of sys_domains when outputting the tree items, set via userTS.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $addDomainName = false;
|
||||
|
||||
/**
|
||||
* Option to add the rootline path above each mount point, set via userTS.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $showMountPathAboveMounts = false;
|
||||
|
||||
/**
|
||||
* An array of background colors for a branch in the tree, set via userTS.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $backgroundColors = [];
|
||||
|
||||
/**
|
||||
* A list of pages not to be shown.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $hiddenRecords = [];
|
||||
|
||||
/**
|
||||
* Contains the state of all items that are expanded.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $expandedState = [];
|
||||
|
||||
/**
|
||||
* Instance of the icon factory, to be used for generating the items.
|
||||
*
|
||||
* @var IconFactory
|
||||
*/
|
||||
protected $iconFactory;
|
||||
|
||||
/**
|
||||
* Number of tree levels which should be returned on the first page tree load
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $levelsToFetch = 2;
|
||||
|
||||
/**
|
||||
* When set to true all nodes returend by API will be expanded
|
||||
* @var bool
|
||||
*/
|
||||
protected $expandAllNodes = false;
|
||||
|
||||
/**
|
||||
* Constructor to set up common objects needed in various places.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
}
|
||||
|
||||
protected function initializeConfiguration()
|
||||
{
|
||||
$userTsConfig = $this->getBackendUser()->getTSConfig();
|
||||
$this->hiddenRecords = GeneralUtility::intExplode(
|
||||
',',
|
||||
$userTsConfig['options.']['hideRecords.']['pages'] ?? '',
|
||||
true
|
||||
);
|
||||
$this->backgroundColors = $userTsConfig['options.']['pageTree.']['backgroundColor.'] ?? [];
|
||||
$this->addIdAsPrefix = (bool)($userTsConfig['options.']['pageTree.']['showPageIdWithTitle'] ?? false);
|
||||
$this->addDomainName = (bool)($userTsConfig['options.']['pageTree.']['showDomainNameWithTitle'] ?? false);
|
||||
$this->useNavTitle = (bool)($userTsConfig['options.']['pageTree.']['showNavTitle'] ?? false);
|
||||
$this->showMountPathAboveMounts = (bool)($userTsConfig['options.']['pageTree.']['showPathAboveMounts'] ?? false);
|
||||
$backendUserConfiguration = GeneralUtility::makeInstance(BackendUserConfiguration::class);
|
||||
$this->expandedState = $backendUserConfiguration->get('BackendComponents.States.Pagetree');
|
||||
if (is_object($this->expandedState) && is_object($this->expandedState->stateHash)) {
|
||||
$this->expandedState = (array)$this->expandedState->stateHash;
|
||||
} else {
|
||||
$this->expandedState = $this->expandedState['stateHash'] ?: [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns page tree configuration in JSON
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function fetchConfigurationAction(): ResponseInterface
|
||||
{
|
||||
$configuration = [
|
||||
'allowRecursiveDelete' => !empty($this->getBackendUser()->uc['recursiveDelete']),
|
||||
'allowDragMove' => $this->isDragMoveAllowed(),
|
||||
'doktypes' => $this->getDokTypes(),
|
||||
'displayDeleteConfirmation' => $this->getBackendUser()->jsConfirmation(JsConfirmation::DELETE),
|
||||
'temporaryMountPoint' => $this->getMountPointPath((int)($this->getBackendUser()->uc['pageTree_temporaryMountPoint'] ?? 0)),
|
||||
];
|
||||
|
||||
return new JsonResponse($configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of doktypes to display in page tree toolbar drag area
|
||||
*
|
||||
* Note: The list can be filtered by the user TypoScript
|
||||
* option "options.pageTree.doktypesToShowInNewPageDragArea".
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getDokTypes(): array
|
||||
{
|
||||
$backendUser = $this->getBackendUser();
|
||||
$doktypeLabelMap = [];
|
||||
foreach ($GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'] as $doktypeItemConfig) {
|
||||
if ($doktypeItemConfig[1] === '--div--') {
|
||||
continue;
|
||||
}
|
||||
$doktypeLabelMap[$doktypeItemConfig[1]] = $doktypeItemConfig[0];
|
||||
}
|
||||
$doktypes = GeneralUtility::intExplode(',', $backendUser->getTSConfig()['options.']['pageTree.']['doktypesToShowInNewPageDragArea'] ?? '', true);
|
||||
$output = [];
|
||||
$allowedDoktypes = GeneralUtility::intExplode(',', $backendUser->groupData['pagetypes_select'], true);
|
||||
$isAdmin = $backendUser->isAdmin();
|
||||
// Early return if backend user may not create any doktype
|
||||
if (!$isAdmin && empty($allowedDoktypes)) {
|
||||
return $output;
|
||||
}
|
||||
foreach ($doktypes as $doktype) {
|
||||
if (!$isAdmin && !in_array($doktype, $allowedDoktypes, true)) {
|
||||
continue;
|
||||
}
|
||||
$label = htmlspecialchars($this->getLanguageService()->sL($doktypeLabelMap[$doktype]));
|
||||
$output[] = [
|
||||
'nodeType' => $doktype,
|
||||
'icon' => $GLOBALS['TCA']['pages']['ctrl']['typeicon_classes'][$doktype] ?? '',
|
||||
'title' => $label,
|
||||
'tooltip' => $label
|
||||
];
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns JSON representing page tree
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function fetchDataAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->initializeConfiguration();
|
||||
|
||||
$items = [];
|
||||
if (!empty($request->getQueryParams()['pid'])) {
|
||||
// Fetching a part of a page tree
|
||||
$entryPoints = $this->getAllEntryPointPageTrees((int)$request->getQueryParams()['pid']);
|
||||
$mountPid = (int)($request->getQueryParams()['mount'] ?? 0);
|
||||
$parentDepth = (int)($request->getQueryParams()['pidDepth'] ?? 0);
|
||||
$this->levelsToFetch = $parentDepth + $this->levelsToFetch;
|
||||
foreach ($entryPoints as $page) {
|
||||
$items = array_merge($items, $this->pagesToFlatArray($page, $mountPid, $parentDepth));
|
||||
}
|
||||
} else {
|
||||
$entryPoints = $this->getAllEntryPointPageTrees();
|
||||
foreach ($entryPoints as $page) {
|
||||
$items = array_merge($items, $this->pagesToFlatArray($page, (int)$page['uid']));
|
||||
}
|
||||
}
|
||||
|
||||
return new JsonResponse($items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns JSON representing page tree filtered by keyword
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function filterDataAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$searchQuery = $request->getQueryParams()['q'] ?? '';
|
||||
if (trim($searchQuery) === '') {
|
||||
return new JsonResponse([]);
|
||||
}
|
||||
|
||||
$this->initializeConfiguration();
|
||||
$this->expandAllNodes = true;
|
||||
|
||||
$items = [];
|
||||
$entryPoints = $this->getAllEntryPointPageTrees(0, $searchQuery);
|
||||
|
||||
foreach ($entryPoints as $page) {
|
||||
if (!empty($page)) {
|
||||
$items = array_merge($items, $this->pagesToFlatArray($page, (int)$page['uid']));
|
||||
}
|
||||
}
|
||||
|
||||
return new JsonResponse($items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a temporary mount point
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function setTemporaryMountPointAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
if (empty($request->getParsedBody()['pid'])) {
|
||||
throw new \RuntimeException(
|
||||
'Required "pid" parameter is missing.',
|
||||
1511792197
|
||||
);
|
||||
}
|
||||
$pid = (int)$request->getParsedBody()['pid'];
|
||||
|
||||
$this->getBackendUser()->uc['pageTree_temporaryMountPoint'] = $pid;
|
||||
$this->getBackendUser()->writeUC();
|
||||
$response = [
|
||||
'mountPointPath' => $this->getMountPointPath($pid)
|
||||
];
|
||||
return new JsonResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts nested tree structure produced by PageTreeRepository to a flat, one level array
|
||||
* and also adds visual representation information to the data.
|
||||
*
|
||||
* @param array $page
|
||||
* @param int $entryPoint
|
||||
* @param int $depth
|
||||
* @param array $inheritedData
|
||||
* @return array
|
||||
*/
|
||||
protected function pagesToFlatArray(array $page, int $entryPoint, int $depth = 0, array $inheritedData = []): array
|
||||
{
|
||||
$backendUser = $this->getBackendUser();
|
||||
$pageId = (int)$page['uid'];
|
||||
if (in_array($pageId, $this->hiddenRecords, true)) {
|
||||
return [];
|
||||
}
|
||||
if ($pageId === 0 && !$backendUser->isAdmin()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$stopPageTree = !empty($page['php_tree_stop']) && $depth > 0;
|
||||
$identifier = $entryPoint . '_' . $pageId;
|
||||
$expanded = !empty($page['expanded'])
|
||||
|| (isset($this->expandedState[$identifier]) && $this->expandedState[$identifier])
|
||||
|| $this->expandAllNodes;
|
||||
|
||||
$backgroundColor = !empty($this->backgroundColors[$pageId]) ? $this->backgroundColors[$pageId] : ($inheritedData['backgroundColor'] ?? '');
|
||||
|
||||
$suffix = '';
|
||||
$prefix = '';
|
||||
$nameSourceField = 'title';
|
||||
$visibleText = $page['title'];
|
||||
$tooltip = BackendUtility::titleAttribForPages($page, '', false);
|
||||
if ($pageId !== 0) {
|
||||
$icon = $this->iconFactory->getIconForRecord('pages', $page, Icon::SIZE_SMALL);
|
||||
} else {
|
||||
$icon = $this->iconFactory->getIcon('apps-pagetree-root', Icon::SIZE_SMALL);
|
||||
}
|
||||
|
||||
if ($this->useNavTitle && trim($page['nav_title'] ?? '') !== '') {
|
||||
$nameSourceField = 'nav_title';
|
||||
$visibleText = $page['nav_title'];
|
||||
}
|
||||
if (trim($visibleText) === '') {
|
||||
$visibleText = htmlspecialchars('[' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.no_title') . ']');
|
||||
}
|
||||
|
||||
if ($this->addDomainName && $page['is_siteroot']) {
|
||||
$domain = $this->getDomainNameForPage($pageId);
|
||||
$suffix = $domain !== '' ? ' [' . $domain . ']' : '';
|
||||
}
|
||||
|
||||
$lockInfo = BackendUtility::isRecordLocked('pages', $pageId);
|
||||
if (is_array($lockInfo)) {
|
||||
$tooltip .= ' - ' . $lockInfo['msg'];
|
||||
}
|
||||
if ($this->addIdAsPrefix) {
|
||||
$prefix = htmlspecialchars('[' . $pageId . '] ');
|
||||
}
|
||||
|
||||
$items = [];
|
||||
$item = [
|
||||
// Used to track if the tree item is collapsed or not
|
||||
'stateIdentifier' => $identifier,
|
||||
'identifier' => $pageId,
|
||||
'depth' => $depth,
|
||||
'tip' => htmlspecialchars($tooltip),
|
||||
'icon' => $icon->getIdentifier(),
|
||||
'name' => $visibleText,
|
||||
'nameSourceField' => $nameSourceField,
|
||||
'alias' => htmlspecialchars($page['alias'] ?? ''),
|
||||
'selectable' => true,
|
||||
'checked' => false,
|
||||
'mountPoint' => $entryPoint,
|
||||
'workspaceId' => !empty($page['t3ver_oid']) ? $page['t3ver_oid'] : $pageId,
|
||||
'allowDelete' => $backendUser->doesUserHaveAccess($page, Permission::PAGE_DELETE),
|
||||
'allowEdit' => $backendUser->doesUserHaveAccess($page, Permission::PAGE_EDIT)
|
||||
&& $backendUser->check('tables_modify', 'pages')
|
||||
&& $backendUser->checkLanguageAccess(0)
|
||||
];
|
||||
|
||||
if (!empty($page['_children']) || $this->getPageTreeRepository()->hasChildren($pageId)) {
|
||||
$item['hasChildren'] = true;
|
||||
if ($depth >= $this->levelsToFetch) {
|
||||
$page = $this->getPageTreeRepository()->getTreeLevels($page, 1);
|
||||
}
|
||||
}
|
||||
if (!empty($prefix)) {
|
||||
$item['prefix'] = htmlspecialchars($prefix);
|
||||
}
|
||||
if (!empty($suffix)) {
|
||||
$item['suffix'] = htmlspecialchars($suffix);
|
||||
}
|
||||
if (is_array($lockInfo)) {
|
||||
$item['locked'] = true;
|
||||
}
|
||||
if ($icon->getOverlayIcon()) {
|
||||
$item['overlayIcon'] = $icon->getOverlayIcon()->getIdentifier();
|
||||
}
|
||||
if ($expanded && is_array($page['_children']) && !empty($page['_children'])) {
|
||||
$item['expanded'] = $expanded;
|
||||
}
|
||||
if ($backgroundColor) {
|
||||
$item['backgroundColor'] = htmlspecialchars($backgroundColor);
|
||||
}
|
||||
if ($stopPageTree) {
|
||||
$item['stopPageTree'] = $stopPageTree;
|
||||
}
|
||||
$class = $this->resolvePageCssClassNames($page);
|
||||
if (!empty($class)) {
|
||||
$item['class'] = $class;
|
||||
}
|
||||
if ($depth === 0) {
|
||||
$item['isMountPoint'] = true;
|
||||
|
||||
if ($this->showMountPathAboveMounts) {
|
||||
$item['readableRootline'] = $this->getMountPointPath($pageId);
|
||||
}
|
||||
}
|
||||
|
||||
$items[] = $item;
|
||||
if (!$stopPageTree && is_array($page['_children']) && !empty($page['_children']) && ($depth < $this->levelsToFetch || $expanded)) {
|
||||
$items[key($items)]['loaded'] = true;
|
||||
foreach ($page['_children'] as $child) {
|
||||
$items = array_merge($items, $this->pagesToFlatArray($child, $entryPoint, $depth + 1, ['backgroundColor' => $backgroundColor]));
|
||||
}
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
protected function getPageTreeRepository(): PageTreeRepository
|
||||
{
|
||||
$backendUser = $this->getBackendUser();
|
||||
$userTsConfig = $backendUser->getTSConfig();
|
||||
$excludedDocumentTypes = GeneralUtility::intExplode(',', $userTsConfig['options.']['pageTree.']['excludeDoktypes'] ?? '', true);
|
||||
|
||||
$additionalQueryRestrictions = [];
|
||||
if (!empty($excludedDocumentTypes)) {
|
||||
$additionalQueryRestrictions[] = GeneralUtility::makeInstance(DocumentTypeExclusionRestriction::class, $excludedDocumentTypes);
|
||||
}
|
||||
|
||||
return GeneralUtility::makeInstance(
|
||||
PageTreeRepository::class,
|
||||
(int)$backendUser->workspace,
|
||||
[],
|
||||
$additionalQueryRestrictions
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all pages for all tree entry points the user is allowed to see
|
||||
*
|
||||
* @param int $startPid
|
||||
* @param string $query The search query can either be a string to be found in the title or the nav_title of a page or the uid of a page.
|
||||
* @return array
|
||||
*/
|
||||
protected function getAllEntryPointPageTrees(int $startPid = 0, string $query = ''): array
|
||||
{
|
||||
$backendUser = $this->getBackendUser();
|
||||
$entryPointId = $startPid > 0 ? $startPid : (int)($backendUser->uc['pageTree_temporaryMountPoint'] ?? 0);
|
||||
if ($entryPointId > 0) {
|
||||
$entryPointIds = [$entryPointId];
|
||||
} else {
|
||||
//watch out for deleted pages returned as webmount
|
||||
$entryPointIds = array_map('intval', $backendUser->returnWebmounts());
|
||||
$entryPointIds = array_unique($entryPointIds);
|
||||
if (empty($entryPointIds)) {
|
||||
// use a virtual root
|
||||
// the real mount points will be fetched in getNodes() then
|
||||
// since those will be the "sub pages" of the virtual root
|
||||
$entryPointIds = [0];
|
||||
}
|
||||
}
|
||||
if (empty($entryPointIds)) {
|
||||
return [];
|
||||
}
|
||||
$repository = $this->getPageTreeRepository();
|
||||
|
||||
$permClause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW);
|
||||
if ($query !== '') {
|
||||
$this->levelsToFetch = 999;
|
||||
$repository->fetchFilteredTree(
|
||||
$query,
|
||||
$this->getAllowedMountPoints(),
|
||||
$permClause
|
||||
);
|
||||
}
|
||||
|
||||
$entryPointRecords = [];
|
||||
foreach ($entryPointIds as $k => $entryPointId) {
|
||||
if (in_array($entryPointId, $this->hiddenRecords, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!empty($this->backgroundColors) && is_array($this->backgroundColors)) {
|
||||
try {
|
||||
$entryPointRootLine = GeneralUtility::makeInstance(RootlineUtility::class, $entryPointId)->get();
|
||||
} catch (RootLineException $e) {
|
||||
$entryPointRootLine = [];
|
||||
}
|
||||
foreach ($entryPointRootLine as $rootLineEntry) {
|
||||
$parentUid = $rootLineEntry['uid'];
|
||||
if (!empty($this->backgroundColors[$parentUid]) && empty($this->backgroundColors[$entryPointId])) {
|
||||
$this->backgroundColors[$entryPointId] = $this->backgroundColors[$parentUid];
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($entryPointId === 0) {
|
||||
$entryPointRecord = [
|
||||
'uid' => 0,
|
||||
'title' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ?: 'TYPO3'
|
||||
];
|
||||
} else {
|
||||
$entryPointRecord = BackendUtility::getRecordWSOL('pages', $entryPointId, '*', $permClause);
|
||||
|
||||
if ($entryPointRecord !== null && !$this->getBackendUser()->isInWebMount($entryPointId)) {
|
||||
$entryPointRecord = null;
|
||||
}
|
||||
}
|
||||
if ($entryPointRecord) {
|
||||
$entryPointRecord['uid'] = (int)$entryPointRecord['uid'];
|
||||
if ($query === '') {
|
||||
$entryPointRecord = $repository->getTreeLevels($entryPointRecord, $this->levelsToFetch);
|
||||
} else {
|
||||
$entryPointRecord = $repository->getTree((int)$entryPointRecord['uid'], null, $entryPointIds, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($entryPointRecord) && !empty($entryPointRecord)) {
|
||||
$entryPointRecords[$k] = $entryPointRecord;
|
||||
}
|
||||
}
|
||||
|
||||
return $entryPointRecords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first configured domain name for a page
|
||||
*
|
||||
* @param int $pageId
|
||||
* @return string
|
||||
*/
|
||||
protected function getDomainNameForPage(int $pageId): string
|
||||
{
|
||||
$domain = '';
|
||||
$siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
|
||||
try {
|
||||
$site = $siteFinder->getSiteByRootPageId($pageId);
|
||||
$domain = (string)$site->getBase();
|
||||
} catch (SiteNotFoundException $e) {
|
||||
// No site found, let's see if it is a legacy-pseudo-site
|
||||
$pseudoSiteFinder = GeneralUtility::makeInstance(PseudoSiteFinder::class);
|
||||
try {
|
||||
$site = $pseudoSiteFinder->getSiteByRootPageId($pageId);
|
||||
$domain = trim((string)$site->getBase(), '/');
|
||||
} catch (SiteNotFoundException $e) {
|
||||
// No pseudo-site found either
|
||||
}
|
||||
}
|
||||
|
||||
return $domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mount point path for a temporary mount or the given id
|
||||
*
|
||||
* @param int $uid
|
||||
* @return string
|
||||
*/
|
||||
protected function getMountPointPath(int $uid): string
|
||||
{
|
||||
if ($uid <= 0) {
|
||||
return '';
|
||||
}
|
||||
$rootline = array_reverse(BackendUtility::BEgetRootLine($uid));
|
||||
array_shift($rootline);
|
||||
$path = [];
|
||||
foreach ($rootline as $rootlineElement) {
|
||||
$record = BackendUtility::getRecordWSOL('pages', $rootlineElement['uid'], 'title, nav_title', '', true, true);
|
||||
$text = $record['title'];
|
||||
if ($this->useNavTitle && trim($record['nav_title'] ?? '') !== '') {
|
||||
$text = $record['nav_title'];
|
||||
}
|
||||
$path[] = htmlspecialchars($text);
|
||||
}
|
||||
return '/' . implode('/', $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches possible css class names to be used when a record was modified in a workspace
|
||||
*
|
||||
* @param array $page Page record (workspace overlaid)
|
||||
* @return string CSS class names to be applied
|
||||
*/
|
||||
protected function resolvePageCssClassNames(array $page): string
|
||||
{
|
||||
$classes = [];
|
||||
|
||||
if ($page['uid'] === 0) {
|
||||
return '';
|
||||
}
|
||||
$workspaceId = (int)$this->getBackendUser()->workspace;
|
||||
if ($workspaceId > 0 && ExtensionManagementUtility::isLoaded('workspaces')) {
|
||||
if ($page['t3ver_oid'] > 0 && (int)$page['t3ver_wsid'] === $workspaceId) {
|
||||
$classes[] = 'ver-element';
|
||||
$classes[] = 'ver-versions';
|
||||
} elseif (
|
||||
$this->getWorkspaceService()->hasPageRecordVersions(
|
||||
$workspaceId,
|
||||
$page['t3ver_oid'] ?: $page['uid']
|
||||
)
|
||||
) {
|
||||
$classes[] = 'ver-versions';
|
||||
}
|
||||
}
|
||||
|
||||
return implode(' ', $classes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if drag-move in the svg tree is allowed for the user
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isDragMoveAllowed(): bool
|
||||
{
|
||||
$backendUser = $this->getBackendUser();
|
||||
return $backendUser->isAdmin()
|
||||
|| ($backendUser->check('tables_modify', 'pages') && $backendUser->checkLanguageAccess(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get allowed mountpoints. Returns temporary mountpoint when temporary mountpoint is used.
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
protected function getAllowedMountPoints(): array
|
||||
{
|
||||
$mountPoints = (int)($this->getBackendUser()->uc['pageTree_temporaryMountPoint'] ?? 0);
|
||||
if (!$mountPoints) {
|
||||
$mountPoints = array_map('intval', $this->getBackendUser()->returnWebmounts());
|
||||
return array_unique($mountPoints);
|
||||
}
|
||||
return [$mountPoints];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WorkspaceService
|
||||
*/
|
||||
protected function getWorkspaceService(): WorkspaceService
|
||||
{
|
||||
return GeneralUtility::makeInstance(WorkspaceService::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService|null
|
||||
*/
|
||||
protected function getLanguageService(): ?LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'] ?? null;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller;
|
||||
|
||||
/*
|
||||
* 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\Backend\Shortcut\ShortcutRepository;
|
||||
use TYPO3\CMS\Backend\Backend\ToolbarItems\ShortcutToolbarItem;
|
||||
use TYPO3\CMS\Backend\Module\ModuleLoader;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
|
||||
/**
|
||||
* Controller for shortcut processing
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class ShortcutController
|
||||
{
|
||||
/**
|
||||
* @var ShortcutToolbarItem
|
||||
*/
|
||||
protected $shortcutToolbarItem;
|
||||
|
||||
/**
|
||||
* @var ShortcutRepository
|
||||
*/
|
||||
protected $shortcutRepository;
|
||||
|
||||
/**
|
||||
* @var ModuleLoader
|
||||
*/
|
||||
protected $moduleLoader;
|
||||
|
||||
/**
|
||||
* Set up dependencies
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->shortcutToolbarItem = GeneralUtility::makeInstance(ShortcutToolbarItem::class);
|
||||
$this->shortcutRepository = GeneralUtility::makeInstance(ShortcutRepository::class);
|
||||
// Needed to get the correct icons when reloading the menu after saving it
|
||||
$this->moduleLoader = GeneralUtility::makeInstance(ModuleLoader::class);
|
||||
$this->moduleLoader->load($GLOBALS['TBE_MODULES']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the menu so that it can be returned as response to an AJAX call
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function menuAction(): ResponseInterface
|
||||
{
|
||||
return new HtmlResponse($this->shortcutToolbarItem->getDropDown());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a shortcut through an AJAX call
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function addAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$result = 'success';
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$queryParams = $request->getQueryParams();
|
||||
$url = rawurldecode($parsedBody['url'] ?? $queryParams['url'] ?? '');
|
||||
|
||||
if ($this->shortcutRepository->shortcutExists($url)) {
|
||||
$result = 'alreadyExists';
|
||||
} else {
|
||||
$moduleName = $parsedBody['module'] ?? '';
|
||||
$parentModuleName = $parsedBody['motherModName'] ?? '';
|
||||
$shortcutName = $parsedBody['displayName'] ?? '';
|
||||
$success = $this->shortcutRepository->addShortcut($url, $moduleName, $parentModuleName, $shortcutName);
|
||||
|
||||
if (!$success) {
|
||||
$result = 'failed';
|
||||
}
|
||||
}
|
||||
|
||||
return new HtmlResponse($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the available shortcut groups, renders a form so it can be saved later on, called via AJAX
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface the full HTML for the form
|
||||
*/
|
||||
public function showEditFormAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$queryParams = $request->getQueryParams();
|
||||
|
||||
$selectedShortcutId = (int)($parsedBody['shortcutId'] ?? $queryParams['shortcutId']);
|
||||
$selectedShortcutGroupId = (int)($parsedBody['shortcutGroup'] ?? $queryParams['shortcutGroup']);
|
||||
$selectedShortcut = $this->shortcutRepository->getShortcutById($selectedShortcutId);
|
||||
$shortcutGroups = $this->shortcutRepository->getShortcutGroups();
|
||||
|
||||
$editFormView = $this->getFluidTemplateObject('EditForm.html');
|
||||
$editFormView->assign('selectedShortcutId', $selectedShortcutId);
|
||||
$editFormView->assign('selectedShortcutGroupId', $selectedShortcutGroupId);
|
||||
$editFormView->assign('selectedShortcut', $selectedShortcut);
|
||||
$editFormView->assign('shortcutGroups', $shortcutGroups);
|
||||
|
||||
return new HtmlResponse($editFormView->render());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets called when a shortcut is changed, checks whether the user has
|
||||
* permissions to do so and saves the changes if everything is ok
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function updateAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$queryParams = $request->getQueryParams();
|
||||
$shortcutId = (int)($parsedBody['shortcutId'] ?? $queryParams['shortcutId'] ?? 0);
|
||||
$shortcutTitle = strip_tags($parsedBody['shortcutTitle'] ?? $queryParams['shortcutTitle'] ?? '');
|
||||
$shortcutGroupId = (int)($parsedBody['shortcutGroup'] ?? $queryParams['shortcutGroup'] ?? 0);
|
||||
|
||||
$success = $this->shortcutRepository->updateShortcut($shortcutId, $shortcutTitle, $shortcutGroupId);
|
||||
|
||||
return new HtmlResponse($success ? $shortcutTitle : 'failed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a shortcut through an AJAX call
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function removeAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$queryParams = $request->getQueryParams();
|
||||
$shortcutId = (int)($parsedBody['shortcutId'] ?? $queryParams['shortcutId'] ?? 0);
|
||||
$success = $this->shortcutRepository->removeShortcut($shortcutId);
|
||||
|
||||
return new JsonResponse(['success' => $success]);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a new standalone view, shorthand function
|
||||
*
|
||||
* @param string $templateFilename
|
||||
* @return StandaloneView
|
||||
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidExtensionNameException
|
||||
* @throws \InvalidArgumentException
|
||||
* @internal param string $templateFile
|
||||
*/
|
||||
protected function getFluidTemplateObject(string $templateFilename): StandaloneView
|
||||
{
|
||||
$view = GeneralUtility::makeInstance(StandaloneView::class);
|
||||
$view->setLayoutRootPaths(['EXT:backend/Resources/Private/Layouts']);
|
||||
$view->setPartialRootPaths(['EXT:backend/Resources/Private/Partials']);
|
||||
$view->setTemplateRootPaths(['EXT:backend/Resources/Private/Templates/ShortcutToolbarItem']);
|
||||
$view->setTemplate($templateFilename);
|
||||
$view->getRequest()->setControllerExtensionName('Backend');
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller;
|
||||
|
||||
/*
|
||||
* 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\Clipboard\Clipboard;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Compatibility\PublicPropertyDeprecationTrait;
|
||||
use TYPO3\CMS\Core\DataHandling\DataHandler;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Http\RedirectResponse;
|
||||
use TYPO3\CMS\Core\Messaging\AbstractMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* Script Class, creating object of \TYPO3\CMS\Core\DataHandling\DataHandler and
|
||||
* sending the posted data to the object.
|
||||
*
|
||||
* Used by many smaller forms/links in TYPO3, including the QuickEdit module.
|
||||
* Is not used by FormEngine though (main form rendering script) - that uses the same class (DataHandler) but makes its own initialization (to save the redirect request).
|
||||
* For all other cases than FormEngine it is recommended to use this script for submitting your editing forms - but the best solution in any case would probably be to link your application to FormEngine, that will give you easy form-rendering as well.
|
||||
*/
|
||||
class SimpleDataHandlerController
|
||||
{
|
||||
use PublicPropertyDeprecationTrait;
|
||||
|
||||
/**
|
||||
* Properties which have been moved to protected status from public
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $deprecatedPublicProperties = [
|
||||
'flags' => 'Using $flags of class SimpleDataHandlerController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'data' => 'Using $data of class SimpleDataHandlerController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'cmd' => 'Using $cmd of class SimpleDataHandlerController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'mirror' => 'Using $mirror of class SimpleDataHandlerController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'cacheCmd' => 'Using $cacheCmd of class SimpleDataHandlerController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'redirect' => 'Using $redirect of class SimpleDataHandlerController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'CB' => 'Using $CB of class SimpleDataHandlerController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'tce' => 'Using $tce of class SimpleDataHandlerController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Array. Accepts options to be set in TCE object. Currently it supports "reverseOrder" (bool).
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $flags;
|
||||
|
||||
/**
|
||||
* Data array on the form [tablename][uid][fieldname] = value
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* Command array on the form [tablename][uid][command] = value.
|
||||
* This array may get additional data set internally based on clipboard commands send in CB var!
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $cmd;
|
||||
|
||||
/**
|
||||
* Array passed to ->setMirror.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $mirror;
|
||||
|
||||
/**
|
||||
* Cache command sent to ->clear_cacheCmd
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cacheCmd;
|
||||
|
||||
/**
|
||||
* Redirect URL. Script will redirect to this location after performing operations (unless errors has occurred)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $redirect;
|
||||
|
||||
/**
|
||||
* Clipboard command array. May trigger changes in "cmd"
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $CB;
|
||||
|
||||
/**
|
||||
* TYPO3 Core Engine
|
||||
*
|
||||
* @var \TYPO3\CMS\Core\DataHandling\DataHandler
|
||||
*/
|
||||
protected $tce;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// @deprecated since TYPO3 v9, will be obsolete in TYPO3 v10.0 with removal of init()
|
||||
$request = $GLOBALS['TYPO3_REQUEST'];
|
||||
$GLOBALS['SOBE'] = $this;
|
||||
// @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 processRequest() method, it just redirects to the given URL afterwards.
|
||||
*
|
||||
* @param ServerRequestInterface $request the current request
|
||||
* @return ResponseInterface the response with the content
|
||||
*/
|
||||
public function mainAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->initializeClipboard();
|
||||
$this->processRequest();
|
||||
|
||||
// Write errors to flash message queue
|
||||
$this->tce->printLogErrorMessages();
|
||||
if ($this->redirect) {
|
||||
return new RedirectResponse(GeneralUtility::locationHeaderUrl($this->redirect), 303);
|
||||
}
|
||||
return new HtmlResponse('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes all AJAX calls and returns a JSON formatted string
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function processAjaxRequest(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
// do the regular / main logic
|
||||
$this->initializeClipboard();
|
||||
$this->processRequest();
|
||||
|
||||
/** @var \TYPO3\CMS\Core\Messaging\FlashMessageService $flashMessageService */
|
||||
$flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
|
||||
|
||||
$content = [
|
||||
'redirect' => $this->redirect,
|
||||
'messages' => [],
|
||||
'hasErrors' => false
|
||||
];
|
||||
|
||||
// Prints errors (= write them to the message queue)
|
||||
$this->tce->printLogErrorMessages();
|
||||
|
||||
$messages = $flashMessageService->getMessageQueueByIdentifier()->getAllMessagesAndFlush();
|
||||
if (!empty($messages)) {
|
||||
foreach ($messages as $message) {
|
||||
$content['messages'][] = [
|
||||
'title' => $message->getTitle(),
|
||||
'message' => $message->getMessage(),
|
||||
'severity' => $message->getSeverity()
|
||||
];
|
||||
if ($message->getSeverity() === AbstractMessage::ERROR) {
|
||||
$content['hasErrors'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new JsonResponse($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization of the class
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
*/
|
||||
public function init(ServerRequestInterface $request = null): void
|
||||
{
|
||||
if ($request === null) {
|
||||
// Method signature in TYPO3 v10.0: protected function init(ServerRequestInterface $request)
|
||||
trigger_error('SimpleDataHandlerController->init() will be set to protected in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
|
||||
$request = $GLOBALS['TYPO3_REQUEST'];
|
||||
}
|
||||
|
||||
$beUser = $this->getBackendUser();
|
||||
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$queryParams = $request->getQueryParams();
|
||||
|
||||
// GPvars:
|
||||
$this->flags = $parsedBody['flags'] ?? $queryParams['flags'] ?? null;
|
||||
$this->data = $parsedBody['data'] ?? $queryParams['data'] ?? null;
|
||||
$this->cmd = $parsedBody['cmd'] ?? $queryParams['cmd'] ?? null;
|
||||
$this->mirror = $parsedBody['mirror'] ?? $queryParams['mirror'] ?? null;
|
||||
$this->cacheCmd = $parsedBody['cacheCmd'] ?? $queryParams['cacheCmd'] ?? null;
|
||||
$redirect = $parsedBody['redirect'] ?? $queryParams['redirect'] ?? '';
|
||||
$this->redirect = GeneralUtility::sanitizeLocalUrl($redirect);
|
||||
$this->CB = $parsedBody['CB'] ?? $queryParams['CB'] ?? null;
|
||||
// Creating DataHandler object
|
||||
$this->tce = GeneralUtility::makeInstance(DataHandler::class);
|
||||
// Configuring based on user prefs.
|
||||
if ($beUser->uc['recursiveDelete']) {
|
||||
// TRUE if the delete Recursive flag is set.
|
||||
$this->tce->deleteTree = 1;
|
||||
}
|
||||
if ($beUser->uc['copyLevels']) {
|
||||
// Set to number of page-levels to copy.
|
||||
$this->tce->copyTree = MathUtility::forceIntegerInRange($beUser->uc['copyLevels'], 0, 100);
|
||||
}
|
||||
if ($beUser->uc['neverHideAtCopy']) {
|
||||
$this->tce->neverHideAtCopy = 1;
|
||||
}
|
||||
// Reverse order.
|
||||
if ($this->flags['reverseOrder']) {
|
||||
$this->tce->reverseOrder = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clipboard pasting and deleting.
|
||||
*
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function initClipboard()
|
||||
{
|
||||
trigger_error('SimpleDataHandlerController->initClipboard() will be replaced by protected method initializeClipboard() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
|
||||
$this->initializeClipboard();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executing the posted actions ...
|
||||
*
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function main()
|
||||
{
|
||||
trigger_error('SimpleDataHandlerController->main() will be replaced by protected method processRequest() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
|
||||
$this->processRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clipboard pasting and deleting.
|
||||
*/
|
||||
protected function initializeClipboard(): void
|
||||
{
|
||||
if (is_array($this->CB)) {
|
||||
$clipObj = GeneralUtility::makeInstance(Clipboard::class);
|
||||
$clipObj->initializeClipboard();
|
||||
if ($this->CB['paste']) {
|
||||
$clipObj->setCurrentPad($this->CB['pad']);
|
||||
$this->cmd = $clipObj->makePasteCmdArray(
|
||||
$this->CB['paste'],
|
||||
$this->cmd,
|
||||
$this->CB['update'] ?? null
|
||||
);
|
||||
}
|
||||
if ($this->CB['delete']) {
|
||||
$clipObj->setCurrentPad($this->CB['pad']);
|
||||
$this->cmd = $clipObj->makeDeleteCmdArray($this->cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executing the posted actions ...
|
||||
*/
|
||||
protected function processRequest(): void
|
||||
{
|
||||
// LOAD DataHandler with data and cmd arrays:
|
||||
$this->tce->start($this->data, $this->cmd);
|
||||
if (is_array($this->mirror)) {
|
||||
$this->tce->setMirror($this->mirror);
|
||||
}
|
||||
// Register uploaded files
|
||||
$this->tce->process_uploads($_FILES);
|
||||
// Execute actions:
|
||||
$this->tce->process_datamap();
|
||||
$this->tce->process_cmdmap();
|
||||
// Clearing cache:
|
||||
if (!empty($this->cacheCmd)) {
|
||||
$this->tce->clear_cacheCmd($this->cacheCmd);
|
||||
}
|
||||
// Update page tree?
|
||||
if (isset($this->data['pages']) || isset($this->cmd['pages'])) {
|
||||
BackendUtility::setUpdateSignal('updatePageTree');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current BE user.
|
||||
*
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,688 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller;
|
||||
|
||||
/*
|
||||
* 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\Configuration\SiteTcaConfiguration;
|
||||
use TYPO3\CMS\Backend\Exception\SiteValidationErrorException;
|
||||
use TYPO3\CMS\Backend\Form\FormDataCompiler;
|
||||
use TYPO3\CMS\Backend\Form\FormDataGroup\SiteConfigurationDataGroup;
|
||||
use TYPO3\CMS\Backend\Form\FormResultCompiler;
|
||||
use TYPO3\CMS\Backend\Form\NodeFactory;
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplate;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Configuration\SiteConfiguration;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
|
||||
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Http\RedirectResponse;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageService;
|
||||
use TYPO3\CMS\Core\Site\SiteFinder;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
use TYPO3\CMS\Frontend\Page\PageRepository;
|
||||
use TYPO3Fluid\Fluid\View\ViewInterface;
|
||||
|
||||
/**
|
||||
* Backend controller: The "Site management" -> "Sites" module
|
||||
*
|
||||
* List all site root pages, CRUD site configuration.
|
||||
*
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class SiteConfigurationController
|
||||
{
|
||||
protected const ALLOWED_ACTIONS = ['overview', 'edit', 'save', 'delete'];
|
||||
|
||||
/**
|
||||
* @var ModuleTemplate
|
||||
*/
|
||||
protected $moduleTemplate;
|
||||
|
||||
/**
|
||||
* @var ViewInterface
|
||||
*/
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
* @var SiteFinder
|
||||
*/
|
||||
protected $siteFinder;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
|
||||
$this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main entry method: Dispatch to other actions - those method names that end with "Action".
|
||||
*
|
||||
* @param ServerRequestInterface $request the current request
|
||||
* @return ResponseInterface the response with the content
|
||||
*/
|
||||
public function handleRequest(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
// forcing uncached sites will re-initialize `SiteFinder`
|
||||
// which is used later by FormEngine (implicit behavior)
|
||||
$this->siteFinder->getAllSites(false);
|
||||
$this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
|
||||
$this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/Modal');
|
||||
$action = $request->getQueryParams()['action'] ?? $request->getParsedBody()['action'] ?? 'overview';
|
||||
|
||||
if (!in_array($action, self::ALLOWED_ACTIONS, true)) {
|
||||
return new HtmlResponse('Action not allowed', 400);
|
||||
}
|
||||
|
||||
$this->initializeView($action);
|
||||
|
||||
$result = call_user_func_array([$this, $action . 'Action'], [$request]);
|
||||
if ($result instanceof ResponseInterface) {
|
||||
return $result;
|
||||
}
|
||||
$this->moduleTemplate->setContent($this->view->render());
|
||||
return new HtmlResponse($this->moduleTemplate->renderContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* List pages that have 'is_siteroot' flag set - those that have the globe icon in page tree.
|
||||
* Link to Add / Edit / Delete for each.
|
||||
*/
|
||||
protected function overviewAction(): void
|
||||
{
|
||||
$this->configureOverViewDocHeader();
|
||||
$allSites = $this->siteFinder->getAllSites();
|
||||
$pages = $this->getAllSitePages();
|
||||
$unassignedSites = [];
|
||||
foreach ($allSites as $identifier => $site) {
|
||||
$rootPageId = $site->getRootPageId();
|
||||
if (isset($pages[$rootPageId])) {
|
||||
$pages[$rootPageId]['siteIdentifier'] = $identifier;
|
||||
$pages[$rootPageId]['siteConfiguration'] = $site;
|
||||
} else {
|
||||
$unassignedSites[] = $site;
|
||||
}
|
||||
}
|
||||
$this->view->assignMultiple([
|
||||
'pages' => $pages,
|
||||
'unassignedSites' => $unassignedSites
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a form to create a new site configuration, or edit an existing one.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function editAction(ServerRequestInterface $request): void
|
||||
{
|
||||
$this->configureEditViewDocHeader();
|
||||
|
||||
// Put site and friends TCA into global TCA
|
||||
// @todo: We might be able to get rid of that later
|
||||
$GLOBALS['TCA'] = array_merge($GLOBALS['TCA'], GeneralUtility::makeInstance(SiteTcaConfiguration::class)->getTca());
|
||||
|
||||
$siteIdentifier = $request->getQueryParams()['site'] ?? null;
|
||||
$pageUid = (int)($request->getQueryParams()['pageUid'] ?? 0);
|
||||
|
||||
if (empty($siteIdentifier) && empty($pageUid)) {
|
||||
throw new \RuntimeException('Either site identifier to edit a config or page uid to add new config must be set', 1521561148);
|
||||
}
|
||||
$isNewConfig = empty($siteIdentifier);
|
||||
|
||||
$defaultValues = [];
|
||||
if ($isNewConfig) {
|
||||
$defaultValues['site']['rootPageId'] = $pageUid;
|
||||
}
|
||||
|
||||
$allSites = $this->siteFinder->getAllSites();
|
||||
if (!$isNewConfig && !isset($allSites[$siteIdentifier])) {
|
||||
throw new \RuntimeException('Existing config for site ' . $siteIdentifier . ' not found', 1521561226);
|
||||
}
|
||||
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
$returnUrl = $uriBuilder->buildUriFromRoute('site_configuration');
|
||||
|
||||
$formDataGroup = GeneralUtility::makeInstance(SiteConfigurationDataGroup::class);
|
||||
$formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
|
||||
$formDataCompilerInput = [
|
||||
'tableName' => 'site',
|
||||
'vanillaUid' => $isNewConfig ? $pageUid : $allSites[$siteIdentifier]->getRootPageId(),
|
||||
'command' => $isNewConfig ? 'new' : 'edit',
|
||||
'returnUrl' => (string)$returnUrl,
|
||||
'customData' => [
|
||||
'siteIdentifier' => $isNewConfig ? '' : $siteIdentifier,
|
||||
],
|
||||
'defaultValues' => $defaultValues,
|
||||
];
|
||||
$formData = $formDataCompiler->compile($formDataCompilerInput);
|
||||
$nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
|
||||
$formData['renderType'] = 'outerWrapContainer';
|
||||
$formResult = $nodeFactory->create($formData)->render();
|
||||
// Needed to be set for 'onChange="reload"' and reload on type change to work
|
||||
$formResult['doSaveFieldName'] = 'doSave';
|
||||
$formResultCompiler = GeneralUtility::makeInstance(FormResultCompiler::class);
|
||||
$formResultCompiler->mergeResult($formResult);
|
||||
$formResultCompiler->addCssFiles();
|
||||
// Always add rootPageId as additional field to have a reference for new records
|
||||
$this->view->assign('rootPageId', $isNewConfig ? $pageUid : $allSites[$siteIdentifier]->getRootPageId());
|
||||
$this->view->assign('returnUrl', $returnUrl);
|
||||
$this->view->assign('formEngineHtml', $formResult['html']);
|
||||
$this->view->assign('formEngineFooter', $formResultCompiler->printNeededJSFunctions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Save incoming data from editAction and redirect to overview or edit
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function saveAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
// Put site and friends TCA into global TCA
|
||||
// @todo: We might be able to get rid of that later
|
||||
$GLOBALS['TCA'] = array_merge($GLOBALS['TCA'], GeneralUtility::makeInstance(SiteTcaConfiguration::class)->getTca());
|
||||
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
$siteTca = GeneralUtility::makeInstance(SiteTcaConfiguration::class)->getTca();
|
||||
|
||||
$overviewRoute = $uriBuilder->buildUriFromRoute('site_configuration', ['action' => 'overview']);
|
||||
$parsedBody = $request->getParsedBody();
|
||||
if (isset($parsedBody['closeDoc']) && (int)$parsedBody['closeDoc'] === 1) {
|
||||
// Closing means no save, just redirect to overview
|
||||
return new RedirectResponse($overviewRoute);
|
||||
}
|
||||
$isSave = $parsedBody['_savedok'] ?? $parsedBody['doSave'] ?? false;
|
||||
$isSaveClose = $parsedBody['_saveandclosedok'] ?? false;
|
||||
if (!$isSave && !$isSaveClose) {
|
||||
throw new \RuntimeException('Either save or save and close', 1520370364);
|
||||
}
|
||||
|
||||
if (!isset($parsedBody['data']['site']) || !is_array($parsedBody['data']['site'])) {
|
||||
throw new \RuntimeException('No site data or site identifier given', 1521030950);
|
||||
}
|
||||
|
||||
$data = $parsedBody['data'];
|
||||
// This can be NEW123 for new records
|
||||
$pageId = (int)key($data['site']);
|
||||
$sysSiteRow = current($data['site']);
|
||||
$siteIdentifier = $sysSiteRow['identifier'] ?? '';
|
||||
|
||||
$isNewConfiguration = false;
|
||||
$currentIdentifier = '';
|
||||
try {
|
||||
$currentSite = $this->siteFinder->getSiteByRootPageId($pageId);
|
||||
$currentSiteConfiguration = $currentSite->getConfiguration();
|
||||
$currentIdentifier = $currentSite->getIdentifier();
|
||||
} catch (SiteNotFoundException $e) {
|
||||
$currentSiteConfiguration = [];
|
||||
$isNewConfiguration = true;
|
||||
$pageId = (int)$parsedBody['rootPageId'];
|
||||
if (!$pageId > 0) {
|
||||
// Early validation of rootPageId - it must always be given and greater than 0
|
||||
throw new \RuntimeException('No root page id found', 1521719709);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate site identifier and do not store or further process it
|
||||
$siteIdentifier = $this->validateAndProcessIdentifier($isNewConfiguration, $siteIdentifier, $pageId);
|
||||
unset($sysSiteRow['identifier']);
|
||||
|
||||
try {
|
||||
$newSysSiteData = [];
|
||||
// Hard set rootPageId: This is TCA readOnly and not transmitted by FormEngine, but is also the "uid" of the site record
|
||||
$newSysSiteData['rootPageId'] = $pageId;
|
||||
foreach ($sysSiteRow as $fieldName => $fieldValue) {
|
||||
$type = $siteTca['site']['columns'][$fieldName]['config']['type'];
|
||||
switch ($type) {
|
||||
case 'input':
|
||||
case 'text':
|
||||
$fieldValue = $this->validateAndProcessValue('site', $fieldName, $fieldValue);
|
||||
$newSysSiteData[$fieldName] = $fieldValue;
|
||||
break;
|
||||
|
||||
case 'inline':
|
||||
$newSysSiteData[$fieldName] = [];
|
||||
$childRowIds = GeneralUtility::trimExplode(',', $fieldValue, true);
|
||||
if (!isset($siteTca['site']['columns'][$fieldName]['config']['foreign_table'])) {
|
||||
throw new \RuntimeException('No foreign_table found for inline type', 1521555037);
|
||||
}
|
||||
$foreignTable = $siteTca['site']['columns'][$fieldName]['config']['foreign_table'];
|
||||
foreach ($childRowIds as $childRowId) {
|
||||
$childRowData = [];
|
||||
if (!isset($data[$foreignTable][$childRowId])) {
|
||||
if (!empty($currentSiteConfiguration[$fieldName][$childRowId])) {
|
||||
// A collapsed inline record: Fetch data from existing config
|
||||
$newSysSiteData[$fieldName][] = $currentSiteConfiguration[$fieldName][$childRowId];
|
||||
continue;
|
||||
}
|
||||
throw new \RuntimeException('No data found for table ' . $foreignTable . ' with id ' . $childRowId, 1521555177);
|
||||
}
|
||||
$childRow = $data[$foreignTable][$childRowId];
|
||||
foreach ($childRow as $childFieldName => $childFieldValue) {
|
||||
if ($childFieldName === 'pid') {
|
||||
// pid is added by inline by default, but not relevant for yml storage
|
||||
continue;
|
||||
}
|
||||
$type = $siteTca[$foreignTable]['columns'][$childFieldName]['config']['type'];
|
||||
switch ($type) {
|
||||
case 'input':
|
||||
case 'select':
|
||||
case 'text':
|
||||
$childRowData[$childFieldName] = $childFieldValue;
|
||||
break;
|
||||
case 'check':
|
||||
$childRowData[$childFieldName] = (bool)$childFieldValue;
|
||||
break;
|
||||
default:
|
||||
throw new \RuntimeException('TCA type ' . $type . ' not implemented in site handling', 1521555340);
|
||||
}
|
||||
}
|
||||
$newSysSiteData[$fieldName][] = $childRowData;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'select':
|
||||
$newSysSiteData[$fieldName] = MathUtility::canBeInterpretedAsInteger($fieldValue) ? (int)$fieldValue : $fieldValue;
|
||||
break;
|
||||
|
||||
case 'check':
|
||||
$newSysSiteData[$fieldName] = (bool)$fieldValue;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \RuntimeException('TCA type "' . $type . '" is not implemented in site handling', 1521032781);
|
||||
}
|
||||
}
|
||||
|
||||
// keep root config objects not given via GUI
|
||||
// this way extension authors are able to use their own objects on root level
|
||||
// that are not configurable via GUI
|
||||
// however: we overwrite the full subset of any GUI object to make sure we have a clean state
|
||||
$newSysSiteData = array_merge($currentSiteConfiguration, $newSysSiteData);
|
||||
$newSiteConfiguration = $this->validateFullStructure($newSysSiteData);
|
||||
|
||||
// Persist the configuration
|
||||
$siteConfigurationManager = GeneralUtility::makeInstance(SiteConfiguration::class, Environment::getConfigPath() . '/sites');
|
||||
if (!$isNewConfiguration && $currentIdentifier !== $siteIdentifier) {
|
||||
$siteConfigurationManager->rename($currentIdentifier, $siteIdentifier);
|
||||
}
|
||||
$siteConfigurationManager->write($siteIdentifier, $newSiteConfiguration);
|
||||
} catch (SiteValidationErrorException $e) {
|
||||
// Do not store new config if a validation error is thrown, but redirect only to show a generated flash message
|
||||
}
|
||||
|
||||
$saveRoute = $uriBuilder->buildUriFromRoute('site_configuration', ['action' => 'edit', 'site' => $siteIdentifier]);
|
||||
if ($isSaveClose) {
|
||||
return new RedirectResponse($overviewRoute);
|
||||
}
|
||||
return new RedirectResponse($saveRoute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation and processing of site identifier
|
||||
*
|
||||
* @param bool $isNew If true, we're dealing with a new record
|
||||
* @param string $identifier Given identifier to validate and process
|
||||
* @param int $rootPageId Page uid this identifier is bound to
|
||||
* @return mixed Verified / modified value
|
||||
*/
|
||||
protected function validateAndProcessIdentifier(bool $isNew, string $identifier, int $rootPageId)
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
// Normal "eval" processing of field first
|
||||
$identifier = $this->validateAndProcessValue('site', 'identifier', $identifier);
|
||||
if ($isNew) {
|
||||
// Verify no other site with this identifier exists. If so, find a new unique name as
|
||||
// identifier and show a flash message the identifier has been adapted
|
||||
try {
|
||||
$this->siteFinder->getSiteByIdentifier($identifier);
|
||||
// Force this identifier to be unique
|
||||
$originalIdentifier = $identifier;
|
||||
$identifier = $identifier . '-' . str_replace('.', '', uniqid((string)mt_rand(), true));
|
||||
$message = sprintf(
|
||||
$languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.identifierRenamed.message'),
|
||||
$originalIdentifier,
|
||||
$identifier
|
||||
);
|
||||
$messageTitle = $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.identifierRenamed.title');
|
||||
$flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $messageTitle, FlashMessage::WARNING, true);
|
||||
$flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
|
||||
$defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
|
||||
$defaultFlashMessageQueue->enqueue($flashMessage);
|
||||
} catch (SiteNotFoundException $e) {
|
||||
// Do nothing, this new identifier is ok
|
||||
}
|
||||
} else {
|
||||
// If this is an existing config, the site for this identifier must have the same rootPageId, otherwise
|
||||
// a user tried to rename a site identifier to a different site that already exists. If so, we do not rename
|
||||
// the site and show a flash message
|
||||
try {
|
||||
$site = $this->siteFinder->getSiteByIdentifier($identifier);
|
||||
if ($site->getRootPageId() !== $rootPageId) {
|
||||
// Find original value and keep this
|
||||
$origSite = $this->siteFinder->getSiteByRootPageId($rootPageId);
|
||||
$originalIdentifier = $identifier;
|
||||
$identifier = $origSite->getIdentifier();
|
||||
$message = sprintf(
|
||||
$languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.identifierExists.message'),
|
||||
$originalIdentifier,
|
||||
$identifier
|
||||
);
|
||||
$messageTitle = $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.identifierExists.title');
|
||||
$flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $messageTitle, FlashMessage::WARNING, true);
|
||||
$flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
|
||||
$defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
|
||||
$defaultFlashMessageQueue->enqueue($flashMessage);
|
||||
}
|
||||
} catch (SiteNotFoundException $e) {
|
||||
// User is renaming identifier which does not exist yet. That's ok
|
||||
}
|
||||
}
|
||||
return $identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple validation and processing method for incoming form field values.
|
||||
*
|
||||
* Note this does not support all TCA "eval" options but only what we really need.
|
||||
*
|
||||
* @param string $tableName Table name
|
||||
* @param string $fieldName Field name
|
||||
* @param mixed $fieldValue Incoming value from FormEngine
|
||||
* @return mixed Verified / modified value
|
||||
* @throws SiteValidationErrorException
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function validateAndProcessValue(string $tableName, string $fieldName, $fieldValue)
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$fieldConfig = $GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
|
||||
$handledEvals = [];
|
||||
if (!empty($fieldConfig['eval'])) {
|
||||
$evalArray = GeneralUtility::trimExplode(',', $fieldConfig['eval'], true);
|
||||
// Processing
|
||||
if (in_array('alphanum_x', $evalArray, true)) {
|
||||
$handledEvals[] = 'alphanum_x';
|
||||
$fieldValue = preg_replace('/[^a-zA-Z0-9_-]/', '', $fieldValue);
|
||||
}
|
||||
if (in_array('lower', $evalArray, true)) {
|
||||
$handledEvals[] = 'lower';
|
||||
$fieldValue = mb_strtolower($fieldValue, 'utf-8');
|
||||
}
|
||||
if (in_array('trim', $evalArray, true)) {
|
||||
$handledEvals[] = 'trim';
|
||||
$fieldValue = trim($fieldValue);
|
||||
}
|
||||
if (in_array('int', $evalArray, true)) {
|
||||
$handledEvals[] = 'int';
|
||||
$fieldValue = (int)$fieldValue;
|
||||
}
|
||||
// Validation throws - these should be handled client side already,
|
||||
// eg. 'required' being set and receiving empty, shouldn't happen server side
|
||||
if (in_array('required', $evalArray, true)) {
|
||||
$handledEvals[] = 'required';
|
||||
if (empty($fieldValue)) {
|
||||
$message = sprintf(
|
||||
$languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.required.message'),
|
||||
$fieldName
|
||||
);
|
||||
$messageTitle = $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.required.title');
|
||||
$flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $messageTitle, FlashMessage::WARNING, true);
|
||||
$flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
|
||||
$defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
|
||||
$defaultFlashMessageQueue->enqueue($flashMessage);
|
||||
throw new SiteValidationErrorException(
|
||||
'Field ' . $fieldName . ' is set to required, but received empty.',
|
||||
1521726421
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!empty(array_diff($evalArray, $handledEvals))) {
|
||||
throw new \RuntimeException('At least one not implemented \'eval\' in list ' . $fieldConfig['eval'], 1522491734);
|
||||
}
|
||||
}
|
||||
if (isset($fieldConfig['range']['lower'])) {
|
||||
$fieldValue = (int)$fieldValue < (int)$fieldConfig['range']['lower'] ? (int)$fieldConfig['range']['lower'] : (int)$fieldValue;
|
||||
}
|
||||
if (isset($fieldConfig['range']['upper'])) {
|
||||
$fieldValue = (int)$fieldValue > (int)$fieldConfig['range']['upper'] ? (int)$fieldConfig['range']['upper'] : (int)$fieldValue;
|
||||
}
|
||||
return $fieldValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Last sanitation method after all data has been gathered. Check integrity
|
||||
* of full record, manipulate if possible, or throw exception if unfixable broken.
|
||||
*
|
||||
* @param array $newSysSiteData Incoming data
|
||||
* @return array Updated data if needed
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function validateFullStructure(array $newSysSiteData): array
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
// Verify there are not two error handlers with the same error code
|
||||
if (isset($newSysSiteData['errorHandling']) && is_array($newSysSiteData['errorHandling'])) {
|
||||
$uniqueCriteria = [];
|
||||
$validChildren = [];
|
||||
foreach ($newSysSiteData['errorHandling'] as $child) {
|
||||
if (!isset($child['errorCode'])) {
|
||||
throw new \RuntimeException('No errorCode found', 1521788518);
|
||||
}
|
||||
if (!in_array((int)$child['errorCode'], $uniqueCriteria, true)) {
|
||||
$uniqueCriteria[] = (int)$child['errorCode'];
|
||||
$validChildren[] = $child;
|
||||
} else {
|
||||
$message = sprintf(
|
||||
$languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.duplicateErrorCode.message'),
|
||||
$child['errorCode']
|
||||
);
|
||||
$messageTitle = $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.duplicateErrorCode.title');
|
||||
$flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $messageTitle, FlashMessage::WARNING, true);
|
||||
$flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
|
||||
$defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
|
||||
$defaultFlashMessageQueue->enqueue($flashMessage);
|
||||
}
|
||||
}
|
||||
$newSysSiteData['errorHandling'] = $validChildren;
|
||||
}
|
||||
|
||||
// Verify there is only one inline child per sys_language record configured.
|
||||
if (!isset($newSysSiteData['languages']) || !is_array($newSysSiteData['languages']) || count($newSysSiteData['languages']) < 1) {
|
||||
throw new \RuntimeException(
|
||||
'No default language definition found. The interface does not allow this. Aborting',
|
||||
1521789306
|
||||
);
|
||||
}
|
||||
$uniqueCriteria = [];
|
||||
$validChildren = [];
|
||||
foreach ($newSysSiteData['languages'] as $child) {
|
||||
if (!isset($child['languageId'])) {
|
||||
throw new \RuntimeException('languageId not found', 1521789455);
|
||||
}
|
||||
if (!in_array((int)$child['languageId'], $uniqueCriteria, true)) {
|
||||
$uniqueCriteria[] = (int)$child['languageId'];
|
||||
$validChildren[] = $child;
|
||||
} else {
|
||||
$message = sprintf(
|
||||
$languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.duplicateLanguageId.title'),
|
||||
$child['languageId']
|
||||
);
|
||||
$messageTitle = $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.duplicateLanguageId.title');
|
||||
$flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $messageTitle, FlashMessage::WARNING, true);
|
||||
$flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
|
||||
$defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
|
||||
$defaultFlashMessageQueue->enqueue($flashMessage);
|
||||
}
|
||||
}
|
||||
$newSysSiteData['languages'] = $validChildren;
|
||||
|
||||
return $newSysSiteData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an existing configuration
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function deleteAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$siteIdentifier = $request->getQueryParams()['site'] ?? '';
|
||||
if (empty($siteIdentifier)) {
|
||||
throw new \RuntimeException('Not site identifier given', 1521565182);
|
||||
}
|
||||
// Verify site does exist, method throws if not
|
||||
GeneralUtility::makeInstance(SiteConfiguration::class, Environment::getConfigPath() . '/sites')->delete($siteIdentifier);
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
$overviewRoute = $uriBuilder->buildUriFromRoute('site_configuration', ['action' => 'overview']);
|
||||
return new RedirectResponse($overviewRoute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the Fluid View.
|
||||
*
|
||||
* @param string $templateName
|
||||
*/
|
||||
protected function initializeView(string $templateName): void
|
||||
{
|
||||
$this->view = GeneralUtility::makeInstance(StandaloneView::class);
|
||||
$this->view->setTemplate($templateName);
|
||||
$this->view->setTemplateRootPaths(['EXT:backend/Resources/Private/Templates/SiteConfiguration']);
|
||||
$this->view->setPartialRootPaths(['EXT:backend/Resources/Private/Partials']);
|
||||
$this->view->setLayoutRootPaths(['EXT:backend/Resources/Private/Layouts']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create document header buttons of "edit" action
|
||||
*/
|
||||
protected function configureEditViewDocHeader(): void
|
||||
{
|
||||
$iconFactory = $this->moduleTemplate->getIconFactory();
|
||||
$buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
|
||||
$lang = $this->getLanguageService();
|
||||
$closeButton = $buttonBar->makeLinkButton()
|
||||
->setHref('#')
|
||||
->setClasses('t3js-editform-close')
|
||||
->setTitle($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.closeDoc'))
|
||||
->setShowLabelText(true)
|
||||
->setIcon($iconFactory->getIcon('actions-close', Icon::SIZE_SMALL));
|
||||
$saveButton = $buttonBar->makeInputButton()
|
||||
->setTitle($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.saveDoc'))
|
||||
->setName('_savedok')
|
||||
->setValue('1')
|
||||
->setShowLabelText(true)
|
||||
->setForm('siteConfigurationController')
|
||||
->setIcon($iconFactory->getIcon('actions-document-save', Icon::SIZE_SMALL));
|
||||
$buttonBar->addButton($closeButton);
|
||||
$buttonBar->addButton($saveButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create document header buttons of "overview" action
|
||||
*/
|
||||
protected function configureOverViewDocHeader(): void
|
||||
{
|
||||
$iconFactory = $this->moduleTemplate->getIconFactory();
|
||||
$buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
|
||||
$reloadButton = $buttonBar->makeLinkButton()
|
||||
->setHref(GeneralUtility::getIndpEnv('REQUEST_URI'))
|
||||
->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.reload'))
|
||||
->setIcon($iconFactory->getIcon('actions-refresh', Icon::SIZE_SMALL));
|
||||
$buttonBar->addButton($reloadButton, ButtonBar::BUTTON_POSITION_RIGHT);
|
||||
if ($this->getBackendUser()->mayMakeShortcut()) {
|
||||
$getVars = ['id', 'route'];
|
||||
$shortcutButton = $buttonBar->makeShortcutButton()
|
||||
->setModuleName('site_configuration')
|
||||
->setGetVariables($getVars);
|
||||
$buttonBar->addButton($shortcutButton, ButtonBar::BUTTON_POSITION_RIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of pages that have 'is_siteroot' set
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getAllSitePages(): array
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
|
||||
$queryBuilder->getRestrictions()->removeByType(HiddenRestriction::class);
|
||||
$queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class, 0, false));
|
||||
$statement = $queryBuilder
|
||||
->select('*')
|
||||
->from('pages')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('sys_language_uid', 0),
|
||||
$queryBuilder->expr()->orX(
|
||||
$queryBuilder->expr()->andX(
|
||||
$queryBuilder->expr()->eq('pid', 0),
|
||||
$queryBuilder->expr()->neq('doktype', PageRepository::DOKTYPE_SYSFOLDER)
|
||||
),
|
||||
$queryBuilder->expr()->eq('is_siteroot', 1)
|
||||
)
|
||||
)
|
||||
->orderBy('pid')
|
||||
->addOrderBy('sorting')
|
||||
->execute();
|
||||
|
||||
$pages = [];
|
||||
while ($row = $statement->fetch()) {
|
||||
$row['rootline'] = BackendUtility::BEgetRootLine((int)$row['uid']);
|
||||
array_pop($row['rootline']);
|
||||
$row['rootline'] = array_reverse($row['rootline']);
|
||||
$i = 0;
|
||||
foreach ($row['rootline'] as &$record) {
|
||||
$record['margin'] = $i++ * 20;
|
||||
}
|
||||
$pages[(int)$row['uid']] = $row;
|
||||
}
|
||||
return $pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller;
|
||||
|
||||
/*
|
||||
* 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\Configuration\SiteTcaConfiguration;
|
||||
use TYPO3\CMS\Backend\Form\FormDataCompiler;
|
||||
use TYPO3\CMS\Backend\Form\FormDataGroup\SiteConfigurationDataGroup;
|
||||
use TYPO3\CMS\Backend\Form\InlineStackProcessor;
|
||||
use TYPO3\CMS\Backend\Form\NodeFactory;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* Site configuration FormEngine controller class. Receives inline "edit" and "new"
|
||||
* commands to expand / create site configuration inline records
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class SiteInlineAjaxController extends AbstractFormEngineAjaxController
|
||||
{
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// Bring site TCA into global scope.
|
||||
// @todo: We might be able to get rid of that later
|
||||
$GLOBALS['TCA'] = array_merge($GLOBALS['TCA'], GeneralUtility::makeInstance(SiteTcaConfiguration::class)->getTca());
|
||||
}
|
||||
|
||||
/**
|
||||
* Inline "create" new child of site configuration child records
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function newInlineChildAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$ajaxArguments = $request->getParsedBody()['ajax'] ?? $request->getQueryParams()['ajax'];
|
||||
$parentConfig = $this->extractSignedParentConfigFromRequest((string)$ajaxArguments['context']);
|
||||
$domObjectId = $ajaxArguments[0];
|
||||
$inlineFirstPid = $this->getInlineFirstPidFromDomObjectId($domObjectId);
|
||||
$childChildUid = null;
|
||||
if (isset($ajaxArguments[1]) && MathUtility::canBeInterpretedAsInteger($ajaxArguments[1])) {
|
||||
$childChildUid = (int)$ajaxArguments[1];
|
||||
}
|
||||
// Parse the DOM identifier, add the levels to the structure stack
|
||||
$inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
|
||||
$inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
|
||||
$inlineStackProcessor->injectAjaxConfiguration($parentConfig);
|
||||
$inlineTopMostParent = $inlineStackProcessor->getStructureLevel(0);
|
||||
// Parent, this table embeds the child table
|
||||
$parent = $inlineStackProcessor->getStructureLevel(-1);
|
||||
// Child, a record from this table should be rendered
|
||||
$child = $inlineStackProcessor->getUnstableStructure();
|
||||
if (MathUtility::canBeInterpretedAsInteger($child['uid'])) {
|
||||
// If uid comes in, it is the id of the record neighbor record "create after"
|
||||
$childVanillaUid = -1 * abs((int)$child['uid']);
|
||||
} else {
|
||||
// Else inline first Pid is the storage pid of new inline records
|
||||
$childVanillaUid = (int)$inlineFirstPid;
|
||||
}
|
||||
$childTableName = $parentConfig['foreign_table'];
|
||||
|
||||
$defaultDatabaseRow = [];
|
||||
if ($childTableName === 'site_language') {
|
||||
// Feed new site_language row with data from sys_language record if possible
|
||||
if ($childChildUid > 0) {
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language');
|
||||
$queryBuilder->getRestrictions()->removeByType(HiddenRestriction::class);
|
||||
$row = $queryBuilder->select('*')->from('sys_language')
|
||||
->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($childChildUid, \PDO::PARAM_INT)))
|
||||
->execute()->fetch();
|
||||
if (empty($row)) {
|
||||
throw new \RuntimeException('Referenced sys_language row not found', 1521783937);
|
||||
}
|
||||
if (!empty($row['language_isocode'])) {
|
||||
$defaultDatabaseRow['iso-639-1'] = $row['language_isocode'];
|
||||
$defaultDatabaseRow['base'] = '/' . $row['language_isocode'] . '/';
|
||||
}
|
||||
if (!empty($row['flag']) && $row['flag'] === 'multiple') {
|
||||
$defaultDatabaseRow['flag'] = 'global';
|
||||
} elseif (!empty($row)) {
|
||||
$defaultDatabaseRow['flag'] = $row['flag'];
|
||||
}
|
||||
if (!empty($row['title'])) {
|
||||
$defaultDatabaseRow['title'] = $row['title'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$formDataGroup = GeneralUtility::makeInstance(SiteConfigurationDataGroup::class);
|
||||
$formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
|
||||
$formDataCompilerInput = [
|
||||
'command' => 'new',
|
||||
'tableName' => $childTableName,
|
||||
'vanillaUid' => $childVanillaUid,
|
||||
'databaseRow' => $defaultDatabaseRow,
|
||||
'isInlineChild' => true,
|
||||
'inlineStructure' => $inlineStackProcessor->getStructure(),
|
||||
'inlineFirstPid' => $inlineFirstPid,
|
||||
'inlineParentUid' => $parent['uid'],
|
||||
'inlineParentTableName' => $parent['table'],
|
||||
'inlineParentFieldName' => $parent['field'],
|
||||
'inlineParentConfig' => $parentConfig,
|
||||
'inlineTopMostParentUid' => $inlineTopMostParent['uid'],
|
||||
'inlineTopMostParentTableName' => $inlineTopMostParent['table'],
|
||||
'inlineTopMostParentFieldName' => $inlineTopMostParent['field'],
|
||||
];
|
||||
if ($childChildUid) {
|
||||
$formDataCompilerInput['inlineChildChildUid'] = $childChildUid;
|
||||
}
|
||||
$childData = $formDataCompiler->compile($formDataCompilerInput);
|
||||
|
||||
if ($parentConfig['foreign_selector'] && $parentConfig['appearance']['useCombination']) {
|
||||
throw new \RuntimeException('useCombination not implemented in sites module', 1522493094);
|
||||
}
|
||||
|
||||
$childData['inlineParentUid'] = (int)$parent['uid'];
|
||||
$childData['renderType'] = 'inlineRecordContainer';
|
||||
$nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
|
||||
$childResult = $nodeFactory->create($childData)->render();
|
||||
|
||||
$jsonArray = [
|
||||
'data' => '',
|
||||
'stylesheetFiles' => [],
|
||||
'scriptCall' => [],
|
||||
];
|
||||
|
||||
// The HTML-object-id's prefix of the dynamically created record
|
||||
$objectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
|
||||
$objectPrefix = $objectName . '-' . $child['table'];
|
||||
$objectId = $objectPrefix . '-' . $childData['databaseRow']['uid'];
|
||||
$expandSingle = $parentConfig['appearance']['expandSingle'];
|
||||
if (!$child['uid']) {
|
||||
$jsonArray['scriptCall'][] = 'inline.domAddNewRecord(\'bottom\',' . GeneralUtility::quoteJSvalue($objectName . '_records') . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',json.data);';
|
||||
$jsonArray['scriptCall'][] = 'inline.memorizeAddRecord(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($childData['databaseRow']['uid']) . ',null,' . GeneralUtility::quoteJSvalue($childChildUid) . ');';
|
||||
} else {
|
||||
$jsonArray['scriptCall'][] = 'inline.domAddNewRecord(\'after\',' . GeneralUtility::quoteJSvalue($domObjectId . '_div') . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',json.data);';
|
||||
$jsonArray['scriptCall'][] = 'inline.memorizeAddRecord(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($childData['databaseRow']['uid']) . ',' . GeneralUtility::quoteJSvalue($child['uid']) . ',' . GeneralUtility::quoteJSvalue($childChildUid) . ');';
|
||||
}
|
||||
$jsonArray = $this->mergeChildResultIntoJsonResult($jsonArray, $childResult);
|
||||
if ($parentConfig['appearance']['useSortable']) {
|
||||
$inlineObjectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
|
||||
$jsonArray['scriptCall'][] = 'inline.createDragAndDropSorting(' . GeneralUtility::quoteJSvalue($inlineObjectName . '_records') . ');';
|
||||
}
|
||||
if (!$parentConfig['appearance']['collapseAll'] && $expandSingle) {
|
||||
$jsonArray['scriptCall'][] = 'inline.collapseAllRecords(' . GeneralUtility::quoteJSvalue($objectId) . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($childData['databaseRow']['uid']) . ');';
|
||||
}
|
||||
// Fade out and fade in the new record in the browser view to catch the user's eye
|
||||
$jsonArray['scriptCall'][] = 'inline.fadeOutFadeIn(' . GeneralUtility::quoteJSvalue($objectId . '_div') . ');';
|
||||
|
||||
return new JsonResponse($jsonArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the details of site configuration child records.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function openInlineChildAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$ajaxArguments = $request->getParsedBody()['ajax'] ?? $request->getQueryParams()['ajax'];
|
||||
|
||||
$domObjectId = $ajaxArguments[0];
|
||||
$inlineFirstPid = $this->getInlineFirstPidFromDomObjectId($domObjectId);
|
||||
$parentConfig = $this->extractSignedParentConfigFromRequest((string)$ajaxArguments['context']);
|
||||
|
||||
// Parse the DOM identifier, add the levels to the structure stack
|
||||
$inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
|
||||
$inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
|
||||
$inlineStackProcessor->injectAjaxConfiguration($parentConfig);
|
||||
|
||||
// Parent, this table embeds the child table
|
||||
$parent = $inlineStackProcessor->getStructureLevel(-1);
|
||||
$parentFieldName = $parent['field'];
|
||||
|
||||
// Set flag in config so that only the fields are rendered
|
||||
// @todo: Solve differently / rename / whatever
|
||||
$parentConfig['renderFieldsOnly'] = true;
|
||||
|
||||
$parentData = [
|
||||
'processedTca' => [
|
||||
'columns' => [
|
||||
$parentFieldName => [
|
||||
'config' => $parentConfig,
|
||||
],
|
||||
],
|
||||
],
|
||||
'tableName' => $parent['table'],
|
||||
'inlineFirstPid' => $inlineFirstPid,
|
||||
// Hand over given original return url to compile stack. Needed if inline children compile links to
|
||||
// another view (eg. edit metadata in a nested inline situation like news with inline content element image),
|
||||
// so the back link is still the link from the original request. See issue #82525. This is additionally
|
||||
// given down in TcaInline data provider to compiled children data.
|
||||
'returnUrl' => $parentConfig['originalReturnUrl'],
|
||||
];
|
||||
|
||||
// Child, a record from this table should be rendered
|
||||
$child = $inlineStackProcessor->getUnstableStructure();
|
||||
|
||||
$childData = $this->compileChild($parentData, $parentFieldName, (int)$child['uid'], $inlineStackProcessor->getStructure());
|
||||
|
||||
$childData['inlineParentUid'] = (int)$parent['uid'];
|
||||
$childData['renderType'] = 'inlineRecordContainer';
|
||||
$nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
|
||||
$childResult = $nodeFactory->create($childData)->render();
|
||||
|
||||
$jsonArray = [
|
||||
'data' => '',
|
||||
'stylesheetFiles' => [],
|
||||
'scriptCall' => [],
|
||||
];
|
||||
|
||||
// The HTML-object-id's prefix of the dynamically created record
|
||||
$objectPrefix = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid) . '-' . $child['table'];
|
||||
$objectId = $objectPrefix . '-' . (int)$child['uid'];
|
||||
$expandSingle = $parentConfig['appearance']['expandSingle'];
|
||||
$jsonArray['scriptCall'][] = 'inline.domAddRecordDetails(' . GeneralUtility::quoteJSvalue($domObjectId) . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . ($expandSingle ? '1' : '0') . ',json.data);';
|
||||
if ($parentConfig['foreign_unique']) {
|
||||
$jsonArray['scriptCall'][] = 'inline.removeUsed(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',\'' . (int)$child['uid'] . '\');';
|
||||
}
|
||||
$jsonArray = $this->mergeChildResultIntoJsonResult($jsonArray, $childResult);
|
||||
if ($parentConfig['appearance']['useSortable']) {
|
||||
$inlineObjectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
|
||||
$jsonArray['scriptCall'][] = 'inline.createDragAndDropSorting(' . GeneralUtility::quoteJSvalue($inlineObjectName . '_records') . ');';
|
||||
}
|
||||
if (!$parentConfig['appearance']['collapseAll'] && $expandSingle) {
|
||||
$jsonArray['scriptCall'][] = 'inline.collapseAllRecords(' . GeneralUtility::quoteJSvalue($objectId) . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',\'' . (int)$child['uid'] . '\');';
|
||||
}
|
||||
|
||||
return new JsonResponse($jsonArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a full child record
|
||||
*
|
||||
* @param array $parentData Result array of parent
|
||||
* @param string $parentFieldName Name of parent field
|
||||
* @param int $childUid Uid of child to compile
|
||||
* @param array $inlineStructure Current inline structure
|
||||
* @return array Full result array
|
||||
* @throws \RuntimeException
|
||||
*
|
||||
* @todo: This clones methods compileChild from TcaInline Provider. Find a better abstraction
|
||||
* @todo: to also encapsulate the more complex scenarios with combination child and friends.
|
||||
*/
|
||||
protected function compileChild(array $parentData, string $parentFieldName, int $childUid, array $inlineStructure): array
|
||||
{
|
||||
$parentConfig = $parentData['processedTca']['columns'][$parentFieldName]['config'];
|
||||
|
||||
$inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
|
||||
$inlineStackProcessor->initializeByGivenStructure($inlineStructure);
|
||||
$inlineTopMostParent = $inlineStackProcessor->getStructureLevel(0);
|
||||
|
||||
// @todo: do not use stack processor here ...
|
||||
$child = $inlineStackProcessor->getUnstableStructure();
|
||||
$childTableName = $child['table'];
|
||||
|
||||
$formDataGroup = GeneralUtility::makeInstance(SiteConfigurationDataGroup::class);
|
||||
$formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
|
||||
$formDataCompilerInput = [
|
||||
'command' => 'edit',
|
||||
'tableName' => $childTableName,
|
||||
'vanillaUid' => (int)$childUid,
|
||||
'returnUrl' => $parentData['returnUrl'],
|
||||
'isInlineChild' => true,
|
||||
'inlineStructure' => $inlineStructure,
|
||||
'inlineFirstPid' => $parentData['inlineFirstPid'],
|
||||
'inlineParentConfig' => $parentConfig,
|
||||
'isInlineAjaxOpeningContext' => true,
|
||||
|
||||
// values of the current parent element
|
||||
// it is always a string either an id or new...
|
||||
'inlineParentUid' => $parentData['databaseRow']['uid'],
|
||||
'inlineParentTableName' => $parentData['tableName'],
|
||||
'inlineParentFieldName' => $parentFieldName,
|
||||
|
||||
// values of the top most parent element set on first level and not overridden on following levels
|
||||
'inlineTopMostParentUid' => $inlineTopMostParent['uid'],
|
||||
'inlineTopMostParentTableName' => $inlineTopMostParent['table'],
|
||||
'inlineTopMostParentFieldName' => $inlineTopMostParent['field'],
|
||||
];
|
||||
if ($parentConfig['foreign_selector'] && $parentConfig['appearance']['useCombination']) {
|
||||
throw new \RuntimeException('useCombination not implemented in sites module', 1522493095);
|
||||
}
|
||||
return $formDataCompiler->compile($formDataCompilerInput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge stuff from child array into json array.
|
||||
* This method is needed since ajax handling methods currently need to put scriptCalls before and after child code.
|
||||
*
|
||||
* @param array $jsonResult Given json result
|
||||
* @param array $childResult Given child result
|
||||
* @return array Merged json array
|
||||
*/
|
||||
protected function mergeChildResultIntoJsonResult(array $jsonResult, array $childResult): array
|
||||
{
|
||||
$jsonResult['data'] .= $childResult['html'];
|
||||
$jsonResult['stylesheetFiles'] = [];
|
||||
foreach ($childResult['stylesheetFiles'] as $stylesheetFile) {
|
||||
$jsonResult['stylesheetFiles'][] = $this->getRelativePathToStylesheetFile($stylesheetFile);
|
||||
}
|
||||
if (!empty($childResult['inlineData'])) {
|
||||
$jsonResult['scriptCall'][] = 'inline.addToDataArray(' . json_encode($childResult['inlineData']) . ');';
|
||||
}
|
||||
if (!empty($childResult['additionalJavaScriptSubmit'])) {
|
||||
$additionalJavaScriptSubmit = implode('', $childResult['additionalJavaScriptSubmit']);
|
||||
$additionalJavaScriptSubmit = str_replace([CR, LF], '', $additionalJavaScriptSubmit);
|
||||
$jsonResult['scriptCall'][] = 'TBE_EDITOR.addActionChecks("submit", "' . addslashes($additionalJavaScriptSubmit) . '");';
|
||||
}
|
||||
foreach ($childResult['additionalJavaScriptPost'] as $singleAdditionalJavaScriptPost) {
|
||||
$jsonResult['scriptCall'][] = $singleAdditionalJavaScriptPost;
|
||||
}
|
||||
if (!empty($childResult['additionalInlineLanguageLabelFiles'])) {
|
||||
$labels = [];
|
||||
foreach ($childResult['additionalInlineLanguageLabelFiles'] as $additionalInlineLanguageLabelFile) {
|
||||
ArrayUtility::mergeRecursiveWithOverrule(
|
||||
$labels,
|
||||
$this->getLabelsFromLocalizationFile($additionalInlineLanguageLabelFile)
|
||||
);
|
||||
}
|
||||
$javaScriptCode = [];
|
||||
$javaScriptCode[] = 'if (typeof TYPO3 === \'undefined\' || typeof TYPO3.lang === \'undefined\') {';
|
||||
$javaScriptCode[] = ' TYPO3.lang = {}';
|
||||
$javaScriptCode[] = '}';
|
||||
$javaScriptCode[] = 'var additionalInlineLanguageLabels = ' . json_encode($labels) . ';';
|
||||
$javaScriptCode[] = 'for (var attributeName in additionalInlineLanguageLabels) {';
|
||||
$javaScriptCode[] = ' if (typeof TYPO3.lang[attributeName] === \'undefined\') {';
|
||||
$javaScriptCode[] = ' TYPO3.lang[attributeName] = additionalInlineLanguageLabels[attributeName]';
|
||||
$javaScriptCode[] = ' }';
|
||||
$javaScriptCode[] = '}';
|
||||
|
||||
$jsonResult['scriptCall'][] = implode(LF, $javaScriptCode);
|
||||
}
|
||||
$requireJsModule = $this->createExecutableStringRepresentationOfRegisteredRequireJsModules($childResult);
|
||||
$jsonResult['scriptCall'] = array_merge($requireJsModule, $jsonResult['scriptCall']);
|
||||
|
||||
return $jsonResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inline ajax helper method.
|
||||
*
|
||||
* Validates the config that is transferred over the wire to provide the
|
||||
* correct TCA config for the parent table
|
||||
*
|
||||
* @param string $contextString
|
||||
* @throws \RuntimeException
|
||||
* @return array
|
||||
*/
|
||||
protected function extractSignedParentConfigFromRequest(string $contextString): array
|
||||
{
|
||||
if ($contextString === '') {
|
||||
throw new \RuntimeException('Empty context string given', 1522771624);
|
||||
}
|
||||
$context = json_decode($contextString, true);
|
||||
if (empty($context['config'])) {
|
||||
throw new \RuntimeException('Empty context config section given', 1522771632);
|
||||
}
|
||||
if (!hash_equals(GeneralUtility::hmac((string)$context['config'], 'InlineContext'), (string)$context['hmac'])) {
|
||||
throw new \RuntimeException('Hash does not validate', 1522771640);
|
||||
}
|
||||
return json_decode($context['config'], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get inlineFirstPid from a given objectId string
|
||||
*
|
||||
* @param string $domObjectId The id attribute of an element
|
||||
* @return int|null Pid or null
|
||||
*/
|
||||
protected function getInlineFirstPidFromDomObjectId(string $domObjectId): ?int
|
||||
{
|
||||
// Substitute FlexForm addition and make parsing a bit easier
|
||||
$domObjectId = str_replace('---', ':', $domObjectId);
|
||||
// The starting pattern of an object identifier (e.g. "data-<firstPidValue>-<anything>)
|
||||
$pattern = '/^data' . '-' . '(.+?)' . '-' . '(.+)$/';
|
||||
if (preg_match($pattern, $domObjectId, $match)) {
|
||||
return (int)$match[1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller;
|
||||
|
||||
/*
|
||||
* 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 TYPO3\CMS\Backend\Backend\ToolbarItems\SystemInformationToolbarItem;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Controller for system information processing
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class SystemInformationController
|
||||
{
|
||||
/**
|
||||
* @var SystemInformationToolbarItem
|
||||
*/
|
||||
protected $toolbarItem;
|
||||
|
||||
/**
|
||||
* Set up dependencies
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->toolbarItem = GeneralUtility::makeInstance(SystemInformationToolbarItem::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the menu for AJAX calls
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function renderMenuAction(): ResponseInterface
|
||||
{
|
||||
return new HtmlResponse($this->toolbarItem->getDropDown());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller;
|
||||
|
||||
/*
|
||||
* 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\Configuration\BackendUserConfiguration;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* A wrapper class to call BE_USER->uc
|
||||
* used for AJAX and TYPO3.Storage JS object
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class UserSettingsController
|
||||
{
|
||||
/**
|
||||
* @var BackendUserConfiguration
|
||||
*/
|
||||
protected $backendUserConfiguration;
|
||||
|
||||
/**
|
||||
* Initializes the backendUserConfiguration
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->backendUserConfiguration = GeneralUtility::makeInstance(BackendUserConfiguration::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes all AJAX calls and returns a JSON for the data
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function processAjaxRequest(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
// do the regular / main logic, depending on the action parameter
|
||||
$action = $request->getParsedBody()['action'] ?? $request->getQueryParams()['action'] ?? '';
|
||||
$key = $request->getParsedBody()['key'] ?? $request->getQueryParams()['key'] ?? '';
|
||||
$value = $request->getParsedBody()['value'] ?? $request->getQueryParams()['value'] ?? '';
|
||||
$data = $this->processRequest($action, $key, $value);
|
||||
|
||||
return (new JsonResponse())->setPayload($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process data
|
||||
*
|
||||
* @param string $action
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
* @return mixed
|
||||
*
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function process($action, $key = '', $value = '')
|
||||
{
|
||||
trigger_error('UserSettingsController->process() will be replaced by protected method processRequest() in TYPO3 v10.0. Do not call from other extensions.', E_USER_DEPRECATED);
|
||||
return $this->processRequest($action, $key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process data
|
||||
*
|
||||
* @param string $action
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
*/
|
||||
protected function processRequest(string $action, string $key = '', $value = '')
|
||||
{
|
||||
switch ($action) {
|
||||
case 'get':
|
||||
$content = $this->backendUserConfiguration->get($key);
|
||||
break;
|
||||
case 'getAll':
|
||||
$content = $this->backendUserConfiguration->getAll();
|
||||
break;
|
||||
case 'set':
|
||||
$this->backendUserConfiguration->set($key, $value);
|
||||
$content = $this->backendUserConfiguration->getAll();
|
||||
break;
|
||||
case 'addToList':
|
||||
$this->backendUserConfiguration->addToList($key, $value);
|
||||
$content = $this->backendUserConfiguration->getAll();
|
||||
break;
|
||||
case 'removeFromList':
|
||||
$this->backendUserConfiguration->removeFromList($key, $value);
|
||||
$content = $this->backendUserConfiguration->getAll();
|
||||
break;
|
||||
case 'unset':
|
||||
$this->backendUserConfiguration->unsetOption($key);
|
||||
$content = $this->backendUserConfiguration->getAll();
|
||||
break;
|
||||
case 'clear':
|
||||
$this->backendUserConfiguration->clear();
|
||||
$content = ['result' => true];
|
||||
break;
|
||||
default:
|
||||
$content = ['result' => false];
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Controller\Wizard;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Backend\Template\DocumentTemplate;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
||||
|
||||
/**
|
||||
* Class AbstractWizardController
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class AbstractWizardController
|
||||
{
|
||||
/**
|
||||
* Checks access for element
|
||||
*
|
||||
* @param string $table Table name
|
||||
* @param int $uid Record uid
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkEditAccess($table, $uid)
|
||||
{
|
||||
$record = BackendUtility::getRecord($table, $uid);
|
||||
BackendUtility::fixVersioningPid($table, $record);
|
||||
if (is_array($record)) {
|
||||
// If pages:
|
||||
if ($table === 'pages') {
|
||||
$calculatedPermissions = $this->getBackendUserAuthentication()->calcPerms($record);
|
||||
$hasAccess = $calculatedPermissions & Permission::PAGE_EDIT;
|
||||
} else {
|
||||
// Fetching pid-record first.
|
||||
$calculatedPermissions = $this->getBackendUserAuthentication()->calcPerms(
|
||||
BackendUtility::getRecord('pages', $record['pid'])
|
||||
);
|
||||
$hasAccess = $calculatedPermissions & Permission::CONTENT_EDIT;
|
||||
}
|
||||
// Check internals regarding access:
|
||||
if ($hasAccess) {
|
||||
$hasAccess = $this->getBackendUserAuthentication()->recordEditAccessInternals($table, $record);
|
||||
}
|
||||
} else {
|
||||
$hasAccess = false;
|
||||
}
|
||||
return (bool)$hasAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of BackendUserAuthentication
|
||||
*
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUserAuthentication()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of LanguageService
|
||||
*
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of DocumentTemplate
|
||||
*
|
||||
* @return DocumentTemplate
|
||||
*/
|
||||
protected function getDocumentTemplate()
|
||||
{
|
||||
return $GLOBALS['TBE_TEMPLATE'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller\Wizard;
|
||||
|
||||
/*
|
||||
* 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\Form\FormDataCompiler;
|
||||
use TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord;
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Compatibility\PublicPropertyDeprecationTrait;
|
||||
use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
|
||||
use TYPO3\CMS\Core\DataHandling\DataHandler;
|
||||
use TYPO3\CMS\Core\Http\RedirectResponse;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\HttpUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* Script Class for adding new items to a group/select field. Performs proper redirection as needed.
|
||||
* Script is typically called after new child record was added and then adds the new child to select value of parent.
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class AddController extends AbstractWizardController
|
||||
{
|
||||
use PublicPropertyDeprecationTrait;
|
||||
|
||||
/**
|
||||
* Properties which have been moved to protected status from public
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $deprecatedPublicProperties = [
|
||||
'content' => 'Using $content of class AddController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'processDataFlag' => 'Using $processDataFlag of class AddController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'pid' => 'Using $pid of class AddController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'table' => 'Using $table of class AddController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'id' => 'Using $id of class AddController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'P' => 'Using $P of class AddController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'returnEditConf' => 'Using $returnEditConf of class AddController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
];
|
||||
/**
|
||||
* Content accumulation for the module.
|
||||
*
|
||||
* @var string
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, unused
|
||||
*/
|
||||
protected $content;
|
||||
|
||||
/**
|
||||
* If set, the DataHandler class is loaded and used to add the returning ID to the parent record.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $processDataFlag = 0;
|
||||
|
||||
/**
|
||||
* Create new record -pid (pos/neg). If blank, return immediately
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $pid;
|
||||
|
||||
/**
|
||||
* The parent table we are working on.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table;
|
||||
|
||||
/**
|
||||
* Loaded with the created id of a record FormEngine returns ...
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* Wizard parameters, coming from TCEforms linking to the wizard.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $P;
|
||||
|
||||
/**
|
||||
* Information coming back from the FormEngine script, telling what the table/id was of the newly created record.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $returnEditConf;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->getLanguageService()->includeLLFile('EXT:core/Resources/Private/Language/locallang_wizards.xlf');
|
||||
// @deprecated since TYPO3 v9, will be moved out of __construct() in TYPO3 v10.0
|
||||
$this->init($GLOBALS['TYPO3_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
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function mainAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
return $this->processRequest($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function
|
||||
* Will issue a location-header, redirecting either BACK or to a new FormEngine instance...
|
||||
*
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function main()
|
||||
{
|
||||
trigger_error('AddController->main() will be replaced by protected method processRequest() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
|
||||
|
||||
$response = $this->processRequest($GLOBALS['TYPO3_REQUEST']);
|
||||
HttpUtility::redirect($response->getHeaders()['location'][0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization of the class.
|
||||
* @param ServerRequestInterface $request
|
||||
*/
|
||||
protected function init(ServerRequestInterface $request): void
|
||||
{
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$queryParams = $request->getQueryParams();
|
||||
// Init GPvars:
|
||||
$this->P = $parsedBody['P'] ?? $queryParams['P'] ?? [];
|
||||
$this->returnEditConf = $parsedBody['returnEditConf'] ?? $queryParams['returnEditConf'] ?? null;
|
||||
// Get this record
|
||||
$record = BackendUtility::getRecord($this->P['table'], $this->P['uid']);
|
||||
// Set table:
|
||||
$this->table = $this->P['params']['table'];
|
||||
// Get TSconfig for it.
|
||||
$TSconfig = BackendUtility::getTCEFORM_TSconfig(
|
||||
$this->P['table'],
|
||||
is_array($record) ? $record : ['pid' => $this->P['pid']]
|
||||
);
|
||||
// Set [params][pid]
|
||||
if (strpos($this->P['params']['pid'], '###') === 0 && substr($this->P['params']['pid'], -3) === '###') {
|
||||
$keyword = substr($this->P['params']['pid'], 3, -3);
|
||||
if (strpos($keyword, 'PAGE_TSCONFIG_') === 0) {
|
||||
$this->pid = (int)$TSconfig[$this->P['field']][$keyword];
|
||||
} else {
|
||||
$this->pid = (int)$TSconfig['_' . $keyword];
|
||||
}
|
||||
} else {
|
||||
$this->pid = (int)$this->P['params']['pid'];
|
||||
}
|
||||
// Return if new record as parent (not possibly/allowed)
|
||||
if ($this->pid === '') {
|
||||
// HTTP Redirect is performed by processRequest()
|
||||
return;
|
||||
}
|
||||
// Else proceed:
|
||||
// If a new id has returned from a newly created record...
|
||||
if ($this->returnEditConf) {
|
||||
$editConfiguration = json_decode($this->returnEditConf, true);
|
||||
if (is_array($editConfiguration[$this->table]) && MathUtility::canBeInterpretedAsInteger($this->P['uid'])) {
|
||||
// Getting id and cmd from returning editConf array.
|
||||
reset($editConfiguration[$this->table]);
|
||||
$this->id = (int)key($editConfiguration[$this->table]);
|
||||
$cmd = current($editConfiguration[$this->table]);
|
||||
// ... and if everything seems OK we will register some classes for inclusion and instruct the object
|
||||
// to perform processing later.
|
||||
if ($this->P['params']['setValue']
|
||||
&& $cmd === 'edit'
|
||||
&& $this->id
|
||||
&& $this->P['table']
|
||||
&& $this->P['field'] && $this->P['uid']
|
||||
) {
|
||||
$liveRecord = BackendUtility::getLiveVersionOfRecord($this->table, $this->id, 'uid');
|
||||
if ($liveRecord) {
|
||||
$this->id = $liveRecord['uid'];
|
||||
}
|
||||
$this->processDataFlag = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function
|
||||
* Will issue a location-header, redirecting either BACK or to a new FormEngine instance...
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function processRequest(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
// Return if new record as parent (not possibly/allowed)
|
||||
if ($this->pid === '') {
|
||||
return new RedirectResponse(GeneralUtility::sanitizeLocalUrl($this->P['returnUrl']));
|
||||
}
|
||||
|
||||
if ($this->returnEditConf) {
|
||||
if ($this->processDataFlag) {
|
||||
// Because OnTheFly can't handle MM relations with intermediate tables we use TcaDatabaseRecord here
|
||||
// Otherwise already stored relations are overwritten with the new entry
|
||||
/** @var TcaDatabaseRecord $formDataGroup */
|
||||
$formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
|
||||
/** @var FormDataCompiler $formDataCompiler */
|
||||
$formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
|
||||
$input = [
|
||||
'tableName' => $this->P['table'],
|
||||
'vanillaUid' => (int)$this->P['uid'],
|
||||
'command' => 'edit',
|
||||
];
|
||||
$result = $formDataCompiler->compile($input);
|
||||
$currentParentRow = $result['databaseRow'];
|
||||
|
||||
// If that record was found (should absolutely be...), then init DataHandler and set, prepend or append
|
||||
// the record
|
||||
if (is_array($currentParentRow)) {
|
||||
/** @var DataHandler $dataHandler */
|
||||
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
|
||||
$data = [];
|
||||
$recordId = $this->table . '_' . $this->id;
|
||||
// Setting the new field data:
|
||||
// If the field is a flexForm field, work with the XML structure instead:
|
||||
if ($this->P['flexFormPath']) {
|
||||
// Current value of flexForm path:
|
||||
$currentFlexFormData = $currentParentRow[$this->P['field']];
|
||||
/** @var FlexFormTools $flexFormTools */
|
||||
$flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
|
||||
$currentFlexFormValueByPath = $flexFormTools->getArrayValueByPath(
|
||||
$this->P['flexFormPath'],
|
||||
$currentFlexFormData
|
||||
);
|
||||
|
||||
// Compile currentFlexFormData to functional string
|
||||
$currentFlexFormValues = [];
|
||||
foreach ($currentFlexFormValueByPath as $value) {
|
||||
if (is_array($value)) {
|
||||
// group fields are always resolved to array
|
||||
$currentFlexFormValues[] = $value['table'] . '_' . $value['uid'];
|
||||
} else {
|
||||
// but select fields may be uids only
|
||||
$currentFlexFormValues[] = $value;
|
||||
}
|
||||
}
|
||||
$currentFlexFormValue = implode(',', $currentFlexFormValues);
|
||||
|
||||
$insertValue = '';
|
||||
switch ((string)$this->P['params']['setValue']) {
|
||||
case 'set':
|
||||
$insertValue = $recordId;
|
||||
break;
|
||||
case 'append':
|
||||
$insertValue = $currentFlexFormValue . ',' . $recordId;
|
||||
break;
|
||||
case 'prepend':
|
||||
$insertValue = $recordId . ',' . $currentFlexFormValue;
|
||||
break;
|
||||
}
|
||||
$insertValue = implode(',', GeneralUtility::trimExplode(',', $insertValue, true));
|
||||
$data[$this->P['table']][$this->P['uid']][$this->P['field']] = [];
|
||||
$flexFormTools->setArrayValueByPath(
|
||||
$this->P['flexFormPath'],
|
||||
$data[$this->P['table']][$this->P['uid']][$this->P['field']],
|
||||
$insertValue
|
||||
);
|
||||
} else {
|
||||
$currentValue = $currentParentRow[$this->P['field']];
|
||||
|
||||
// Normalize CSV values
|
||||
if (!is_array($currentValue)) {
|
||||
$currentValue = GeneralUtility::trimExplode(',', $currentValue, true);
|
||||
}
|
||||
|
||||
// Normalize all items to "<table>_<uid>" format
|
||||
$currentValue = array_map(function ($item) {
|
||||
// Handle per-item table for "group" elements
|
||||
if (is_array($item)) {
|
||||
$item = $item['table'] . '_' . $item['uid'];
|
||||
} else {
|
||||
$item = $this->table . '_' . $item;
|
||||
}
|
||||
|
||||
return $item;
|
||||
}, $currentValue);
|
||||
|
||||
switch ((string)$this->P['params']['setValue']) {
|
||||
case 'set':
|
||||
$currentValue = [$recordId];
|
||||
break;
|
||||
case 'append':
|
||||
$currentValue[] = $recordId;
|
||||
break;
|
||||
case 'prepend':
|
||||
array_unshift($currentValue, $recordId);
|
||||
break;
|
||||
}
|
||||
|
||||
$data[$this->P['table']][$this->P['uid']][$this->P['field']] = implode(',', $currentValue);
|
||||
}
|
||||
// Submit the data:
|
||||
$dataHandler->start($data, []);
|
||||
$dataHandler->process_datamap();
|
||||
}
|
||||
}
|
||||
// Return to the parent FormEngine record editing session:
|
||||
return new RedirectResponse(GeneralUtility::sanitizeLocalUrl($this->P['returnUrl']));
|
||||
}
|
||||
|
||||
// Redirecting to FormEngine with instructions to create a new record
|
||||
// AND when closing to return back with information about that records ID etc.
|
||||
/** @var \TYPO3\CMS\Core\Http\NormalizedParams */
|
||||
$normalizedParams = $request->getAttribute('normalizedParams');
|
||||
/** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
$redirectUrl = (string)$uriBuilder->buildUriFromRoute('record_edit', [
|
||||
'returnEditConf' => 1,
|
||||
'edit[' . $this->P['params']['table'] . '][' . $this->pid . ']' => 'new',
|
||||
'returnUrl' => $normalizedParams->getRequestUri(),
|
||||
]);
|
||||
|
||||
return new RedirectResponse($redirectUrl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller\Wizard;
|
||||
|
||||
/*
|
||||
* 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\Core\Compatibility\PublicPropertyDeprecationTrait;
|
||||
use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
|
||||
use TYPO3\CMS\Core\Database\RelationHandler;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Http\RedirectResponse;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\HttpUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* Script Class for redirecting a backend user to the editing form when an "Edit wizard" link was clicked in FormEngine somewhere
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class EditController extends AbstractWizardController
|
||||
{
|
||||
use PublicPropertyDeprecationTrait;
|
||||
|
||||
/**
|
||||
* Properties which have been moved to protected status from public
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $deprecatedPublicProperties = [
|
||||
'P' => 'Using $P of class EditController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'doClose' => 'Using $doClose of class EditController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Wizard parameters, coming from FormEngine linking to the wizard.
|
||||
*
|
||||
* Contains the following parts:
|
||||
* - table
|
||||
* - field
|
||||
* - formName
|
||||
* - hmac
|
||||
* - fieldChangeFunc
|
||||
* - fieldChangeFuncHash
|
||||
* - currentValue
|
||||
* - currentSelectedValues
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $P;
|
||||
|
||||
/**
|
||||
* Boolean; if set, the window will be closed by JavaScript
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $doClose;
|
||||
|
||||
/**
|
||||
* A little JavaScript to close the open window.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $closeWindow = '<script language="javascript" type="text/javascript">close();</script>';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->getLanguageService()->includeLLFile('EXT:core/Resources/Private/Language/locallang_wizards.xlf');
|
||||
|
||||
// @deprecated since TYPO3 v9, will be moved out of __construct() in TYPO3 v10.0
|
||||
$this->init($GLOBALS['TYPO3_REQUEST']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization of the script
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
*/
|
||||
protected function init(ServerRequestInterface $request)
|
||||
{
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$queryParams = $request->getQueryParams();
|
||||
|
||||
$this->P = $parsedBody['P'] ?? $queryParams['P'] ?? [];
|
||||
|
||||
// Used for the return URL to FormEngine so that we can close the window.
|
||||
$this->doClose = $parsedBody['doClose'] ?? $queryParams['doClose'] ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function mainAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$content = $this->processRequest($request);
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function
|
||||
* Makes a header-location redirect to an edit form IF POSSIBLE from the passed data - otherwise the window will
|
||||
* just close.
|
||||
*
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
* @return string
|
||||
*/
|
||||
public function main()
|
||||
{
|
||||
trigger_error('EditController->main() will be set to protected in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
|
||||
$request = $GLOBALS['TYPO3_REQUEST'];
|
||||
|
||||
$response = $this->processRequest($request);
|
||||
|
||||
if ($response instanceof RedirectResponse) {
|
||||
HttpUtility::redirect($response->getHeaders()['location'][0]);
|
||||
} else {
|
||||
return $response->getBody()->getContents();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process request function
|
||||
* Makes a header-location redirect to an edit form IF POSSIBLE from the passed data - otherwise the window will
|
||||
* just close.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function processRequest(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
if ($this->doClose) {
|
||||
return new HtmlResponse($this->closeWindow);
|
||||
}
|
||||
// Initialize:
|
||||
$table = $this->P['table'];
|
||||
$field = $this->P['field'];
|
||||
|
||||
if (empty($this->P['flexFormDataStructureIdentifier'])) {
|
||||
// If there is not flex data structure identifier, field config is found in globals
|
||||
$config = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
|
||||
} else {
|
||||
// If there is a flex data structure identifier, parse that data structure and
|
||||
// fetch config defined by given flex path
|
||||
$flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
|
||||
$dataStructure = $flexFormTools->parseDataStructureByIdentifier($this->P['flexFormDataStructureIdentifier']);
|
||||
$config = $flexFormTools->getArrayValueByPath($this->P['flexFormDataStructurePath'], $dataStructure);
|
||||
if (!is_array($config)) {
|
||||
throw new \RuntimeException(
|
||||
'Something went wrong finding flex path ' . $this->P['flexFormDataStructurePath']
|
||||
. ' in data structure identified by ' . $this->P['flexFormDataStructureIdentifier'],
|
||||
1537356346
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
|
||||
$urlParameters = [
|
||||
'returnUrl' => (string)$uriBuilder->buildUriFromRoute('wizard_edit', ['doClose' => 1])
|
||||
];
|
||||
|
||||
// Detecting the various allowed field type setups and acting accordingly.
|
||||
if (is_array($config)
|
||||
&& $config['type'] === 'select'
|
||||
&& !$config['MM']
|
||||
&& $config['maxitems'] <= 1
|
||||
&& MathUtility::canBeInterpretedAsInteger($this->P['currentValue'])
|
||||
&& $this->P['currentValue']
|
||||
&& $config['foreign_table']
|
||||
) {
|
||||
// SINGLE value
|
||||
$urlParameters['edit[' . $config['foreign_table'] . '][' . $this->P['currentValue'] . ']'] = 'edit';
|
||||
// Redirect to FormEngine
|
||||
$url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
|
||||
return new RedirectResponse($url);
|
||||
}
|
||||
|
||||
if (is_array($config)
|
||||
&& $this->P['currentSelectedValues']
|
||||
&& (
|
||||
$config['type'] === 'select'
|
||||
&& $config['foreign_table']
|
||||
|| $config['type'] === 'group'
|
||||
&& $config['internal_type'] === 'db'
|
||||
)
|
||||
) {
|
||||
// MULTIPLE VALUES:
|
||||
// Init settings:
|
||||
$allowedTables = $config['type'] === 'group' ? $config['allowed'] : $config['foreign_table'];
|
||||
$prependName = 1;
|
||||
// Selecting selected values into an array:
|
||||
$relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
|
||||
$relationHandler->start($this->P['currentSelectedValues'], $allowedTables);
|
||||
$value = $relationHandler->getValueArray($prependName);
|
||||
// Traverse that array and make parameters for FormEngine
|
||||
foreach ($value as $rec) {
|
||||
$recTableUidParts = GeneralUtility::revExplode('_', $rec, 2);
|
||||
$urlParameters['edit[' . $recTableUidParts[0] . '][' . $recTableUidParts[1] . ']'] = 'edit';
|
||||
}
|
||||
// Redirect to FormEngine
|
||||
$url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
|
||||
|
||||
return new RedirectResponse($url);
|
||||
}
|
||||
return new HtmlResponse($this->closeWindow);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller\Wizard;
|
||||
|
||||
/*
|
||||
* 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\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
|
||||
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
|
||||
/**
|
||||
* Wizard for rendering image manipulation view
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class ImageManipulationController
|
||||
{
|
||||
/**
|
||||
* @var StandaloneView
|
||||
*/
|
||||
private $templateView;
|
||||
|
||||
/**
|
||||
* @param StandaloneView $templateView
|
||||
*/
|
||||
public function __construct(StandaloneView $templateView = null)
|
||||
{
|
||||
if (!$templateView) {
|
||||
$templateView = GeneralUtility::makeInstance(StandaloneView::class);
|
||||
$templateView->setLayoutRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Layouts/')]);
|
||||
$templateView->setPartialRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Partials/ImageManipulation/')]);
|
||||
$templateView->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates/ImageManipulation/ImageManipulationWizard.html'));
|
||||
}
|
||||
$this->templateView = $templateView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the HTML for the wizard inside the modal
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface $response
|
||||
*/
|
||||
public function getWizardContent(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
if ($this->isSignatureValid($request)) {
|
||||
$parsedBody = json_decode($request->getParsedBody()['arguments'], true);
|
||||
$fileUid = $parsedBody['image'];
|
||||
$image = null;
|
||||
if (MathUtility::canBeInterpretedAsInteger($fileUid)) {
|
||||
try {
|
||||
$image = ResourceFactory::getInstance()->getFileObject($fileUid);
|
||||
} catch (FileDoesNotExistException $e) {
|
||||
}
|
||||
}
|
||||
$viewData = [
|
||||
'image' => $image,
|
||||
'cropVariants' => $parsedBody['cropVariants']
|
||||
];
|
||||
$content = $this->templateView->renderSection('Main', $viewData);
|
||||
return new HtmlResponse($content);
|
||||
}
|
||||
return new HtmlResponse('', 403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if hmac signature is correct
|
||||
*
|
||||
* @param ServerRequestInterface $request the request with the POST parameters
|
||||
* @return bool
|
||||
*/
|
||||
protected function isSignatureValid(ServerRequestInterface $request): bool
|
||||
{
|
||||
$token = GeneralUtility::hmac($request->getParsedBody()['arguments'], 'ajax_wizard_image_manipulation');
|
||||
return hash_equals($token, $request->getParsedBody()['signature']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller\Wizard;
|
||||
|
||||
/*
|
||||
* 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\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Compatibility\PublicPropertyDeprecationTrait;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\HttpUtility;
|
||||
|
||||
/**
|
||||
* Script Class for redirecting the user to the Web > List module if a wizard-link has been clicked in FormEngine
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class ListController extends AbstractWizardController
|
||||
{
|
||||
use PublicPropertyDeprecationTrait;
|
||||
|
||||
/**
|
||||
* Properties which have been moved to protected status from public
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $deprecatedPublicProperties = [
|
||||
'pid' => 'Using $pid of class ListController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'P' => 'Using $P of class ListController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'table' => 'Using $table of class ListController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'id' => 'Using $id of class ListController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
];
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $pid;
|
||||
|
||||
/**
|
||||
* Wizard parameters, coming from FormEngine linking to the wizard.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $P;
|
||||
|
||||
/**
|
||||
* Table to show, if none, then all tables are listed in list module.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table;
|
||||
|
||||
/**
|
||||
* Page id to list.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* Initialization of the class, setting GPvars.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->getLanguageService()->includeLLFile('EXT:core/Resources/Private/Language/locallang_wizards.xlf');
|
||||
|
||||
// @deprecated since TYPO3 v9, will be obsolete in TYPO3 v10.0
|
||||
$request = $GLOBALS['TYPO3_REQUEST'];
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$queryParams = $request->getQueryParams();
|
||||
|
||||
$this->P = $parsedBody['P'] ?? $queryParams['P'] ?? null;
|
||||
$this->table = $parsedBody['table'] ?? $queryParams['table'] ?? null;
|
||||
$this->id = $parsedBody['id'] ?? $queryParams['id'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function mainAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->processRequest($request);
|
||||
return new HtmlResponse('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function
|
||||
* Will issue a location-header, redirecting either BACK or to a new FormEngine instance...
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function main(): void
|
||||
{
|
||||
trigger_error('ListController->main() will be replaced by protected method processRequest() in TYPO3 v10.0. Do not call from other extension.', E_USER_DEPRECATED);
|
||||
$this->processRequest($GLOBALS['TYPO3_REQUEST']);
|
||||
}
|
||||
/**
|
||||
* Main function
|
||||
* Will issue a location-header, redirecting either BACK or to a new FormEngine instance...
|
||||
* @param ServerRequestInterface $request
|
||||
*/
|
||||
protected function processRequest(ServerRequestInterface $request): void
|
||||
{
|
||||
// Get this record
|
||||
$origRow = BackendUtility::getRecord($this->P['table'], $this->P['uid']);
|
||||
// Get TSconfig for it.
|
||||
$TSconfig = BackendUtility::getTCEFORM_TSconfig(
|
||||
$this->table,
|
||||
is_array($origRow) ? $origRow : ['pid' => $this->P['pid']]
|
||||
);
|
||||
// Set [params][pid]
|
||||
if (strpos($this->P['params']['pid'], '###') === 0 && substr($this->P['params']['pid'], -3) === '###') {
|
||||
$keyword = substr($this->P['params']['pid'], 3, -3);
|
||||
if (strpos($keyword, 'PAGE_TSCONFIG_') === 0) {
|
||||
$this->pid = (int)$TSconfig[$this->P['field']][$keyword];
|
||||
} else {
|
||||
$this->pid = (int)$TSconfig['_' . $keyword];
|
||||
}
|
||||
} else {
|
||||
$this->pid = (int)$this->P['params']['pid'];
|
||||
}
|
||||
// Make redirect:
|
||||
// If pid is blank OR if id is set, then return...
|
||||
if ((string)$this->id !== '') {
|
||||
$redirectUrl = GeneralUtility::sanitizeLocalUrl($this->P['returnUrl']);
|
||||
} else {
|
||||
/** @var UriBuilder $uriBuilder */
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
$normalizedParams = $request->getAttribute('normalizedParams');
|
||||
$requestUri = $normalizedParams->getRequestUri();
|
||||
// Otherwise, show the list:
|
||||
$urlParameters = [];
|
||||
$urlParameters['id'] = $this->pid;
|
||||
$urlParameters['table'] = $this->P['params']['table'];
|
||||
$urlParameters['returnUrl'] = !empty($this->P['returnUrl'])
|
||||
? GeneralUtility::sanitizeLocalUrl($this->P['returnUrl'])
|
||||
: $requestUri;
|
||||
$redirectUrl = (string)$uriBuilder->buildUriFromRoute('web_list', $urlParameters);
|
||||
}
|
||||
HttpUtility::redirect($redirectUrl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Backend\Controller\Wizard;
|
||||
|
||||
/*
|
||||
* 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\Form\Wizard\SuggestWizardDefaultReceiver;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Receives ajax request from FormEngine suggest wizard and creates suggest answer as json result
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class SuggestWizardController
|
||||
{
|
||||
/**
|
||||
* Ajax handler for the "suggest" feature in FormEngine.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @throws \RuntimeException for incomplete or invalid arguments
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function searchAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$parsedBody = $request->getParsedBody();
|
||||
|
||||
$search = $parsedBody['value'] ?? null;
|
||||
$tableName = $parsedBody['tableName'] ?? null;
|
||||
$fieldName = $parsedBody['fieldName'] ?? null;
|
||||
$uid = $parsedBody['uid'] ?? null;
|
||||
$pid = isset($parsedBody['pid']) ? (int)$parsedBody['pid'] : 0;
|
||||
$dataStructureIdentifier = '' ?? null;
|
||||
if (!empty($parsedBody['dataStructureIdentifier'])) {
|
||||
$dataStructureIdentifier = $parsedBody['dataStructureIdentifier'];
|
||||
}
|
||||
$flexFormSheetName = $parsedBody['flexFormSheetName'] ?? null;
|
||||
$flexFormFieldName = $parsedBody['flexFormFieldName'] ?? null;
|
||||
$flexFormContainerName = $parsedBody['flexFormContainerName'] ?? null;
|
||||
$flexFormContainerFieldName = $parsedBody['flexFormContainerFieldName'] ?? null;
|
||||
|
||||
// Determine TCA config of field
|
||||
if (empty($dataStructureIdentifier)) {
|
||||
// Normal columns field
|
||||
$fieldConfig = $GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
|
||||
$fieldNameInPageTsConfig = $fieldName;
|
||||
} else {
|
||||
// A flex flex form field
|
||||
$flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
|
||||
$dataStructure = $flexFormTools->parseDataStructureByIdentifier($dataStructureIdentifier);
|
||||
if (empty($flexFormContainerFieldName)) {
|
||||
// @todo: See if a path in pageTsConfig like "TCEForm.tableName.theContainerFieldName =" is useful and works with other pageTs, too.
|
||||
$fieldNameInPageTsConfig = $flexFormFieldName;
|
||||
if (!isset($dataStructure['sheets'][$flexFormSheetName]['ROOT']
|
||||
['el'][$flexFormFieldName]['TCEforms']['config'])
|
||||
) {
|
||||
throw new \RuntimeException(
|
||||
'Specified path ' . $flexFormFieldName . ' not found in flex form data structure',
|
||||
1480609491
|
||||
);
|
||||
}
|
||||
$fieldConfig = $dataStructure['sheets'][$flexFormSheetName]['ROOT']
|
||||
['el'][$flexFormFieldName]['TCEforms']['config'];
|
||||
} else {
|
||||
$fieldNameInPageTsConfig = $flexFormContainerFieldName;
|
||||
if (!isset($dataStructure['sheets'][$flexFormSheetName]['ROOT']
|
||||
['el'][$flexFormFieldName]
|
||||
['el'][$flexFormContainerName]
|
||||
['el'][$flexFormContainerFieldName]['TCEforms']['config'])
|
||||
) {
|
||||
throw new \RuntimeException(
|
||||
'Specified path ' . $flexFormContainerName . ' not found in flex form section container data structure',
|
||||
1480611208
|
||||
);
|
||||
}
|
||||
$fieldConfig = $dataStructure['sheets'][$flexFormSheetName]['ROOT']
|
||||
['el'][$flexFormFieldName]
|
||||
['el'][$flexFormContainerName]
|
||||
['el'][$flexFormContainerFieldName]['TCEforms']['config'];
|
||||
}
|
||||
}
|
||||
|
||||
$pageTsConfig = BackendUtility::getPagesTSconfig($pid);
|
||||
|
||||
$wizardConfig = $fieldConfig['suggestOptions'] ?? [];
|
||||
|
||||
$queryTables = $this->getTablesToQueryFromFieldConfiguration($fieldConfig);
|
||||
$whereClause = $this->getWhereClause($fieldConfig);
|
||||
|
||||
$resultRows = [];
|
||||
|
||||
// fetch the records for each query table. A query table is a table from which records are allowed to
|
||||
// be added to the TCEForm selector, originally fetched from the "allowed" config option in the TCA
|
||||
foreach ($queryTables as $queryTable) {
|
||||
// if the table does not exist, skip it
|
||||
if (!is_array($GLOBALS['TCA'][$queryTable]) || empty($GLOBALS['TCA'][$queryTable])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$config = $this->getConfigurationForTable($queryTable, $wizardConfig, $pageTsConfig, $tableName, $fieldNameInPageTsConfig);
|
||||
|
||||
// process addWhere
|
||||
if (!isset($config['addWhere']) && $whereClause) {
|
||||
$config['addWhere'] = $whereClause;
|
||||
}
|
||||
if (isset($config['addWhere'])) {
|
||||
$replacement = [
|
||||
'###THIS_UID###' => (int)$uid,
|
||||
'###CURRENT_PID###' => (int)$pid
|
||||
];
|
||||
if (isset($pageTsConfig['TCEFORM.'][$tableName . '.'][$fieldNameInPageTsConfig . '.'])) {
|
||||
$fieldTSconfig = $pageTsConfig['TCEFORM.'][$tableName . '.'][$fieldNameInPageTsConfig . '.'];
|
||||
if (isset($fieldTSconfig['PAGE_TSCONFIG_ID'])) {
|
||||
$replacement['###PAGE_TSCONFIG_ID###'] = (int)$fieldTSconfig['PAGE_TSCONFIG_ID'];
|
||||
}
|
||||
if (isset($fieldTSconfig['PAGE_TSCONFIG_IDLIST'])) {
|
||||
$replacement['###PAGE_TSCONFIG_IDLIST###'] = implode(',', GeneralUtility::intExplode(',', $fieldTSconfig['PAGE_TSCONFIG_IDLIST']));
|
||||
}
|
||||
if (isset($fieldTSconfig['PAGE_TSCONFIG_STR'])) {
|
||||
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($fieldConfig['foreign_table']);
|
||||
// nasty hack, but it's currently not possible to just quote anything "inside" the value but not escaping
|
||||
// the whole field as it is not known where it is used in the WHERE clause
|
||||
$replacement['###PAGE_TSCONFIG_STR###'] = trim($connection->quote($fieldTSconfig['PAGE_TSCONFIG_STR']), '\'');
|
||||
}
|
||||
}
|
||||
$config['addWhere'] = strtr(' ' . $config['addWhere'], $replacement);
|
||||
}
|
||||
|
||||
// instantiate the class that should fetch the records for this $queryTable
|
||||
$receiverClassName = $config['receiverClass'];
|
||||
if (!class_exists($receiverClassName)) {
|
||||
$receiverClassName = SuggestWizardDefaultReceiver::class;
|
||||
}
|
||||
$receiverObj = GeneralUtility::makeInstance($receiverClassName, $queryTable, $config);
|
||||
$params = [
|
||||
'value' => $search,
|
||||
'uid' => $uid,
|
||||
];
|
||||
$rows = $receiverObj->queryTable($params);
|
||||
if (empty($rows)) {
|
||||
continue;
|
||||
}
|
||||
$resultRows = $rows + $resultRows;
|
||||
unset($rows);
|
||||
}
|
||||
|
||||
// Limit the number of items in the result list
|
||||
$maxItems = $config['maxItemsInResultList'] ?? 10;
|
||||
$maxItems = min(count($resultRows), $maxItems);
|
||||
|
||||
array_splice($resultRows, $maxItems);
|
||||
return (new JsonResponse())->setPayload(array_values($resultRows));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns TRUE if a table has been marked as hidden in the configuration
|
||||
*
|
||||
* @param array $tableConfig
|
||||
* @return bool
|
||||
*/
|
||||
protected function isTableHidden(array $tableConfig)
|
||||
{
|
||||
return (bool)$tableConfig['ctrl']['hideTable'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current backend user is allowed to access the given table, based on the ctrl-section of the
|
||||
* table's configuration array (TCA) entry.
|
||||
*
|
||||
* @param array $tableConfig
|
||||
* @return bool
|
||||
*/
|
||||
protected function currentBackendUserMayAccessTable(array $tableConfig)
|
||||
{
|
||||
if ($this->getBackendUser()->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the user is no admin, they may not access admin-only tables
|
||||
if ($tableConfig['ctrl']['adminOnly']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// allow access to root level pages if security restrictions should be bypassed
|
||||
return !$tableConfig['ctrl']['rootLevel'] || $tableConfig['ctrl']['security']['ignoreRootLevelRestriction'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configuration for the suggest wizard for the given table. This does multiple overlays from the
|
||||
* TSconfig.
|
||||
*
|
||||
* @param string $queryTable The table to query
|
||||
* @param array $wizardConfig The configuration for the wizard as configured in the data structure
|
||||
* @param array $TSconfig The TSconfig array of the current page
|
||||
* @param string $table The table where the wizard is used
|
||||
* @param string $field The field where the wizard is used
|
||||
* @return array
|
||||
*/
|
||||
protected function getConfigurationForTable($queryTable, array $wizardConfig, array $TSconfig, $table, $field)
|
||||
{
|
||||
$config = (array)$wizardConfig['default'];
|
||||
|
||||
if (is_array($wizardConfig[$queryTable])) {
|
||||
ArrayUtility::mergeRecursiveWithOverrule($config, $wizardConfig[$queryTable]);
|
||||
}
|
||||
$globalSuggestTsConfig = $TSconfig['TCEFORM.']['suggest.'];
|
||||
$currentFieldSuggestTsConfig = $TSconfig['TCEFORM.'][$table . '.'][$field . '.']['suggest.'];
|
||||
|
||||
// merge the configurations of different "levels" to get the working configuration for this table and
|
||||
// field (i.e., go from the most general to the most special configuration)
|
||||
if (is_array($globalSuggestTsConfig['default.'])) {
|
||||
ArrayUtility::mergeRecursiveWithOverrule($config, $globalSuggestTsConfig['default.']);
|
||||
}
|
||||
|
||||
if (is_array($globalSuggestTsConfig[$queryTable . '.'])) {
|
||||
ArrayUtility::mergeRecursiveWithOverrule($config, $globalSuggestTsConfig[$queryTable . '.']);
|
||||
}
|
||||
|
||||
// use $table instead of $queryTable here because we overlay a config
|
||||
// for the input-field here, not for the queried table
|
||||
if (is_array($currentFieldSuggestTsConfig['default.'])) {
|
||||
ArrayUtility::mergeRecursiveWithOverrule($config, $currentFieldSuggestTsConfig['default.']);
|
||||
}
|
||||
|
||||
if (is_array($currentFieldSuggestTsConfig[$queryTable . '.'])) {
|
||||
ArrayUtility::mergeRecursiveWithOverrule($config, $currentFieldSuggestTsConfig[$queryTable . '.']);
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the given field configuration for the tables that should be used for querying and returns them as an
|
||||
* array.
|
||||
*
|
||||
* @param array $fieldConfig
|
||||
* @return array
|
||||
*/
|
||||
protected function getTablesToQueryFromFieldConfiguration(array $fieldConfig)
|
||||
{
|
||||
$queryTables = [];
|
||||
|
||||
if (isset($fieldConfig['allowed'])) {
|
||||
if ($fieldConfig['allowed'] !== '*') {
|
||||
// list of allowed tables
|
||||
$queryTables = GeneralUtility::trimExplode(',', $fieldConfig['allowed']);
|
||||
} else {
|
||||
// all tables are allowed, if the user can access them
|
||||
foreach ($GLOBALS['TCA'] as $tableName => $tableConfig) {
|
||||
if (!$this->isTableHidden($tableConfig) && $this->currentBackendUserMayAccessTable($tableConfig)) {
|
||||
$queryTables[] = $tableName;
|
||||
}
|
||||
}
|
||||
unset($tableName, $tableConfig);
|
||||
}
|
||||
} elseif (isset($fieldConfig['foreign_table'])) {
|
||||
// use the foreign table
|
||||
$queryTables = [$fieldConfig['foreign_table']];
|
||||
}
|
||||
|
||||
return $queryTables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SQL WHERE clause to use for querying records. This is currently only relevant if a foreign_table
|
||||
* is configured and should be used; it could e.g. be used to limit to a certain subset of records from the
|
||||
* foreign table
|
||||
*
|
||||
* @param array $fieldConfig
|
||||
* @return string
|
||||
*/
|
||||
protected function getWhereClause(array $fieldConfig)
|
||||
{
|
||||
if (!isset($fieldConfig['foreign_table'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// strip ORDER BY clause
|
||||
return trim(preg_replace('/ORDER[[:space:]]+BY.*/i', '', $fieldConfig['foreign_table_where']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser()
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,792 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Backend\Controller\Wizard;
|
||||
|
||||
/*
|
||||
* 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\Components\ButtonBar;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplate;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Compatibility\PublicPropertyDeprecationTrait;
|
||||
use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
|
||||
use TYPO3\CMS\Core\DataHandling\DataHandler;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Http\RedirectResponse;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\HttpUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* Script Class for rendering the Table Wizard
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class TableController extends AbstractWizardController
|
||||
{
|
||||
use PublicPropertyDeprecationTrait;
|
||||
|
||||
/**
|
||||
* Properties which have been moved to protected status from public
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $deprecatedPublicProperties = [
|
||||
'content' => 'Using $content of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'inputStyle' => 'Using $inputStyle of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'xmlStorage' => 'Using $xmlStorage of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'numNewRows' => 'Using $numNewRows of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'colsFieldName' => 'Using $colsFieldName of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'P' => 'Using $P of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'TABLECFG' => 'Using $TABLECFG of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'tableParsing_quote' => 'Using $tableParsing_quote of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
'tableParsing_delimiter' => 'Using $tableParsing_delimiter of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Content accumulation for the module.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $content;
|
||||
|
||||
/**
|
||||
* If TRUE, <input> fields are shown instead of textareas.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $inputStyle = false;
|
||||
|
||||
/**
|
||||
* If set, the string version of the content is interpreted/written as XML
|
||||
* instead of the original line-based kind. This variable still needs binding
|
||||
* to the wizard parameters - but support is ready!
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $xmlStorage = 0;
|
||||
|
||||
/**
|
||||
* Number of new rows to add in bottom of wizard
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $numNewRows = 1;
|
||||
|
||||
/**
|
||||
* Name of field in parent record which MAY contain the number of columns for the table
|
||||
* here hardcoded to the value of tt_content. Should be set by FormEngine parameters (from P)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $colsFieldName = 'cols';
|
||||
|
||||
/**
|
||||
* Wizard parameters, coming from FormEngine linking to the wizard.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $P;
|
||||
|
||||
/**
|
||||
* The array which is constantly submitted by the multidimensional form of this wizard.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $TABLECFG;
|
||||
|
||||
/**
|
||||
* Table parsing
|
||||
* quoting of table cells
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $tableParsing_quote;
|
||||
|
||||
/**
|
||||
* delimiter between table cells
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $tableParsing_delimiter;
|
||||
|
||||
/**
|
||||
* @var IconFactory
|
||||
*/
|
||||
protected $iconFactory;
|
||||
|
||||
/**
|
||||
* 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_wizards.xlf');
|
||||
|
||||
// @deprecated since TYPO3 v9, will be moved out of __construct() in TYPO3 v10.0
|
||||
$this->init($GLOBALS['TYPO3_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
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function mainAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$response = $this->renderContent($request);
|
||||
|
||||
if (empty($response)) {
|
||||
$response = new HtmlResponse($this->moduleTemplate->renderContent());
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function, rendering the table wizard
|
||||
*
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function main()
|
||||
{
|
||||
trigger_error('TableController->main() will be replaced by protected method renderContent() in TYPO3 v10.0. Do not call from other extensions.', E_USER_DEPRECATED);
|
||||
|
||||
$response = $this->renderContent($GLOBALS['TYPO3_REQUEST']);
|
||||
|
||||
if ($response instanceof RedirectResponse) {
|
||||
HttpUtility::redirect($response->getHeaders()['location'][0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the table wizard content
|
||||
*
|
||||
* @return string HTML content for the form.
|
||||
* @throws \RuntimeException
|
||||
*
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function tableWizard()
|
||||
{
|
||||
trigger_error('TableController->tableWizard() will be replaced by protected method renderTableWizard() in TYPO3 v10.0. Do not call from other extensions.', E_USER_DEPRECATED);
|
||||
|
||||
$result = $this->renderTableWizard($GLOBALS['TYPO3_REQUEST']);
|
||||
|
||||
if ($result instanceof RedirectResponse) {
|
||||
HttpUtility::redirect($result->getHeaders()['location'][0]);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will get and return the configuration code string
|
||||
* Will also save (and possibly redirect/exit) the content if a save button has been pressed
|
||||
*
|
||||
* @param array $row Current parent record row
|
||||
* @return array Table config code in an array
|
||||
*
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function getConfigCode($row)
|
||||
{
|
||||
trigger_error('TableController->getConfigCode() will be replaced by protected method getConfiguration() in TYPO3 v10.0. Do not call from other extensions.', E_USER_DEPRECATED);
|
||||
|
||||
$result = $this->getConfiguration($row, $GLOBALS['TYPO3_REQUEST']);
|
||||
|
||||
if ($result instanceof RedirectResponse) {
|
||||
HttpUtility::redirect($result->getHeaders()['location'][0]);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the HTML for the Table Wizard:
|
||||
*
|
||||
* @param array $configuration Table config array
|
||||
* @return string HTML for the table wizard
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function getTableHTML($configuration)
|
||||
{
|
||||
trigger_error('TableController->getTableHTML() will be replaced by protected method getTableWizard() in TYPO3 v10.0. Do not call from other extensions.', E_USER_DEPRECATED);
|
||||
return $this->getTableWizard($configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if a control button (up/down/around/delete) has been pressed for an item and accordingly it will
|
||||
* manipulate the internal TABLECFG array
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function changeFunc()
|
||||
{
|
||||
trigger_error('TableController->changeFunc() will be replaced by protected method manipulateTable() in TYPO3 v10.0. Do not call from other extensions.', E_USER_DEPRECATED);
|
||||
$this->manipulateTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the input array to a configuration code string
|
||||
*
|
||||
* @param array $cfgArr Array of table configuration (follows the input structure from the table wizard POST form)
|
||||
* @return string The array converted into a string with line-based configuration.
|
||||
* @see cfgString2CfgArray()
|
||||
*
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function cfgArray2CfgString($cfgArr)
|
||||
{
|
||||
trigger_error('TableController->cfgArray2CfgString() will be replaced by protected method configurationArrayToString() in TYPO3 v10.0. Do not call from other extensions.', E_USER_DEPRECATED);
|
||||
return $this->configurationArrayToString($cfgArr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the input configuration code string into an array
|
||||
*
|
||||
* @param string $configurationCode Configuration code
|
||||
* @param int $columns Default number of columns
|
||||
* @return array Configuration array
|
||||
* @see cfgArray2CfgString()
|
||||
*
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
public function cfgString2CfgArray($configurationCode, $columns)
|
||||
{
|
||||
trigger_error('TableController->cfgString2CfgArray() will be replaced by protected method configurationStringToArray() in TYPO3 v10.0. Do not call from other extensions.', E_USER_DEPRECATED);
|
||||
return $this->configurationStringToArray($configurationCode, $columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization of the class
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
*/
|
||||
protected function init(ServerRequestInterface $request): void
|
||||
{
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$queryParams = $request->getQueryParams();
|
||||
// GPvars:
|
||||
$this->P = $parsedBody['P'] ?? $queryParams['P'] ?? null;
|
||||
$this->TABLECFG = $parsedBody['TABLE'] ?? $queryParams['TABLE'] ?? null;
|
||||
// Setting options:
|
||||
$this->xmlStorage = $this->P['params']['xmlOutput'];
|
||||
$this->numNewRows = MathUtility::forceIntegerInRange($this->P['params']['numNewRows'], 1, 50, 5);
|
||||
// Textareas or input fields:
|
||||
$this->inputStyle = isset($this->TABLECFG['textFields']) ? (bool)$this->TABLECFG['textFields'] : true;
|
||||
$this->tableParsing_delimiter = '|';
|
||||
$this->tableParsing_quote = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function, rendering the table wizard
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface|null
|
||||
*/
|
||||
protected function renderContent(ServerRequestInterface $request): ?ResponseInterface
|
||||
{
|
||||
$normalizedParams = $request->getAttribute('normalizedParams');
|
||||
$requestUri = $normalizedParams->getRequestUri();
|
||||
list($rUri) = explode('#', $requestUri);
|
||||
$this->content .= '<form action="' . htmlspecialchars($rUri) . '" method="post" id="TableController" name="wizardForm">';
|
||||
if ($this->P['table'] && $this->P['field'] && $this->P['uid']) {
|
||||
$tableWizard = $this->renderTableWizard($request);
|
||||
|
||||
if ($tableWizard instanceof RedirectResponse) {
|
||||
return $tableWizard;
|
||||
}
|
||||
|
||||
$this->content .= '<h2>' . htmlspecialchars($this->getLanguageService()->getLL('table_title')) . '</h2>'
|
||||
. '<div>' . $tableWizard . '</div>';
|
||||
} else {
|
||||
$this->content .= '<h2>' . htmlspecialchars($this->getLanguageService()->getLL('table_title')) . '</h2>'
|
||||
. '<div><span class="text-danger">' . htmlspecialchars($this->getLanguageService()->getLL('table_noData')) . '</span></div>';
|
||||
}
|
||||
$this->content .= '</form>';
|
||||
// Setting up the buttons and markers for docHeader
|
||||
$this->getButtons();
|
||||
// Build the <body> for the module
|
||||
$this->moduleTemplate->setContent($this->content);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the panel of buttons for submitting the form or otherwise perform operations.
|
||||
*/
|
||||
protected function getButtons(): void
|
||||
{
|
||||
$buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
|
||||
if ($this->P['table'] && $this->P['field'] && $this->P['uid']) {
|
||||
// CSH
|
||||
$cshButton = $buttonBar->makeHelpButton()
|
||||
->setModuleName('xMOD_csh_corebe')
|
||||
->setFieldName('wizard_table_wiz');
|
||||
$buttonBar->addButton($cshButton);
|
||||
// Close
|
||||
$closeButton = $buttonBar->makeLinkButton()
|
||||
->setHref($this->P['returnUrl'])
|
||||
->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-close', Icon::SIZE_SMALL))
|
||||
->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.closeDoc'))
|
||||
->setShowLabelText(true);
|
||||
$buttonBar->addButton($closeButton, ButtonBar::BUTTON_POSITION_LEFT, 1);
|
||||
// Save
|
||||
$saveButton = $buttonBar->makeInputButton()
|
||||
->setName('_savedok')
|
||||
->setValue('1')
|
||||
->setForm('TableController')
|
||||
->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-save', Icon::SIZE_SMALL))
|
||||
->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.saveDoc'))
|
||||
->setShowLabelText(true);
|
||||
$buttonBar->addButton($saveButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
|
||||
// Reload
|
||||
$reloadButton = $buttonBar->makeInputButton()
|
||||
->setName('_refresh')
|
||||
->setValue('1')
|
||||
->setForm('TableController')
|
||||
->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-refresh', Icon::SIZE_SMALL))
|
||||
->setTitle($this->getLanguageService()->getLL('forms_refresh'));
|
||||
$buttonBar->addButton($reloadButton, ButtonBar::BUTTON_POSITION_RIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the table wizard content
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return string|ResponseInterface HTML content for the form.
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function renderTableWizard(ServerRequestInterface $request)
|
||||
{
|
||||
if (!$this->checkEditAccess($this->P['table'], $this->P['uid'])) {
|
||||
throw new \RuntimeException('Wizard Error: No access', 1349692692);
|
||||
}
|
||||
// First, check the references by selecting the record:
|
||||
$row = BackendUtility::getRecord($this->P['table'], $this->P['uid']);
|
||||
if (!is_array($row)) {
|
||||
throw new \RuntimeException('Wizard Error: No reference to record', 1294587125);
|
||||
}
|
||||
// This will get the content of the form configuration code field to us - possibly cleaned up,
|
||||
// saved to database etc. if the form has been submitted in the meantime.
|
||||
$tableCfgArray = $this->getConfiguration($row, $request);
|
||||
|
||||
if ($tableCfgArray instanceof ResponseInterface) {
|
||||
return $tableCfgArray;
|
||||
}
|
||||
|
||||
// Generation of the Table Wizards HTML code:
|
||||
$content = $this->getTableWizard($tableCfgArray);
|
||||
// Return content:
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will get and return the configuration code string
|
||||
* Will also save (and possibly redirect/exit) the content if a save button has been pressed
|
||||
*
|
||||
* @param array $row Current parent record row
|
||||
* @param ServerRequestInterface $request
|
||||
* @return array|ResponseInterface Table config code in an array
|
||||
*/
|
||||
protected function getConfiguration(array $row, ServerRequestInterface $request)
|
||||
{
|
||||
// Get delimiter settings
|
||||
$this->tableParsing_quote = $row['table_enclosure'] ? chr((int)$row['table_enclosure']) : '';
|
||||
$this->tableParsing_delimiter = $row['table_delimiter'] ? chr((int)$row['table_delimiter']) : '|';
|
||||
// If some data has been submitted, then construct
|
||||
if (isset($this->TABLECFG['c'])) {
|
||||
// Process incoming:
|
||||
$this->manipulateTable();
|
||||
// Convert to string (either line based or XML):
|
||||
if ($this->xmlStorage) {
|
||||
// Convert the input array to XML:
|
||||
$bodyText = '<?xml version="1.0" encoding="utf-8" standalone="yes" ?>' . LF . GeneralUtility::array2xml($this->TABLECFG['c'], '', 0, 'T3TableWizard');
|
||||
// Setting cfgArr directly from the input:
|
||||
$configuration = $this->TABLECFG['c'];
|
||||
} else {
|
||||
// Convert the input array to a string of configuration code:
|
||||
$bodyText = $this->configurationArrayToString($this->TABLECFG['c']);
|
||||
// Create cfgArr from the string based configuration - that way it is cleaned up
|
||||
// and any incompatibilities will be removed!
|
||||
$configuration = $this->configurationStringToArray($bodyText, (int)$row[$this->colsFieldName]);
|
||||
}
|
||||
// If a save button has been pressed, then save the new field content:
|
||||
if ($_POST['_savedok'] || $_POST['_saveandclosedok']) {
|
||||
// Get DataHandler object:
|
||||
/** @var DataHandler $dataHandler */
|
||||
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
|
||||
// Put content into the data array:
|
||||
$data = [];
|
||||
if ($this->P['flexFormPath']) {
|
||||
// Current value of flexForm path:
|
||||
$currentFlexFormData = GeneralUtility::xml2array($row[$this->P['field']]);
|
||||
/** @var FlexFormTools $flexFormTools */
|
||||
$flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
|
||||
$flexFormTools->setArrayValueByPath($this->P['flexFormPath'], $currentFlexFormData, $bodyText);
|
||||
$data[$this->P['table']][$this->P['uid']][$this->P['field']] = $currentFlexFormData;
|
||||
} else {
|
||||
$data[$this->P['table']][$this->P['uid']][$this->P['field']] = $bodyText;
|
||||
}
|
||||
// Perform the update:
|
||||
$dataHandler->start($data, []);
|
||||
$dataHandler->process_datamap();
|
||||
// If the save/close button was pressed, then redirect the screen:
|
||||
if ($_POST['_saveandclosedok']) {
|
||||
return new RedirectResponse(GeneralUtility::sanitizeLocalUrl($this->P['returnUrl']));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If nothing has been submitted, load the $bodyText variable from the selected database row:
|
||||
if ($this->xmlStorage) {
|
||||
$configuration = GeneralUtility::xml2array($row[$this->P['field']]);
|
||||
} else {
|
||||
if ($this->P['flexFormPath']) {
|
||||
// Current value of flexForm path:
|
||||
$currentFlexFormData = GeneralUtility::xml2array($row[$this->P['field']]);
|
||||
/** @var FlexFormTools $flexFormTools */
|
||||
$flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
|
||||
$configuration = $flexFormTools->getArrayValueByPath(
|
||||
$this->P['flexFormPath'],
|
||||
$currentFlexFormData
|
||||
);
|
||||
$configuration = $this->configurationStringToArray($configuration, 0);
|
||||
} else {
|
||||
// Regular line based table configuration:
|
||||
$columns = $row[$this->colsFieldName] ?? 0;
|
||||
$configuration = $this->configurationStringToArray($row[$this->P['field']] ?? '', (int)$columns);
|
||||
}
|
||||
}
|
||||
$configuration = is_array($configuration) ? $configuration : [];
|
||||
}
|
||||
return $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the HTML for the Table Wizard:
|
||||
*
|
||||
* @param array $configuration Table config array
|
||||
* @return string HTML for the table wizard
|
||||
*/
|
||||
protected function getTableWizard(array $configuration): string
|
||||
{
|
||||
// Traverse the rows:
|
||||
$tRows = [];
|
||||
$k = 0;
|
||||
$countLines = count($configuration);
|
||||
foreach ($configuration as $cellArr) {
|
||||
if (is_array($cellArr)) {
|
||||
// Initialize:
|
||||
$cells = [];
|
||||
$a = 0;
|
||||
// Traverse the columns:
|
||||
foreach ($cellArr as $cellContent) {
|
||||
if ($this->inputStyle) {
|
||||
$cells[] = '<input class="form-control" type="text" name="TABLE[c][' . ($k + 1) * 2 . '][' . ($a + 1) * 2 . ']" value="' . htmlspecialchars($cellContent) . '" />';
|
||||
} else {
|
||||
$cellContent = preg_replace('/<br[ ]?[\\/]?>/i', LF, $cellContent);
|
||||
$cells[] = '<textarea class="form-control" rows="6" name="TABLE[c][' . ($k + 1) * 2 . '][' . ($a + 1) * 2 . ']">' . htmlspecialchars($cellContent) . '</textarea>';
|
||||
}
|
||||
// Increment counter:
|
||||
$a++;
|
||||
}
|
||||
// CTRL panel for a table row (move up/down/around):
|
||||
$onClick = 'document.wizardForm.action+=' . GeneralUtility::quoteJSvalue('#ANC_' . (($k + 1) * 2 - 2)) . ';';
|
||||
$onClick = ' onclick="' . htmlspecialchars($onClick) . '"';
|
||||
$ctrl = '';
|
||||
if ($k !== 0) {
|
||||
$ctrl .= '<button class="btn btn-default" name="TABLE[row_up][' . ($k + 1) * 2 . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_up')) . '"' . $onClick . '><span class="t3-icon fa fa-fw fa-angle-up"></span></button>';
|
||||
} else {
|
||||
$ctrl .= '<button class="btn btn-default" name="TABLE[row_bottom][' . ($k + 1) * 2 . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_bottom')) . '"' . $onClick . '><span class="t3-icon fa fa-fw fa-angle-double-down"></span></button>';
|
||||
}
|
||||
if ($k + 1 !== $countLines) {
|
||||
$ctrl .= '<button class="btn btn-default" name="TABLE[row_down][' . ($k + 1) * 2 . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_down')) . '"' . $onClick . '><span class="t3-icon fa fa-fw fa-angle-down"></span></button>';
|
||||
} else {
|
||||
$ctrl .= '<button class="btn btn-default" name="TABLE[row_top][' . ($k + 1) * 2 . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_top')) . '"' . $onClick . '><span class="t3-icon fa fa-fw fa-angle-double-up"></span></button>';
|
||||
}
|
||||
$ctrl .= '<button class="btn btn-default" name="TABLE[row_remove][' . ($k + 1) * 2 . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_removeRow')) . '"' . $onClick . '><span class="t3-icon fa fa-fw fa-trash"></span></button>';
|
||||
$ctrl .= '<button class="btn btn-default" name="TABLE[row_add][' . ($k + 1) * 2 . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_addRow')) . '"' . $onClick . '><span class="t3-icon fa fa-fw fa-plus"></span></button>';
|
||||
$tRows[] = '
|
||||
<tr>
|
||||
<td>
|
||||
<a name="ANC_' . ($k + 1) * 2 . '"></a>
|
||||
<span class="btn-group' . ($this->inputStyle ? '' : '-vertical') . '">' . $ctrl . '</span>
|
||||
</td>
|
||||
<td>' . implode('</td>
|
||||
<td>', $cells) . '</td>
|
||||
</tr>';
|
||||
// Increment counter:
|
||||
$k++;
|
||||
}
|
||||
}
|
||||
// CTRL panel for a table column (move left/right/around/delete)
|
||||
$cells = [];
|
||||
$cells[] = '';
|
||||
// Finding first row:
|
||||
$firstRow = reset($configuration);
|
||||
if (is_array($firstRow)) {
|
||||
$cols = count($firstRow);
|
||||
for ($a = 1; $a <= $cols; $a++) {
|
||||
$b = $a * 2;
|
||||
$ctrl = '';
|
||||
if ($a !== 1) {
|
||||
$ctrl .= '<button class="btn btn-default" name="TABLE[col_left][' . $b . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_left')) . '"><span class="t3-icon fa fa-fw fa-angle-left"></span></button>';
|
||||
} else {
|
||||
$ctrl .= '<button class="btn btn-default" name="TABLE[col_end][' . $b . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_end')) . '"><span class="t3-icon fa fa-fw fa-angle-double-right"></span></button>';
|
||||
}
|
||||
if ($a != $cols) {
|
||||
$ctrl .= '<button class="btn btn-default" name="TABLE[col_right][' . $b . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_right')) . '"><span class="t3-icon fa fa-fw fa-angle-right"></span></button>';
|
||||
} else {
|
||||
$ctrl .= '<button class="btn btn-default" name="TABLE[col_start][' . $b . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_start')) . '"><span class="t3-icon fa fa-fw fa-angle-double-left"></span></button>';
|
||||
}
|
||||
$ctrl .= '<button class="btn btn-default" name="TABLE[col_remove][' . $b . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_removeColumn')) . '"><span class="t3-icon fa fa-fw fa-trash"></span></button>';
|
||||
$ctrl .= '<button class="btn btn-default" name="TABLE[col_add][' . $b . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_addColumn')) . '"><span class="t3-icon fa fa-fw fa-plus"></span></button>';
|
||||
$cells[] = '<span class="btn-group">' . $ctrl . '</span>';
|
||||
}
|
||||
$tRows[] = '
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td>' . implode('</td>
|
||||
<td>', $cells) . '</td>
|
||||
</tr>
|
||||
</tfoot>';
|
||||
}
|
||||
$content = '';
|
||||
$addSubmitOnClick = 'onclick="document.getElementById(\'TableController\').submit();"';
|
||||
// Implode all table rows into a string, wrapped in table tags.
|
||||
$content .= '
|
||||
|
||||
<!-- Table wizard -->
|
||||
<div class="table-fit table-fit-inline-block">
|
||||
<table id="typo3-tablewizard" class="table table-center">
|
||||
' . implode('', $tRows) . '
|
||||
</table>
|
||||
</div>';
|
||||
// Input type checkbox:
|
||||
$content .= '
|
||||
|
||||
<!-- Input mode check box: -->
|
||||
<div class="checkbox">
|
||||
<input type="hidden" name="TABLE[textFields]" value="0" />
|
||||
<label for="textFields">
|
||||
<input type="checkbox" ' . $addSubmitOnClick . ' name="TABLE[textFields]" id="textFields" value="1"' . ($this->inputStyle ? ' checked="checked"' : '') . ' />
|
||||
' . $this->getLanguageService()->getLL('table_smallFields') . '
|
||||
</label>
|
||||
</div>';
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if a control button (up/down/around/delete) has been pressed for an item and accordingly it will
|
||||
* manipulate the internal TABLECFG array
|
||||
*/
|
||||
protected function manipulateTable(): void
|
||||
{
|
||||
if ($this->TABLECFG['col_remove']) {
|
||||
$kk = key($this->TABLECFG['col_remove']);
|
||||
$cmd = 'col_remove';
|
||||
} elseif ($this->TABLECFG['col_add']) {
|
||||
$kk = key($this->TABLECFG['col_add']);
|
||||
$cmd = 'col_add';
|
||||
} elseif ($this->TABLECFG['col_start']) {
|
||||
$kk = key($this->TABLECFG['col_start']);
|
||||
$cmd = 'col_start';
|
||||
} elseif ($this->TABLECFG['col_end']) {
|
||||
$kk = key($this->TABLECFG['col_end']);
|
||||
$cmd = 'col_end';
|
||||
} elseif ($this->TABLECFG['col_left']) {
|
||||
$kk = key($this->TABLECFG['col_left']);
|
||||
$cmd = 'col_left';
|
||||
} elseif ($this->TABLECFG['col_right']) {
|
||||
$kk = key($this->TABLECFG['col_right']);
|
||||
$cmd = 'col_right';
|
||||
} elseif ($this->TABLECFG['row_remove']) {
|
||||
$kk = key($this->TABLECFG['row_remove']);
|
||||
$cmd = 'row_remove';
|
||||
} elseif ($this->TABLECFG['row_add']) {
|
||||
$kk = key($this->TABLECFG['row_add']);
|
||||
$cmd = 'row_add';
|
||||
} elseif ($this->TABLECFG['row_top']) {
|
||||
$kk = key($this->TABLECFG['row_top']);
|
||||
$cmd = 'row_top';
|
||||
} elseif ($this->TABLECFG['row_bottom']) {
|
||||
$kk = key($this->TABLECFG['row_bottom']);
|
||||
$cmd = 'row_bottom';
|
||||
} elseif ($this->TABLECFG['row_up']) {
|
||||
$kk = key($this->TABLECFG['row_up']);
|
||||
$cmd = 'row_up';
|
||||
} elseif ($this->TABLECFG['row_down']) {
|
||||
$kk = key($this->TABLECFG['row_down']);
|
||||
$cmd = 'row_down';
|
||||
} else {
|
||||
$kk = '';
|
||||
$cmd = '';
|
||||
}
|
||||
if ($cmd && MathUtility::canBeInterpretedAsInteger($kk)) {
|
||||
if (strpos($cmd, 'row_') === 0) {
|
||||
switch ($cmd) {
|
||||
case 'row_remove':
|
||||
unset($this->TABLECFG['c'][$kk]);
|
||||
break;
|
||||
case 'row_add':
|
||||
for ($a = 1; $a <= $this->numNewRows; $a++) {
|
||||
// Checking if set: The point is that any new row between existing rows
|
||||
// will be TRUE after one row is added while if rows are added in the bottom
|
||||
// of the table there will be no existing rows to stop the addition of new rows
|
||||
// which means it will add up to $this->numNewRows rows then.
|
||||
if (!isset($this->TABLECFG['c'][$kk + $a])) {
|
||||
$this->TABLECFG['c'][$kk + $a] = [];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'row_top':
|
||||
$this->TABLECFG['c'][1] = $this->TABLECFG['c'][$kk];
|
||||
unset($this->TABLECFG['c'][$kk]);
|
||||
break;
|
||||
case 'row_bottom':
|
||||
$this->TABLECFG['c'][10000000] = $this->TABLECFG['c'][$kk];
|
||||
unset($this->TABLECFG['c'][$kk]);
|
||||
break;
|
||||
case 'row_up':
|
||||
$this->TABLECFG['c'][$kk - 3] = $this->TABLECFG['c'][$kk];
|
||||
unset($this->TABLECFG['c'][$kk]);
|
||||
break;
|
||||
case 'row_down':
|
||||
$this->TABLECFG['c'][$kk + 3] = $this->TABLECFG['c'][$kk];
|
||||
unset($this->TABLECFG['c'][$kk]);
|
||||
break;
|
||||
}
|
||||
ksort($this->TABLECFG['c']);
|
||||
}
|
||||
if (strpos($cmd, 'col_') === 0) {
|
||||
foreach ($this->TABLECFG['c'] as $cAK => $value) {
|
||||
switch ($cmd) {
|
||||
case 'col_remove':
|
||||
unset($this->TABLECFG['c'][$cAK][$kk]);
|
||||
break;
|
||||
case 'col_add':
|
||||
$this->TABLECFG['c'][$cAK][$kk + 1] = '';
|
||||
break;
|
||||
case 'col_start':
|
||||
$this->TABLECFG['c'][$cAK][1] = $this->TABLECFG['c'][$cAK][$kk];
|
||||
unset($this->TABLECFG['c'][$cAK][$kk]);
|
||||
break;
|
||||
case 'col_end':
|
||||
$this->TABLECFG['c'][$cAK][1000000] = $this->TABLECFG['c'][$cAK][$kk];
|
||||
unset($this->TABLECFG['c'][$cAK][$kk]);
|
||||
break;
|
||||
case 'col_left':
|
||||
$this->TABLECFG['c'][$cAK][$kk - 3] = $this->TABLECFG['c'][$cAK][$kk];
|
||||
unset($this->TABLECFG['c'][$cAK][$kk]);
|
||||
break;
|
||||
case 'col_right':
|
||||
$this->TABLECFG['c'][$cAK][$kk + 3] = $this->TABLECFG['c'][$cAK][$kk];
|
||||
unset($this->TABLECFG['c'][$cAK][$kk]);
|
||||
break;
|
||||
}
|
||||
ksort($this->TABLECFG['c'][$cAK]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Convert line breaks to <br /> tags:
|
||||
foreach ($this->TABLECFG['c'] as $a => $value) {
|
||||
foreach ($this->TABLECFG['c'][$a] as $b => $value2) {
|
||||
$this->TABLECFG['c'][$a][$b] = str_replace(
|
||||
[CR, LF],
|
||||
['', '<br />'],
|
||||
$this->TABLECFG['c'][$a][$b]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the input array to a configuration code string
|
||||
*
|
||||
* @param array $cfgArr Array of table configuration (follows the input structure from the table wizard POST form)
|
||||
* @return string The array converted into a string with line-based configuration.
|
||||
* @see configurationStringToArray()
|
||||
*/
|
||||
protected function configurationArrayToString(array $cfgArr): string
|
||||
{
|
||||
$inLines = [];
|
||||
// Traverse the elements of the table wizard and transform the settings into configuration code.
|
||||
foreach ($cfgArr as $valueA) {
|
||||
$thisLine = [];
|
||||
foreach ($valueA as $valueB) {
|
||||
$thisLine[] = $this->tableParsing_quote
|
||||
. str_replace($this->tableParsing_delimiter, '', $valueB) . $this->tableParsing_quote;
|
||||
}
|
||||
$inLines[] = implode($this->tableParsing_delimiter, $thisLine);
|
||||
}
|
||||
// Finally, implode the lines into a string:
|
||||
return implode(LF, $inLines);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the input configuration code string into an array
|
||||
*
|
||||
* @param string $configurationCode Configuration code
|
||||
* @param int $columns Default number of columns
|
||||
* @return array Configuration array
|
||||
* @see configurationArrayToString()
|
||||
*/
|
||||
protected function configurationStringToArray(string $configurationCode, int $columns): array
|
||||
{
|
||||
// Explode lines in the configuration code - each line is a table row.
|
||||
$tableLines = explode(LF, $configurationCode);
|
||||
// Setting number of columns
|
||||
// auto...
|
||||
if (!$columns && trim($tableLines[0])) {
|
||||
$columns = count(explode($this->tableParsing_delimiter, $tableLines[0]));
|
||||
}
|
||||
$columns = $columns ?: 4;
|
||||
// Traverse the number of table elements:
|
||||
$configurationArray = [];
|
||||
foreach ($tableLines as $key => $value) {
|
||||
// Initialize:
|
||||
$valueParts = explode($this->tableParsing_delimiter, $value);
|
||||
// Traverse columns:
|
||||
for ($a = 0; $a < $columns; $a++) {
|
||||
if ($this->tableParsing_quote
|
||||
&& $valueParts[$a][0] === $this->tableParsing_quote
|
||||
&& substr($valueParts[$a], -1, 1) === $this->tableParsing_quote
|
||||
) {
|
||||
$valueParts[$a] = substr(trim($valueParts[$a]), 1, -1);
|
||||
}
|
||||
$configurationArray[$key][$a] = (string)$valueParts[$a];
|
||||
}
|
||||
}
|
||||
return $configurationArray;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user