2111 lines
97 KiB
PHP
2111 lines
97 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/*
|
|
* This file is part of the TYPO3 CMS project.
|
|
*
|
|
* It is free software; you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License, either version 2
|
|
* of the License, or any later version.
|
|
*
|
|
* For the full copyright and license information, please read the
|
|
* LICENSE.txt file that was distributed with this source code.
|
|
*
|
|
* The TYPO3 project - inspiring people to share!
|
|
*/
|
|
|
|
namespace TYPO3\CMS\Backend\Controller;
|
|
|
|
use Psr\EventDispatcher\EventDispatcherInterface;
|
|
use Psr\Http\Message\ResponseInterface;
|
|
use Psr\Http\Message\ServerRequestInterface;
|
|
use Psr\Http\Message\UriInterface;
|
|
use TYPO3\CMS\Backend\Attribute\AsController;
|
|
use TYPO3\CMS\Backend\Breadcrumb\BreadcrumbFactory;
|
|
use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
|
|
use TYPO3\CMS\Backend\Controller\Event\AfterFormEnginePageInitializedEvent;
|
|
use TYPO3\CMS\Backend\Controller\Event\AfterRecordOpenedEvent;
|
|
use TYPO3\CMS\Backend\Controller\Event\BeforeFormEnginePageInitializedEvent;
|
|
use TYPO3\CMS\Backend\Domain\Repository\Localization\LocalizationRepository;
|
|
use TYPO3\CMS\Backend\Dto\FormElementData;
|
|
use TYPO3\CMS\Backend\Form\Exception\AccessDeniedException;
|
|
use TYPO3\CMS\Backend\Form\Exception\DatabaseRecordException;
|
|
use TYPO3\CMS\Backend\Form\Exception\DatabaseRecordWorkspaceDeletePlaceholderException;
|
|
use TYPO3\CMS\Backend\Form\Exception\NoFieldsToRenderException;
|
|
use TYPO3\CMS\Backend\Form\FormAction;
|
|
use TYPO3\CMS\Backend\Form\FormDataCompiler;
|
|
use TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord;
|
|
use TYPO3\CMS\Backend\Form\FormResult;
|
|
use TYPO3\CMS\Backend\Form\FormResultCollection;
|
|
use TYPO3\CMS\Backend\Form\FormResultFactory;
|
|
use TYPO3\CMS\Backend\Form\FormResultHandler;
|
|
use TYPO3\CMS\Backend\Form\NodeFactory;
|
|
use TYPO3\CMS\Backend\Module\ModuleInterface;
|
|
use TYPO3\CMS\Backend\Module\ModuleProvider;
|
|
use TYPO3\CMS\Backend\Routing\Exception\ResourceNotFoundException;
|
|
use TYPO3\CMS\Backend\Routing\PreviewUriBuilder;
|
|
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
|
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
|
|
use TYPO3\CMS\Backend\Template\Components\ComponentFactory;
|
|
use TYPO3\CMS\Backend\Template\Enum\ModuleLayout;
|
|
use TYPO3\CMS\Backend\Template\ModuleTemplate;
|
|
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
|
|
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
|
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
|
use TYPO3\CMS\Core\Database\Connection;
|
|
use TYPO3\CMS\Core\Database\ConnectionPool;
|
|
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
|
|
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
|
|
use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction;
|
|
use TYPO3\CMS\Core\Database\ReferenceIndex;
|
|
use TYPO3\CMS\Core\DataHandling\DataHandler;
|
|
use TYPO3\CMS\Core\DataHandling\TableColumnType;
|
|
use TYPO3\CMS\Core\Domain\RecordFactory;
|
|
use TYPO3\CMS\Core\Http\RedirectResponse;
|
|
use TYPO3\CMS\Core\Imaging\IconFactory;
|
|
use TYPO3\CMS\Core\Imaging\IconSize;
|
|
use TYPO3\CMS\Core\Localization\LanguageService;
|
|
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
|
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
|
|
use TYPO3\CMS\Core\Messaging\FlashMessageService;
|
|
use TYPO3\CMS\Core\Page\PageRenderer;
|
|
use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
|
|
use TYPO3\CMS\Core\Resource\Exception\InsufficientUserPermissionsException;
|
|
use TYPO3\CMS\Core\Resource\FileInterface;
|
|
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
|
use TYPO3\CMS\Core\Routing\BackendEntryPointResolver;
|
|
use TYPO3\CMS\Core\Schema\Capability\TcaSchemaCapability;
|
|
use TYPO3\CMS\Core\Schema\SchemaLabelResolver;
|
|
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
|
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
|
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
|
use TYPO3\CMS\Core\Utility\MathUtility;
|
|
use TYPO3\CMS\Core\Utility\PathUtility;
|
|
use TYPO3\CMS\Core\Versioning\VersionState;
|
|
|
|
/**
|
|
* Main backend controller almost always used if some database record is edited in the backend.
|
|
*
|
|
* Main job of this controller is to evaluate and sanitize $request parameters,
|
|
* call the DataHandler if records should be created or updated and
|
|
* execute FormEngine for record rendering.
|
|
*/
|
|
#[AsController]
|
|
class EditDocumentController
|
|
{
|
|
/**
|
|
* An array looking approx like [tablename][list-of-ids]=command, eg. "&edit[pages][123]=edit".
|
|
*
|
|
* @var array<string,mixed>
|
|
*/
|
|
protected array $editconf = [];
|
|
|
|
/**
|
|
* Array of tables with a lists of field names to edit for those tables. If specified, only those fields
|
|
* will be rendered. Otherwise, all (available) fields in the record are shown according to the TCA type.
|
|
*/
|
|
protected array $columnsOnly = [];
|
|
|
|
/**
|
|
* Default values for fields
|
|
*
|
|
* @var array|null [table][field]
|
|
*/
|
|
protected $defVals;
|
|
|
|
/**
|
|
* Array of values to force being set as hidden fields in FormEngine
|
|
*
|
|
* @var array|null [table][field]
|
|
*/
|
|
protected $overrideVals;
|
|
|
|
/**
|
|
* If set, this value will be set in $this->retUrl as "returnUrl", if not,
|
|
* $this->retUrl will link to dummy action
|
|
*
|
|
* @var string|null
|
|
*/
|
|
protected $returnUrl;
|
|
|
|
/**
|
|
* Prepared return URL. Contains the URL that we should return to from FormEngine if
|
|
* close button is clicked. Usually passed along as 'returnUrl', but falls back to
|
|
* "dummy" action.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $retUrl;
|
|
|
|
/**
|
|
* Boolean: If set, then the GET var "&id=" will be added to the
|
|
* retUrl string so that the NEW id of something is returned to the script calling the form.
|
|
*/
|
|
protected bool $returnNewPageId = false;
|
|
|
|
/**
|
|
* The preview page id.
|
|
* ID for displaying the page in the frontend, "save and view"
|
|
* Is set to the pid value of the last shown record from "viewId" - thus indicating which page to
|
|
* show when clicking the SAVE/VIEW button and transferred via GET/POST parameter "popViewId"
|
|
*/
|
|
protected int $popViewId = 0;
|
|
|
|
/**
|
|
* If true, $this->editconf array is added a redirect response, used by Wizard/AddController
|
|
*/
|
|
protected bool $returnEditConf = false;
|
|
|
|
/**
|
|
* @var array
|
|
*/
|
|
protected $pageinfo;
|
|
|
|
/**
|
|
* Array of the elements to create edit forms for.
|
|
*
|
|
* @var FormElementData[]
|
|
*/
|
|
protected array $elementsData = [];
|
|
|
|
/**
|
|
* Pointer to the first element in $elementsData
|
|
*/
|
|
protected ?FormElementData $firstEl = null;
|
|
|
|
/**
|
|
* Counter, used to count the number of errors (when users do not have edit permissions)
|
|
*/
|
|
protected int $numberOfErrors = 0;
|
|
|
|
protected ?ModuleInterface $module = null;
|
|
|
|
public function __construct(
|
|
private readonly ComponentFactory $componentFactory,
|
|
protected readonly EventDispatcherInterface $eventDispatcher,
|
|
protected readonly IconFactory $iconFactory,
|
|
protected readonly RecordFactory $recordFactory,
|
|
protected readonly BreadcrumbFactory $breadcrumbFactory,
|
|
protected readonly PageRenderer $pageRenderer,
|
|
protected readonly UriBuilder $uriBuilder,
|
|
protected readonly ModuleTemplateFactory $moduleTemplateFactory,
|
|
protected readonly BackendEntryPointResolver $backendEntryPointResolver,
|
|
protected readonly ModuleProvider $moduleProvider,
|
|
private readonly FormDataCompiler $formDataCompiler,
|
|
private readonly NodeFactory $nodeFactory,
|
|
private readonly FormResultFactory $formResultFactory,
|
|
private readonly FormResultHandler $formResultHandler,
|
|
protected TcaSchemaFactory $tcaSchemaFactory,
|
|
protected readonly LocalizationRepository $localizationRepository,
|
|
private readonly SchemaLabelResolver $schemaLabelResolver,
|
|
protected readonly ResourceFactory $resourceFactory,
|
|
protected readonly FlashMessageService $flashMessageService,
|
|
) {}
|
|
|
|
/**
|
|
* Main dispatcher entry method registered as "record_edit" end point.
|
|
*/
|
|
public function mainAction(ServerRequestInterface $request): ResponseInterface
|
|
{
|
|
$view = $this->moduleTemplateFactory->create($request);
|
|
$view->setLayout(ModuleLayout::NORMAL);
|
|
$view->setUiBlock(true);
|
|
$body = '';
|
|
|
|
// Unlock all locked records
|
|
BackendUtility::lockRecords();
|
|
$queryParams = $request->getQueryParams();
|
|
$parsedBody = $request->getParsedBody();
|
|
$this->module = $this->moduleProvider->getModule((string)($queryParams['module'] ?? ''), $this->getBackendUser());
|
|
|
|
$this->editconf = $this->sanitizeEditConf($parsedBody['edit'] ?? $queryParams['edit'] ?? []);
|
|
$this->defVals = $parsedBody['defVals'] ?? $queryParams['defVals'] ?? null;
|
|
$this->overrideVals = $parsedBody['overrideVals'] ?? $queryParams['overrideVals'] ?? null;
|
|
$this->returnUrl = GeneralUtility::sanitizeLocalUrl($parsedBody['returnUrl'] ?? $queryParams['returnUrl'] ?? '', $request);
|
|
$this->returnEditConf = (bool)($parsedBody['returnEditConf'] ?? $queryParams['returnEditConf'] ?? false);
|
|
$this->columnsOnly = $this->prepareColumnsOnlyConfigurationFromRequest($request);
|
|
$this->popViewId = (int)($parsedBody['popViewId'] ?? $queryParams['popViewId'] ?? 0);
|
|
|
|
// Set overrideVals as default values if defVals does not exist.
|
|
// @todo: Why?
|
|
if (!is_array($this->defVals) && is_array($this->overrideVals)) {
|
|
$this->defVals = $this->overrideVals;
|
|
}
|
|
|
|
// Set final return URL
|
|
$this->retUrl = $this->returnUrl ?: $this->resolveDefaultReturnUrl();
|
|
|
|
// Close document if a request for closing the document has been sent
|
|
$requestAction = FormAction::createFromRequest($request);
|
|
if ($requestAction->shouldHandleDocumentClosing()) {
|
|
if ($response = $this->closeAndPossiblyRedirectAction($requestAction)) {
|
|
return $response;
|
|
}
|
|
}
|
|
|
|
$event = new BeforeFormEnginePageInitializedEvent($this, $request);
|
|
$this->eventDispatcher->dispatch($event);
|
|
|
|
// Process incoming data via DataHandler?
|
|
if ($requestAction->shouldProcessData()) {
|
|
$this->processData($view, $request);
|
|
// Redirect if element should be closed after save
|
|
if ($requestAction->shouldCloseAfterSave()) {
|
|
return $this->closeAndPossiblyRedirectAction($requestAction);
|
|
}
|
|
}
|
|
|
|
// Prepare current request url parameters (which might have been changed already, especially "editconf")
|
|
// Contains $request query parameters. This array is the foundation for creating
|
|
// the $currentEditingUrl var which becomes the url to which forms are submitted.
|
|
$queryParamsForGeneratingCurrentUrl = $queryParams;
|
|
$queryParamsForGeneratingCurrentUrl['edit'] = $this->editconf;
|
|
$queryParamsForGeneratingCurrentUrl['returnUrl'] = $this->retUrl;
|
|
if ($requestAction->shouldProcessData()) {
|
|
// Unset default values since we don't need them anymore. But only if all
|
|
// records have been persisted. If a DataHandler hook rejected a new record,
|
|
// editconf still contains the NEW* key and defVals must be preserved so
|
|
// the form reloads with the correct default values, such as the record type.
|
|
$hasUnresolvedNewRecords = false;
|
|
foreach ($this->editconf as $tableCmds) {
|
|
foreach (array_keys($tableCmds) as $uid) {
|
|
if (str_contains((string)$uid, 'NEW')) {
|
|
$hasUnresolvedNewRecords = true;
|
|
break 2;
|
|
}
|
|
}
|
|
}
|
|
if (!$hasUnresolvedNewRecords) {
|
|
unset($queryParamsForGeneratingCurrentUrl['defVals']);
|
|
}
|
|
}
|
|
|
|
// Preview code is implicit only generated for GET requests, having the query
|
|
// parameters "popViewId" (the preview page id) and "showPreview" set.
|
|
if ($this->popViewId && ($queryParams['showPreview'] ?? false)) {
|
|
// Generate the preview code (markup), which is added to the module body later
|
|
$body = $this->getPreviewUriBuilderForRecordPreview($this->popViewId)->buildImmediateActionElement([PreviewUriBuilder::OPTION_SWITCH_FOCUS => null]);
|
|
// After generating the preview code, those params should no longer be applied to the form
|
|
// action, as this would otherwise always refresh the preview window on saving the record.
|
|
unset($queryParamsForGeneratingCurrentUrl['showPreview'], $queryParamsForGeneratingCurrentUrl['popViewId']);
|
|
}
|
|
|
|
$event = new AfterFormEnginePageInitializedEvent($this, $request);
|
|
$this->eventDispatcher->dispatch($event);
|
|
|
|
if ($requestAction->isPostRequest) {
|
|
// In case save&view is requested, we have to add this information to the redirect
|
|
// URL, since the ImmediateAction will be added to the module body afterward.
|
|
if ($requestAction->savedokview()) {
|
|
$queryParamsForGeneratingCurrentUrl['showPreview'] = true;
|
|
$queryParamsForGeneratingCurrentUrl['popViewId'] = $this->popViewId;
|
|
}
|
|
$url = $this->uriBuilder->buildUriFromRoute('record_edit', $queryParamsForGeneratingCurrentUrl);
|
|
return new RedirectResponse($url, 302);
|
|
}
|
|
|
|
// Begin to show the edit form
|
|
$this->setModuleContext($view);
|
|
$this->pageRenderer->addInlineLanguageLabelFile('EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf');
|
|
$this->pageRenderer->addInlineSetting('ShowItem', 'moduleUrl', (string)$this->uriBuilder->buildUriFromRoute('show_item'));
|
|
|
|
// Generate the URL to the current request with modified GET parameters
|
|
// This is used in various places to "return to" in the form and for buttons etc.
|
|
$currentEditingUrl = $this->uriBuilder->buildUriFromRoute('record_edit', $queryParamsForGeneratingCurrentUrl);
|
|
|
|
// Creating the editing form, wrap it with buttons, document selector etc.
|
|
$formResults = $this->makeEditForm($request, $view, $currentEditingUrl);
|
|
if (count($formResults) > 0) {
|
|
$this->firstEl = $this->elementsData !== [] ? reset($this->elementsData) : null;
|
|
$lastEl = $this->elementsData !== [] ? end($this->elementsData) : null;
|
|
// Dispatch event for extensions to track open documents
|
|
$this->openCurrentDocuments();
|
|
$this->formResultHandler->addAssets($formResults);
|
|
// Put together the various elements (buttons, selectors, form) into a table
|
|
$body .= '
|
|
<form
|
|
action="' . htmlspecialchars((string)$currentEditingUrl) . '"
|
|
method="post"
|
|
enctype="multipart/form-data"
|
|
name="editform"
|
|
id="EditDocumentController"
|
|
>
|
|
' . $formResults->getHtml() . '
|
|
<input type="hidden" name="returnUrl" value="' . htmlspecialchars($this->retUrl) . '" />
|
|
<input type="hidden" name="popViewId" value="' . htmlspecialchars((string)$lastEl?->viewId) . '" />
|
|
<input type="hidden" name="closeDoc" value="0" />
|
|
<input type="hidden" name="returnNewPageId" value="' . ($this->returnNewPageId ? 1 : 0) . '" />';
|
|
$body .= '</form>';
|
|
}
|
|
|
|
if ($this->firstEl === null) {
|
|
// In case firstEl is null, no edit form could be created. Therefore, add an
|
|
// info box and remove the spinner, since it will never be resolved by FormEngine.
|
|
$view->setUiBlock(false);
|
|
$body .= $this->getInfobox(
|
|
$this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:noEditForm.message'),
|
|
$this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:noEditForm'),
|
|
);
|
|
}
|
|
|
|
// Access check...
|
|
// The page will show only if there is a valid page and if this page may be viewed by the user
|
|
$perms_clause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW);
|
|
$this->pageinfo = BackendUtility::readPageAccess($this->firstEl?->viewId, $perms_clause) ?: [];
|
|
|
|
$documentTitle = $this->resolveDocumentTitle($this->getLanguageService());
|
|
|
|
// Setting up the buttons, markers for doc header and navigation component state
|
|
$this->createBreadcrumb($view);
|
|
$this->getButtons($view, $request, $this->firstEl, $currentEditingUrl, $documentTitle);
|
|
|
|
// Create language switch options if the record is already persisted, and it is a single record to edit
|
|
if ($this->isSingleRecordView() && $this->firstEl?->isSavedRecord()) {
|
|
$this->languageSwitch($view, $this->firstEl);
|
|
}
|
|
|
|
$view->setTitle($documentTitle);
|
|
$view->setLayout(ModuleLayout::NORMAL);
|
|
$view->assign('bodyHtml', $body);
|
|
|
|
return $view->renderResponse('Form/EditDocument');
|
|
}
|
|
|
|
protected function sanitizeEditConf(array $editConf): array
|
|
{
|
|
$newConfiguration = [];
|
|
$beUser = $this->getBackendUser();
|
|
// Traverse the GPvar edit array tables
|
|
foreach ($editConf as $table => $conf) {
|
|
if (!is_array($conf) || !$this->tcaSchemaFactory->has($table)) {
|
|
// Skip for invalid config or in case no TCA exists
|
|
continue;
|
|
}
|
|
if (!$beUser->check('tables_modify', $table)) {
|
|
// Skip in case the user has insufficient permissions and increment the error counter
|
|
$this->numberOfErrors++;
|
|
continue;
|
|
}
|
|
// Traverse the keys/comments of each table (keys can be a comma list of uids)
|
|
foreach ($conf as $cKey => $command) {
|
|
if ($command !== 'edit' && $command !== 'new') {
|
|
// Skip if invalid command
|
|
continue;
|
|
}
|
|
$ids = GeneralUtility::trimExplode(',', (string)$cKey, true);
|
|
foreach ($ids as $id) {
|
|
$newConfiguration[$table][$id] = $command;
|
|
}
|
|
}
|
|
}
|
|
// Change $this->editconf if versioning applies to any of the records
|
|
return $this->fixWSversioningInEditConf($newConfiguration);
|
|
}
|
|
|
|
protected function setModuleContext(ModuleTemplate $view): void
|
|
{
|
|
$view->assign('moduleContext', '');
|
|
$view->assign('moduleContextId', '');
|
|
if ($this->module === null) {
|
|
return;
|
|
}
|
|
|
|
$view->setModuleName($this->module->getIdentifier());
|
|
|
|
if ($this->module->isStandalone()) {
|
|
$parent = $this->module;
|
|
} else {
|
|
$parent = $this->module->getParentModule();
|
|
while ($parent?->getParentModule() !== null) {
|
|
$parent = $parent->getParentModule();
|
|
}
|
|
}
|
|
if ($parent === null) {
|
|
return;
|
|
}
|
|
$moduleContext = $parent->getIdentifier();
|
|
|
|
if ($moduleContext === 'file') {
|
|
// Workaround for filelist using 'media' as ModuleStorage module context… :\
|
|
$moduleContext = 'media';
|
|
}
|
|
|
|
$view->assign('moduleContext', $moduleContext);
|
|
}
|
|
|
|
protected function prepareColumnsOnlyConfigurationFromRequest(ServerRequestInterface $request): array
|
|
{
|
|
$columnsOnly = $request->getParsedBody()['columnsOnly'] ?? $request->getQueryParams()['columnsOnly'] ?? null;
|
|
$usedTables = array_keys($request->getQueryParams()['edit'] ?? []);
|
|
$finalColumnsOnly = [];
|
|
if (is_array($columnsOnly) && $columnsOnly !== []) {
|
|
$finalColumnsOnly = array_map(function ($fields) {
|
|
return is_array($fields) ? $fields : GeneralUtility::trimExplode(',', $fields, true);
|
|
}, $columnsOnly);
|
|
$finalColumnsOnly = $this->addSlugFieldsToColumnsOnly($finalColumnsOnly, $usedTables);
|
|
}
|
|
return $finalColumnsOnly;
|
|
}
|
|
|
|
/**
|
|
* Always add required fields of slug field
|
|
*/
|
|
protected function addSlugFieldsToColumnsOnly(array $finalColumnsOnly, array $tables): array
|
|
{
|
|
foreach ($tables as $table) {
|
|
if (!empty($finalColumnsOnly[$table]) && $this->tcaSchemaFactory->has($table)) {
|
|
$schema = $this->tcaSchemaFactory->get($table);
|
|
foreach ($finalColumnsOnly[$table] as $field) {
|
|
if (!$schema->hasField($field)) {
|
|
continue;
|
|
}
|
|
$field = $schema->getField($field);
|
|
$postModifiers = $field->getConfiguration()['generatorOptions']['postModifiers'] ?? [];
|
|
if ($field->isType(TableColumnType::SLUG)
|
|
&& (!is_array($postModifiers) || $postModifiers === [])
|
|
) {
|
|
$fieldGroups = $field->getConfiguration()['generatorOptions']['fields'] ?? [];
|
|
if (is_string($fieldGroups)) {
|
|
$fieldGroups = [$fieldGroups];
|
|
}
|
|
foreach ($fieldGroups as $fields) {
|
|
$finalColumnsOnly['__hiddenGeneratorFields'][$table] = array_merge(
|
|
$finalColumnsOnly['__hiddenGeneratorFields'][$table] ?? [],
|
|
(is_array($fields) ? $fields : GeneralUtility::trimExplode(',', $fields, true))
|
|
);
|
|
}
|
|
}
|
|
}
|
|
if (!empty($finalColumnsOnly['__hiddenGeneratorFields'][$table])) {
|
|
$finalColumnsOnly['__hiddenGeneratorFields'][$table] = array_diff(
|
|
array_unique($finalColumnsOnly['__hiddenGeneratorFields'][$table]),
|
|
$finalColumnsOnly[$table]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
return $finalColumnsOnly;
|
|
}
|
|
|
|
/**
|
|
* Do processing of data, submitting it to DataHandler.
|
|
*
|
|
* Also handles the "duplication" of a record.
|
|
*/
|
|
protected function processData(ModuleTemplate $view, ServerRequestInterface $request): void
|
|
{
|
|
$requestAction = FormAction::createFromRequest($request);
|
|
$parsedBody = $request->getParsedBody();
|
|
|
|
$beUser = $this->getBackendUser();
|
|
|
|
$dataMap = $parsedBody['data'] ?? [];
|
|
$dataHandlerIncomingCommandMap = $parsedBody['cmd'] ?? [];
|
|
$this->returnNewPageId = (bool)($parsedBody['returnNewPageId'] ?? false);
|
|
|
|
/** @var DataHandler $dataHandler */
|
|
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
|
|
// Only options related to $dataMap submission are included here
|
|
$dataHandler->setControl($parsedBody['control'] ?? []);
|
|
|
|
// Set default values fetched previously from GET / POST vars
|
|
if (is_array($dataMap)) {
|
|
foreach ($dataMap as $tableName => $records) {
|
|
if (is_array($this->defVals[$tableName] ?? null)) {
|
|
foreach ($records as $uid => $_) {
|
|
if (str_contains((string)$uid, 'NEW')) {
|
|
$dataMap[$tableName][$uid] = array_merge($this->defVals[$tableName], $dataMap[$tableName][$uid]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load DataHandler with data
|
|
$dataHandler->start($dataMap, $dataHandlerIncomingCommandMap);
|
|
if (is_array($parsedBody['mirror'] ?? null)) {
|
|
$dataHandler->setMirror($parsedBody['mirror']);
|
|
}
|
|
|
|
// Perform the saving operation with DataHandler:
|
|
if ($requestAction->shouldProcessData()) {
|
|
$dataHandler->process_datamap();
|
|
$dataHandler->process_cmdmap();
|
|
|
|
// Update the module menu for the current backend user, as they updated their UI language
|
|
$currentUserId = $beUser->getUserId();
|
|
if ($currentUserId
|
|
&& (string)($dataMap['be_users'][$currentUserId]['lang'] ?? '') !== ''
|
|
&& $dataMap['be_users'][$currentUserId]['lang'] !== $beUser->user['lang']
|
|
) {
|
|
$newLanguageKey = $dataMap['be_users'][$currentUserId]['lang'];
|
|
// Update the current backend user language as well
|
|
$beUser->user['lang'] = $newLanguageKey;
|
|
// Re-create LANG to have the current request updated the translated page as well
|
|
$this->getLanguageService()->init($newLanguageKey);
|
|
BackendUtility::setUpdateSignal('updateBackendLanguage', [ 'language' => $newLanguageKey ]);
|
|
}
|
|
|
|
// If pages are being edited, we set an instruction about updating the page tree after this operation.
|
|
if ($dataHandler->pagetreeNeedsRefresh
|
|
&& (isset($dataMap['pages']) || $beUser->workspace !== 0 && !empty($dataMap))
|
|
) {
|
|
BackendUtility::setUpdateSignal('updatePageTree');
|
|
}
|
|
|
|
// If there was saved any new items, load them:
|
|
if (!empty($dataHandler->substNEWwithIDs)) {
|
|
// Save the expanded/collapsed states for new inline records, if any
|
|
$this->updateInlineView($request->getParsedBody()['uc'] ?? $request->getQueryParams()['uc'] ?? null, $dataHandler);
|
|
$newEditConf = [];
|
|
// Traverse all new records and forge the content of $this->editconf so we can continue to edit these records!
|
|
// $this->editconf is now updated to replace NEW-Ids with the actual persisted IDs.
|
|
foreach ($this->editconf as $tableName => $tableCmds) {
|
|
$keys = array_keys($dataHandler->substNEWwithIDs_table, $tableName);
|
|
if ($keys !== []) {
|
|
foreach ($keys as $key) {
|
|
$editId = $dataHandler->substNEWwithIDs[$key];
|
|
// Check if the $editId isn't a child record of an IRRE action
|
|
if (!(is_array($dataHandler->newRelatedIDs[$tableName] ?? null)
|
|
&& in_array($editId, $dataHandler->newRelatedIDs[$tableName]))
|
|
) {
|
|
// Translate new id to the workspace version
|
|
if ($versionRec = BackendUtility::getWorkspaceVersionOfRecord(
|
|
$beUser->workspace,
|
|
$tableName,
|
|
$editId,
|
|
'uid'
|
|
)) {
|
|
$editId = $versionRec['uid'];
|
|
}
|
|
$newEditConf[$tableName][$editId] = 'edit';
|
|
}
|
|
if ($tableName === 'pages'
|
|
&& !$this->shouldRedirectToEmptyPage()
|
|
&& $this->retUrl !== $this->getCloseUrl($request)
|
|
&& $this->returnNewPageId
|
|
) {
|
|
$this->retUrl .= '&id=' . $dataHandler->substNEWwithIDs[$key];
|
|
}
|
|
}
|
|
} else {
|
|
$newEditConf[$tableName] = $tableCmds;
|
|
}
|
|
}
|
|
if ($newEditConf !== []) {
|
|
$this->editconf = $newEditConf;
|
|
}
|
|
}
|
|
// See if any records was auto-created as new versions?
|
|
if (!empty($dataHandler->autoVersionIdMap)) {
|
|
$this->editconf = $this->fixWSversioningInEditConf($this->editconf, $dataHandler->autoVersionIdMap);
|
|
}
|
|
}
|
|
|
|
// If a document is saved and a new one is created right after.
|
|
if ($requestAction->savedoknew()) {
|
|
// Find the current table
|
|
reset($this->editconf);
|
|
$nTable = (string)key($this->editconf);
|
|
// Determine insertion mode: 'top' is self-explaining,
|
|
// otherwise new elements are inserted after one using a negative uid
|
|
$insertRecordOnTop = ($this->getTsConfigOption($nTable, 'saveDocNew') === 'top');
|
|
$ids = array_keys($this->editconf[$nTable]);
|
|
// Depending on $insertRecordOnTop, retrieve either the first or last id to get the records' pid+uid
|
|
if ($insertRecordOnTop) {
|
|
$nUid = (int)reset($ids);
|
|
} else {
|
|
$nUid = (int)end($ids);
|
|
}
|
|
$nRec = BackendUtility::getRecord($nTable, $nUid);
|
|
if ($insertRecordOnTop) {
|
|
$relatedPageId = $nRec['pid'];
|
|
} else {
|
|
if ((int)($nRec['t3ver_oid'] ?? 0) === 0) {
|
|
$relatedPageId = -$nRec['uid'];
|
|
} else {
|
|
// Use uid of live version of workspace version
|
|
$relatedPageId = -$nRec['t3ver_oid'];
|
|
}
|
|
}
|
|
// Setting a blank editconf array for a new record:
|
|
$this->editconf = [];
|
|
$this->editconf[$nTable][$relatedPageId] = 'new';
|
|
}
|
|
|
|
// Explicitly require a save operation
|
|
if ($requestAction->shouldProcessData()) {
|
|
$erroneousRecords = $dataHandler->printLogErrorMessages();
|
|
$messages = [];
|
|
$table = (string)key($this->editconf);
|
|
$uidList = array_keys($this->editconf[$table]);
|
|
|
|
foreach ($uidList as $uid) {
|
|
$uid = (int)abs($uid);
|
|
if (!in_array($table . '.' . $uid, $erroneousRecords, true)) {
|
|
$realUidInPayload = ($tceSubstId = array_search($uid, $dataHandler->substNEWwithIDs, true)) !== false ? $tceSubstId : $uid;
|
|
$row = $dataMap[$table][$uid] ?? $dataMap[$table][$realUidInPayload] ?? null;
|
|
if ($row === null) {
|
|
continue;
|
|
}
|
|
// Ensure, uid is always available to make labels with foreign table lookups possible
|
|
$row['uid'] ??= $realUidInPayload;
|
|
// If the label column of the record is not available, fetch it from database.
|
|
// This is the case when EditDocumentController is booted in single field mode (e.g.
|
|
// Template module > 'info/modify' > edit 'setup' field) or in case the field is
|
|
// not in "showitem" or is set to readonly (e.g. "file" in sys_file_metadata).
|
|
$labelCapability = $this->tcaSchemaFactory->get($table)->getCapability(TcaSchemaCapability::Label);
|
|
$labelFields = $labelCapability->getAllLabelFieldNames();
|
|
foreach ($labelFields as $labelField) {
|
|
if (!isset($row[$labelField])) {
|
|
$tmpRecord = BackendUtility::getRecord($table, $uid, $labelFields);
|
|
if ($tmpRecord !== null) {
|
|
$row = array_merge($row, $tmpRecord);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
$recordTitle = BackendUtility::cropToTitleLength(BackendUtility::getRecordTitle($table, $row));
|
|
$messages[] = sprintf($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:notification.record_saved.message'), $recordTitle);
|
|
}
|
|
}
|
|
|
|
// Add messages to the flash message container only if the request is a save action (excludes "duplicate")
|
|
if ($messages !== []) {
|
|
$label = $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:notification.record_saved.title.plural');
|
|
if (count($messages) === 1) {
|
|
$label = $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:notification.record_saved.title.singular');
|
|
}
|
|
if (count($messages) > 10) {
|
|
$messages = [sprintf($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:notification.mass_saving.message'), count($messages))];
|
|
}
|
|
$defaultFlashMessageQueue = $this->flashMessageService->getMessageQueueByIdentifier(FlashMessageQueue::NOTIFICATION_QUEUE);
|
|
$flashMessage = new FlashMessage(
|
|
implode(LF, $messages),
|
|
$label,
|
|
ContextualFeedbackSeverity::OK,
|
|
true
|
|
);
|
|
$defaultFlashMessageQueue->enqueue($flashMessage);
|
|
}
|
|
}
|
|
|
|
// If a document should be duplicated.
|
|
if ($requestAction->duplicatedoc()) {
|
|
// Find current table
|
|
reset($this->editconf);
|
|
$nTable = (string)key($this->editconf);
|
|
// Find the first id, getting the records pid+uid
|
|
$nUid = array_keys($this->editconf[$nTable]);
|
|
$nUid = reset($nUid);
|
|
if (!MathUtility::canBeInterpretedAsInteger($nUid)) {
|
|
$nUid = $dataHandler->substNEWwithIDs[$nUid];
|
|
}
|
|
|
|
$nRec = BackendUtility::getRecord($nTable, $nUid);
|
|
|
|
// Setting a blank editconf array for a new record
|
|
$this->editconf = [];
|
|
|
|
if ((int)($nRec['t3ver_oid'] ?? 0) > 0) {
|
|
$relatedPageId = -$nRec['t3ver_oid'];
|
|
} else {
|
|
$relatedPageId = -$nRec['uid'];
|
|
}
|
|
|
|
/** @var DataHandler $duplicateTce */
|
|
$duplicateTce = GeneralUtility::makeInstance(DataHandler::class);
|
|
$duplicateCmd = [
|
|
$nTable => [
|
|
$nUid => [
|
|
'copy' => $relatedPageId,
|
|
],
|
|
],
|
|
];
|
|
|
|
$duplicateTce->start([], $duplicateCmd);
|
|
$duplicateTce->process_cmdmap();
|
|
$duplicateUid = $duplicateTce->copyMappingArray[$nTable][$nUid] ?? null;
|
|
if ($duplicateUid !== null) {
|
|
if ($nTable === 'pages') {
|
|
BackendUtility::setUpdateSignal('updatePageTree');
|
|
}
|
|
|
|
$this->editconf[$nTable][$duplicateUid] = 'edit';
|
|
|
|
// Inform the user of the duplication
|
|
$view->addFlashMessage($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.recordDuplicated'));
|
|
} else {
|
|
$this->numberOfErrors++;
|
|
// Inform the user about the failed duplication
|
|
$view->addFlashMessage(
|
|
$this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.recordDuplicationFailed'),
|
|
'',
|
|
ContextualFeedbackSeverity::ERROR
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected function getPreviewUriBuilderForRecordPreview($pageId): PreviewUriBuilder
|
|
{
|
|
$array_keys = array_keys($this->editconf);
|
|
$table = reset($array_keys);
|
|
$recordId = 0;
|
|
if ($table) {
|
|
$uids = array_keys($this->editconf[$table]);
|
|
$recordId = (int)((reset($uids) ?: null) ?? '');
|
|
}
|
|
return PreviewUriBuilder::createForRecordPreview($table, $recordId, $pageId);
|
|
}
|
|
|
|
protected function createBreadcrumb(ModuleTemplate $view): void
|
|
{
|
|
// Handle file metadata records
|
|
$file = null;
|
|
if ($this->firstEl !== null && $this->firstEl->table === 'sys_file_metadata' && $this->firstEl->uid > 0) {
|
|
// Happens if it is a select/group
|
|
$fileUid = $this->firstEl->record['file'] ?? 0;
|
|
if (is_array($fileUid)) {
|
|
$fileUid = reset($fileUid);
|
|
}
|
|
try {
|
|
$file = $this->resourceFactory->getFileObject((int)$fileUid);
|
|
} catch (FileDoesNotExistException|InsufficientUserPermissionsException $e) {
|
|
// do nothing when file is not accessible
|
|
}
|
|
}
|
|
|
|
if ($file instanceof FileInterface) {
|
|
$view->assign('moduleContextId', $file->getParentFolder()->getCombinedIdentifier());
|
|
$view->getDocHeaderComponent()->setResourceBreadcrumb($file);
|
|
} elseif ($this->pageinfo !== [] && $this->firstEl !== null) {
|
|
$l10nParent = (int)($this->pageinfo['l10n_parent'] ?? 0);
|
|
$pageUid = $this->pageinfo['uid'] ?? '';
|
|
$view->assign('moduleContextId', $l10nParent !== 0 ? $l10nParent : $pageUid);
|
|
|
|
// Determine breadcrumb based on action (edit existing vs. create new)
|
|
if ($this->firstEl->isSavedRecord()) {
|
|
if ($this->isSingleRecordView()) {
|
|
// Edit single existing record
|
|
$breadcrumbContext = $this->breadcrumbFactory->forEditAction(
|
|
$this->firstEl->table,
|
|
(int)$this->firstEl->uid
|
|
);
|
|
} else {
|
|
// Edit multiple records
|
|
$breadcrumbContext = $this->breadcrumbFactory->forEditMultipleAction(
|
|
$this->firstEl->table,
|
|
(int)($this->pageinfo['uid'] ?? 0)
|
|
);
|
|
}
|
|
} else {
|
|
// Create new record
|
|
$breadcrumbContext = $this->breadcrumbFactory->forNewAction(
|
|
$this->firstEl->table,
|
|
(int)($this->pageinfo['uid'] ?? 0),
|
|
$this->defVals[$this->firstEl->table] ?? []
|
|
);
|
|
}
|
|
$view->getDocHeaderComponent()->setBreadcrumbContext($breadcrumbContext);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates the editing form with FormEngine, based on the input from GPvars.
|
|
*
|
|
* @return FormResultCollection Form result objects
|
|
*/
|
|
protected function makeEditForm(ServerRequestInterface $request, ModuleTemplate $view, UriInterface $currentRequestUrl): FormResultCollection
|
|
{
|
|
// Initialize variables
|
|
$formResults = new FormResultCollection();
|
|
// Traverse the GPvar edit array tables
|
|
foreach ($this->editconf as $table => $conf) {
|
|
// Traverse the keys/comments of each table (keys can be a comma list of uids)
|
|
foreach ($conf as $theUid => $command) {
|
|
try {
|
|
$formDataCompilerInput = [
|
|
'request' => $request,
|
|
'tableName' => $table,
|
|
'vanillaUid' => (int)$theUid,
|
|
'command' => $command,
|
|
'returnUrl' => (string)$currentRequestUrl,
|
|
];
|
|
if (is_array($this->overrideVals) && is_array($this->overrideVals[$table])) {
|
|
$formDataCompilerInput['overrideValues'] = $this->overrideVals[$table];
|
|
}
|
|
if (is_array($this->defVals) && $this->defVals !== []) {
|
|
$formDataCompilerInput['defaultValues'] = $this->defVals;
|
|
}
|
|
|
|
$formData = $this->formDataCompiler->compile($formDataCompilerInput, GeneralUtility::makeInstance(TcaDatabaseRecord::class));
|
|
|
|
$viewId = 0;
|
|
if ($table === 'pages') {
|
|
// Only set viewId in case it's not a new page - as this can not be viewed before being saved
|
|
if ($command !== 'new' && MathUtility::canBeInterpretedAsInteger($formData['databaseRow']['uid'])) {
|
|
$viewId = (int)$formData['databaseRow']['uid'];
|
|
}
|
|
} elseif (!empty($formData['parentPageRow']['uid'])) {
|
|
$viewId = $formData['parentPageRow']['uid'];
|
|
}
|
|
|
|
// Display "is-locked" message
|
|
if ($command === 'edit') {
|
|
$lockInfo = BackendUtility::isRecordLocked($table, $formData['databaseRow']['uid']);
|
|
if ($lockInfo) {
|
|
$view->addFlashMessage($lockInfo['msg'], '', ContextualFeedbackSeverity::WARNING);
|
|
}
|
|
}
|
|
|
|
$el = new FormElementData(
|
|
title: $formData['recordTitle'],
|
|
table: $table,
|
|
uid: $formData['databaseRow']['uid'],
|
|
pid: ($formData['databaseRow']['pid'] ?? $viewId),
|
|
record: $formData['databaseRow'],
|
|
viewId: (int)$viewId,
|
|
command: $command,
|
|
userPermissionOnPage: $formData['userPermissionOnPage'],
|
|
);
|
|
|
|
$this->elementsData[] = $el;
|
|
|
|
if ($command !== 'new') {
|
|
BackendUtility::lockRecords($table, $el->uid, $table === 'tt_content' ? $el->pid : 0);
|
|
}
|
|
|
|
// Set list if only specific fields should be rendered. This will trigger
|
|
// ListOfFieldsContainer instead of FullRecordContainer in FormWrapContainer
|
|
if (!empty($this->columnsOnly[$table])) {
|
|
$formData['fieldListToRender'] = implode(',', $this->columnsOnly[$table]);
|
|
if (!empty($this->columnsOnly['__hiddenGeneratorFields'][$table])) {
|
|
$formData['hiddenFieldListToRender'] = implode(',', $this->columnsOnly['__hiddenGeneratorFields'][$table]);
|
|
}
|
|
}
|
|
|
|
$formData['renderType'] = 'formWrapContainer';
|
|
$formResult = $this->nodeFactory->create($formData)->render();
|
|
|
|
if ($command === 'new') {
|
|
$tableTitle = htmlspecialchars($this->resolveTypeLabel($table, $formData['databaseRow']));
|
|
$formHeading = $this->getLanguageService()->sL('core.core:labels.createNew') . ' ' . $tableTitle;
|
|
$formResult['html'] = '<h1>' . $formHeading . '</h1>' . $formResult['html'];
|
|
} else {
|
|
$recordTitle = trim($formData['recordTitle']) !== ''
|
|
? $formData['recordTitle']
|
|
: '[' . $this->getLanguageService()->sL('core.core:labels.no_title') . ']';
|
|
$recordTitle = BackendUtility::cropToTitleLength($recordTitle);
|
|
$recordIdentity = $this->getRecordIdentityHtml($table, $formData['databaseRow']);
|
|
$formResult['html'] = '<h1>' . htmlspecialchars($recordTitle) . '</h1>' . $recordIdentity . $formResult['html'];
|
|
}
|
|
|
|
// Seems the pid is set as hidden field (again) at end?!
|
|
if ($command === 'new') {
|
|
$formResult['html'] .= '<input type="hidden"'
|
|
. ' name="data[' . htmlspecialchars($table) . '][' . htmlspecialchars($el->uid) . '][pid]"'
|
|
. ' value="' . $el->pid . '" />';
|
|
}
|
|
|
|
$formResult = $this->formResultFactory->create($formResult);
|
|
$formResults->add($formResult);
|
|
} catch (NoFieldsToRenderException $e) {
|
|
$this->numberOfErrors++;
|
|
$formResults->add(new FormResult(
|
|
$this->getInfobox(
|
|
$this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:noFieldsEditForm.message'),
|
|
$this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:noFieldsEditForm'),
|
|
)
|
|
));
|
|
} catch (AccessDeniedException $e) {
|
|
$this->numberOfErrors++;
|
|
|
|
$message = $e->getMessage();
|
|
$title = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.noEditPermission');
|
|
$formResults->add(new FormResult($this->getInfobox($message, $title)));
|
|
} catch (DatabaseRecordException|DatabaseRecordWorkspaceDeletePlaceholderException $e) {
|
|
$formResults->add(new FormResult($this->getInfobox($e->getMessage())));
|
|
}
|
|
}
|
|
}
|
|
return $formResults;
|
|
}
|
|
|
|
/**
|
|
* Helper function for rendering an Infobox
|
|
*/
|
|
protected function getInfobox(string $message, ?string $title = null): string
|
|
{
|
|
return '<div class="callout callout-danger">'
|
|
. '<div class="callout-icon">'
|
|
. '<span class="icon-emphasized">'
|
|
. $this->iconFactory->getIcon('actions-close', IconSize::SMALL)->render()
|
|
. '</span>'
|
|
. '</div>'
|
|
. '<div class="callout-content">'
|
|
. ($title ? '<div class="callout-title">' . htmlspecialchars($title) . '</div>' : '')
|
|
. '<div class="callout-body">' . htmlspecialchars($message) . '</div>'
|
|
. '</div>'
|
|
. '</div>';
|
|
}
|
|
|
|
/**
|
|
* Create the panel of buttons for submitting the form or otherwise perform operations.
|
|
*/
|
|
protected function getButtons(ModuleTemplate $view, ServerRequestInterface $request, ?FormElementData $mainFormElement, UriInterface $currentEditingUrl, string $documentTitle): void
|
|
{
|
|
if ($mainFormElement !== null) {
|
|
$record = $mainFormElement->record;
|
|
$schema = $this->tcaSchemaFactory->get($mainFormElement->table);
|
|
$this->registerCloseButtonToButtonBar($view, ButtonBar::BUTTON_POSITION_LEFT, 1);
|
|
|
|
// Show buttons (duplicate, new, view save) when table is not read-only
|
|
if (!$this->numberOfErrors && !$schema->hasCapability(TcaSchemaCapability::AccessReadOnly)) {
|
|
$view->addButtonToButtonBar($this->componentFactory->createSaveButton('EditDocumentController')->setDisabled(true), ButtonBar::BUTTON_POSITION_LEFT, 2);
|
|
$this->registerViewButtonToButtonBar($view, ButtonBar::BUTTON_POSITION_LEFT, 3);
|
|
|
|
if ($mainFormElement->command !== 'new') {
|
|
$languageCapability = $schema->isLanguageAware() ? $schema->getCapability(TcaSchemaCapability::Language) : null;
|
|
$languageId = 0;
|
|
if (
|
|
$mainFormElement->isSavedRecord()
|
|
&& $schema->isLanguageAware()
|
|
&& isset($record[($languageField = $languageCapability->getLanguageField()->getName())])
|
|
) {
|
|
$languageId = (int)$record[$languageField];
|
|
} elseif (isset($this->defVals[$mainFormElement->table]['language_tag'])) {
|
|
$languageId = (int)$this->defVals[$mainFormElement->table]['language_tag'];
|
|
}
|
|
$l10nParent = 0;
|
|
$translationOriginPointerField = $languageCapability ? $languageCapability->getTranslationOriginPointerField()->getName() : null;
|
|
if ($translationOriginPointerField && isset($record[$translationOriginPointerField])) {
|
|
$value = $record[$translationOriginPointerField];
|
|
if (is_array($value)) {
|
|
$value = reset($value);
|
|
}
|
|
// Happens on group
|
|
if (is_array($value) && isset($value['uid'])) {
|
|
$value = $value['uid'];
|
|
}
|
|
$l10nParent = (int)$value;
|
|
}
|
|
|
|
if ($mainFormElement->table === 'tt_content') {
|
|
$canCreateNewOrDuplicate = $this->isInconsistentLanguageHandlingAllowed() || $this->isPageContentFreeTranslationMode($mainFormElement, $languageId);
|
|
} else {
|
|
$canCreateNewOrDuplicate = $languageId === 0 || $l10nParent === 0;
|
|
}
|
|
if ($canCreateNewOrDuplicate) {
|
|
$this->registerNewButtonToButtonBar($view, ButtonBar::BUTTON_POSITION_LEFT, 4);
|
|
$this->registerDuplicationButtonToButtonBar($view, ButtonBar::BUTTON_POSITION_LEFT, 5);
|
|
}
|
|
}
|
|
if ($mainFormElement->isSavedRecord()) {
|
|
$this->registerHistoryButtonToButtonBar($view, ButtonBar::BUTTON_POSITION_RIGHT, 1, $currentEditingUrl);
|
|
$this->registerDeleteButtonToButtonBar($view, ButtonBar::BUTTON_POSITION_LEFT, 6, $request);
|
|
}
|
|
$this->registerColumnsOnlyButtonToButtonBar($view, ButtonBar::BUTTON_POSITION_LEFT, 7, $currentEditingUrl);
|
|
}
|
|
}
|
|
|
|
$this->registerInfoButtonToButtonBar($view, ButtonBar::BUTTON_POSITION_RIGHT, 2);
|
|
$this->registerOpenInNewWindowButtonToButtonBar($view, ButtonBar::BUTTON_POSITION_RIGHT, 3, $request);
|
|
$this->registerShortcutButtonToButtonBar($view, $request, $documentTitle);
|
|
}
|
|
|
|
/**
|
|
* Return true if inconsistent language handling is allowed
|
|
*/
|
|
protected function isInconsistentLanguageHandlingAllowed(): bool
|
|
{
|
|
$allowInconsistentLanguageHandling = BackendUtility::getPagesTSconfig(
|
|
$this->pageinfo['uid'] ?? 0
|
|
)['mod']['web_layout']['allowInconsistentLanguageHandling'] ?? ['value' => '0'];
|
|
|
|
return $allowInconsistentLanguageHandling['value'] === '1';
|
|
}
|
|
|
|
/**
|
|
* Checks if the page is in free translation mode for tt_content
|
|
*/
|
|
protected function isPageContentFreeTranslationMode(FormElementData $formElementData, int $languageId): bool
|
|
{
|
|
if ($formElementData->table !== 'tt_content') {
|
|
return false;
|
|
}
|
|
if (!$formElementData->isSavedRecord()) {
|
|
return $this->getFreeTranslationMode(
|
|
(int)($this->pageinfo['uid'] ?? 0),
|
|
(int)($this->defVals[$formElementData->table]['colPos'] ?? 0),
|
|
$languageId
|
|
);
|
|
}
|
|
return $this->getFreeTranslationMode(
|
|
(int)($this->pageinfo['uid'] ?? 0),
|
|
(int)($formElementData->record['colPos'] ?? 0),
|
|
$languageId
|
|
);
|
|
}
|
|
|
|
/**
|
|
* True if the page is in free translation mode.
|
|
*/
|
|
protected function getFreeTranslationMode(int $page, int $column, int $language): bool
|
|
{
|
|
$freeTranslationMode = false;
|
|
if ($this->getConnectedContentElementTranslationsCount($page, $column, $language) === 0
|
|
&& $this->getStandAloneContentElementTranslationsCount($page, $column, $language) >= 0
|
|
) {
|
|
$freeTranslationMode = true;
|
|
}
|
|
return $freeTranslationMode;
|
|
}
|
|
|
|
/**
|
|
* Register the close button to the button bar
|
|
*/
|
|
protected function registerCloseButtonToButtonBar(ModuleTemplate $view, string $position, int $group): void
|
|
{
|
|
$closeButton = $this->componentFactory->createLinkButton()
|
|
->setHref('#')
|
|
->setClasses('t3js-editform-close')
|
|
->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.closeDoc'))
|
|
->setShowLabelText(true)
|
|
->setIcon($this->iconFactory->getIcon('actions-close', IconSize::SMALL))
|
|
->setDisabled(true);
|
|
$view->addButtonToButtonBar($closeButton, $position, $group);
|
|
}
|
|
|
|
/**
|
|
* Register the view button to the button bar
|
|
*/
|
|
protected function registerViewButtonToButtonBar(ModuleTemplate $view, string $position, int $group): void
|
|
{
|
|
if ($this->firstEl === null) {
|
|
return;
|
|
}
|
|
// Pid to show the record
|
|
if (!$this->firstEl->viewId) {
|
|
return;
|
|
}
|
|
if ($this->firstEl->table === '') {
|
|
return;
|
|
}
|
|
// @TODO: TsConfig option should change to viewDoc
|
|
if (!$this->getTsConfigOption($this->firstEl->table, 'saveDocView')) {
|
|
return;
|
|
}
|
|
|
|
$previewUriBuilderForCurrentPage = PreviewUriBuilder::create($this->pageinfo)->isPreviewable();
|
|
$pageId = $this->popViewId ?: $this->firstEl->viewId;
|
|
$previewUriBuilder = PreviewUriBuilder::createForRecordPreview($this->firstEl->table, $this->firstEl->record, $pageId);
|
|
if ($previewUriBuilderForCurrentPage || $previewUriBuilder->isPreviewable()) {
|
|
$previewUrl = $previewUriBuilder->buildUri();
|
|
if ($previewUrl) {
|
|
$viewButton = $this->componentFactory->createLinkButton()
|
|
->setHref((string)$previewUrl)
|
|
->setIcon($this->iconFactory->getIcon('actions-view', IconSize::SMALL))
|
|
->setShowLabelText(true)
|
|
->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.viewDoc'))
|
|
->setClasses('t3js-editform-view')
|
|
->setDisabled(true);
|
|
if (!$this->firstEl->isSavedRecord() && $this->firstEl->table === 'pages') {
|
|
$viewButton->setDataAttributes(['is-new' => '']);
|
|
}
|
|
$view->addButtonToButtonBar($viewButton, $position, $group);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register the new button to the button bar
|
|
*/
|
|
protected function registerNewButtonToButtonBar(ModuleTemplate $view, string $position, int $group): void
|
|
{
|
|
if ($this->firstEl === null) {
|
|
return;
|
|
}
|
|
if ($this->firstEl->table === '') {
|
|
return;
|
|
}
|
|
if ($this->firstEl->table === 'sys_file_metadata') {
|
|
return;
|
|
}
|
|
if (!$this->getTsConfigOption($this->firstEl->table, 'saveDocNew')) {
|
|
return;
|
|
}
|
|
$newButton = $this->componentFactory->createLinkButton()
|
|
->setHref('#')
|
|
->setIcon($this->iconFactory->getIcon('actions-plus', IconSize::SMALL))
|
|
->setShowLabelText(true)
|
|
->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.newDoc'))
|
|
->setClasses('t3js-editform-new')
|
|
->setDisabled(true);
|
|
if (!$this->firstEl->isSavedRecord()) {
|
|
$newButton->setDataAttributes(['is-new' => '']);
|
|
}
|
|
$view->addButtonToButtonBar($newButton, $position, $group);
|
|
}
|
|
|
|
/**
|
|
* Register the duplication button to the button bar
|
|
*/
|
|
protected function registerDuplicationButtonToButtonBar(ModuleTemplate $view, string $position, int $group): void
|
|
{
|
|
if (!$this->isSingleRecordView()) {
|
|
return;
|
|
}
|
|
if ($this->firstEl->table === '') {
|
|
return;
|
|
}
|
|
if ($this->firstEl->table === 'sys_file_metadata') {
|
|
return;
|
|
}
|
|
if (!$this->getTsConfigOption($this->firstEl->table, 'showDuplicate')) {
|
|
return;
|
|
}
|
|
$duplicateButton = $this->componentFactory->createLinkButton()
|
|
->setHref('#')
|
|
->setShowLabelText(true)
|
|
->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.duplicateDoc'))
|
|
->setIcon($this->iconFactory->getIcon('actions-document-duplicates-select', IconSize::SMALL))
|
|
->setClasses('t3js-editform-duplicate')
|
|
->setDisabled(true);
|
|
if (!$this->firstEl->isSavedRecord()) {
|
|
$duplicateButton->setDataAttributes(['is-new' => '']);
|
|
}
|
|
$view->addButtonToButtonBar($duplicateButton, $position, $group);
|
|
}
|
|
|
|
/**
|
|
* Register the delete button to the button bar
|
|
*/
|
|
protected function registerDeleteButtonToButtonBar(ModuleTemplate $view, string $position, int $group, ServerRequestInterface $request): void
|
|
{
|
|
if (!$this->isSingleRecordView()) {
|
|
return;
|
|
}
|
|
if (!$this->firstEl?->isSavedRecord()) {
|
|
return;
|
|
}
|
|
if ($this->getDisableDelete()) {
|
|
return;
|
|
}
|
|
if ($this->isRecordCurrentBackendUser()) {
|
|
return;
|
|
}
|
|
if (!$this->firstEl->hasDeleteAccess()) {
|
|
return;
|
|
}
|
|
$returnUrl = $this->retUrl;
|
|
if ($this->firstEl->table === 'pages') {
|
|
// The below is a hack to replace the return url with an url to the current module on id=0. Otherwise,
|
|
// this might lead to empty views, since the current id is the page, which is about to be deleted.
|
|
$parsedUrl = parse_url($returnUrl);
|
|
// @todo consider using $this->module here
|
|
$routePath = str_replace($this->backendEntryPointResolver->getPathFromRequest($request), '', $parsedUrl['path'] ?? '');
|
|
parse_str($parsedUrl['query'] ?? '', $queryParams);
|
|
if ($routePath
|
|
&& isset($queryParams['id'])
|
|
&& (string)$this->firstEl->uid === (string)$queryParams['id']
|
|
) {
|
|
try {
|
|
// TODO: Use the page's pid instead of 0, this requires a clean API to manipulate the page
|
|
// tree from the outside to be able to mark the pid as active
|
|
$returnUrl = (string)$this->uriBuilder->buildUriFromRoutePath($routePath, ['id' => 0]);
|
|
} catch (ResourceNotFoundException $e) {
|
|
// Resolved path can not be matched to a configured route
|
|
}
|
|
}
|
|
}
|
|
|
|
$referenceIndex = GeneralUtility::makeInstance(ReferenceIndex::class);
|
|
$numberOfReferences = $referenceIndex->getNumberOfReferencedRecords(
|
|
$this->firstEl->table,
|
|
(int)$this->firstEl->uid
|
|
);
|
|
$referenceCountMessage = BackendUtility::referenceCount(
|
|
$this->firstEl->table,
|
|
(int)$this->firstEl->uid,
|
|
$this->getLanguageService()->sL(
|
|
'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.referencesToRecord'
|
|
),
|
|
(string)$numberOfReferences
|
|
);
|
|
|
|
$translations = $this->localizationRepository->getRecordTranslations(
|
|
$this->firstEl->table,
|
|
(int)$this->firstEl->uid,
|
|
);
|
|
$count = count($translations);
|
|
$translationCountMessage = '';
|
|
if ($count > 0) {
|
|
$translationCountMessage = sprintf($this->getLanguageService()->sL(
|
|
'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.translationsOfRecord'
|
|
), $count);
|
|
}
|
|
|
|
$deleteUrl = (string)$this->uriBuilder->buildUriFromRoute('tce_db', [
|
|
'cmd' => [
|
|
$this->firstEl->table => [
|
|
$this->firstEl->uid => [
|
|
'delete' => '1',
|
|
],
|
|
],
|
|
],
|
|
'redirect' => $returnUrl,
|
|
]);
|
|
|
|
$recordInfo = $this->firstEl->title;
|
|
if ($this->getBackendUser()->shallDisplayDebugInformation()) {
|
|
$recordInfo .= ' [' . $this->firstEl->table . ':' . $this->firstEl->uid . ']';
|
|
}
|
|
|
|
$deleteButton = $this->componentFactory->createLinkButton()
|
|
->setClasses('t3js-editform-delete-record')
|
|
->setDataAttributes([
|
|
'uid' => $this->firstEl->uid,
|
|
'table' => $this->firstEl->table,
|
|
'record-info' => trim($recordInfo),
|
|
'reference-count-message' => $referenceCountMessage,
|
|
'translation-count-message' => $translationCountMessage,
|
|
])
|
|
->setHref($deleteUrl)
|
|
->setIcon($this->iconFactory->getIcon('actions-edit-delete', IconSize::SMALL))
|
|
->setShowLabelText(true)
|
|
->setTitle($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:deleteItem'))
|
|
->setDisabled(true);
|
|
$view->addButtonToButtonBar($deleteButton, $position, $group);
|
|
}
|
|
|
|
/**
|
|
* Register the info button to the button bar
|
|
*/
|
|
protected function registerInfoButtonToButtonBar(ModuleTemplate $view, string $position, int $group): void
|
|
{
|
|
if (!$this->isSingleRecordView()) {
|
|
return;
|
|
}
|
|
if (!$this->firstEl?->isSavedRecord()) {
|
|
return;
|
|
}
|
|
$button = $this->componentFactory->createGenericButton();
|
|
$button->setLabel($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:showInfo'));
|
|
$button->setAttributes([
|
|
'type' => 'button',
|
|
'data-dispatch-action' => 'TYPO3.InfoWindow.showItem',
|
|
'data-dispatch-args-list' => $this->firstEl->table . ',' . $this->firstEl->uid,
|
|
'disabled' => 'disabled',
|
|
]);
|
|
$button->setIcon($this->iconFactory->getIcon('actions-document-info', IconSize::SMALL));
|
|
$view->addButtonToButtonBar($button, $position, $group);
|
|
}
|
|
|
|
/**
|
|
* Register the history button to the button bar
|
|
*/
|
|
protected function registerHistoryButtonToButtonBar(ModuleTemplate $view, string $position, int $group, UriInterface $currentEditingUrl): void
|
|
{
|
|
if (!$this->isSingleRecordView()) {
|
|
return;
|
|
}
|
|
if ($this->firstEl === null) {
|
|
return;
|
|
}
|
|
if ($this->firstEl->table === '') {
|
|
return;
|
|
}
|
|
if (!$this->getTsConfigOption($this->firstEl->table, 'showHistory', '1')) {
|
|
return;
|
|
}
|
|
$historyUrl = (string)$this->uriBuilder->buildUriFromRoute('record_history', [
|
|
'element' => $this->firstEl->table . ':' . $this->firstEl->uid,
|
|
'returnUrl' => (string)$currentEditingUrl,
|
|
]);
|
|
$historyButton = $this->componentFactory->createLinkButton()
|
|
->setHref($historyUrl)
|
|
->setTitle($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:recordHistory'))
|
|
->setIcon($this->iconFactory->getIcon('actions-document-history-open', IconSize::SMALL))
|
|
->setDisabled(true);
|
|
$view->addButtonToButtonBar($historyButton, $position, $group);
|
|
}
|
|
|
|
/**
|
|
* Register "Edit whole record" button to the button bar
|
|
*/
|
|
protected function registerColumnsOnlyButtonToButtonBar(ModuleTemplate $view, string $position, int $group, UriInterface $currentEditingUrl): void
|
|
{
|
|
if (!$this->isSingleRecordView()) {
|
|
return;
|
|
}
|
|
if ($this->columnsOnly === []) {
|
|
return;
|
|
}
|
|
$query = $currentEditingUrl->getQuery();
|
|
$query .= '&columnsOnly=';
|
|
$url = $currentEditingUrl->withQuery($query);
|
|
$columnsOnlyButton = $this->componentFactory->createLinkButton()
|
|
->setHref((string)$url)
|
|
->setTitle($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:editWholeRecord'))
|
|
->setShowLabelText(true)
|
|
->setIcon($this->iconFactory->getIcon('actions-open', IconSize::SMALL))
|
|
->setDisabled(true);
|
|
|
|
$view->addButtonToButtonBar($columnsOnlyButton, $position, $group);
|
|
}
|
|
|
|
/**
|
|
* Register the open in new window button to the button bar
|
|
*/
|
|
protected function registerOpenInNewWindowButtonToButtonBar(ModuleTemplate $view, string $position, int $group, ServerRequestInterface $request): void
|
|
{
|
|
$closeUrl = $this->getCloseUrl($request);
|
|
if ($this->returnUrl === $closeUrl) {
|
|
return;
|
|
}
|
|
// Generate a URL to the current edit form
|
|
$arguments = $this->getUrlQueryParamsForCurrentRequest($request);
|
|
$arguments['returnUrl'] = $closeUrl;
|
|
$requestUri = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $arguments);
|
|
$openInNewWindowButton = $this->componentFactory
|
|
->createLinkButton()
|
|
->setHref('#')
|
|
->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.openInNewWindow'))
|
|
->setIcon($this->iconFactory->getIcon('actions-window-open', IconSize::SMALL))
|
|
->setDataAttributes([
|
|
'dispatch-action' => 'TYPO3.WindowManager.localOpen',
|
|
'dispatch-args' => GeneralUtility::jsonEncodeForHtmlAttribute([
|
|
$requestUri,
|
|
true, // switchFocus
|
|
md5($requestUri), // windowName,
|
|
'width=670,height=500,status=0,menubar=0,scrollbars=1,resizable=1', // windowFeatures
|
|
]),
|
|
])
|
|
->setDisabled(true);
|
|
$view->addButtonToButtonBar($openInNewWindowButton, $position, $group);
|
|
}
|
|
|
|
/**
|
|
* Register the shortcut button to the button bar
|
|
*/
|
|
protected function registerShortcutButtonToButtonBar(ModuleTemplate $view, ServerRequestInterface $request, string $documentTitle): void
|
|
{
|
|
if ($this->returnUrl === $this->getCloseUrl($request)) {
|
|
return;
|
|
}
|
|
$arguments = $this->getUrlQueryParamsForCurrentRequest($request);
|
|
$view->getDocHeaderComponent()->setShortcutContext(
|
|
'record_edit',
|
|
$documentTitle,
|
|
$arguments
|
|
);
|
|
}
|
|
|
|
protected function getUrlQueryParamsForCurrentRequest(ServerRequestInterface $request): array
|
|
{
|
|
$queryParams = $request->getQueryParams();
|
|
$potentialArguments = [
|
|
'edit',
|
|
'defVals',
|
|
'overrideVals',
|
|
'columnsOnly',
|
|
'returnNewPageId',
|
|
'module',
|
|
];
|
|
$arguments = [];
|
|
foreach ($potentialArguments as $argument) {
|
|
if (!empty($queryParams[$argument])) {
|
|
$arguments[$argument] = $queryParams[$argument];
|
|
}
|
|
}
|
|
return $arguments;
|
|
}
|
|
|
|
/**
|
|
* Get the count of connected translated content elements
|
|
*/
|
|
protected function getConnectedContentElementTranslationsCount(int $page, int $column, int $language): int
|
|
{
|
|
$queryBuilder = $this->getQueryBuilderForTranslationMode($page, $column, $language);
|
|
return (int)$queryBuilder
|
|
->andWhere(
|
|
$queryBuilder->expr()->gt(
|
|
$this->tcaSchemaFactory->get('tt_content')->getCapability(TcaSchemaCapability::Language)->getTranslationOriginPointerField()->getName(),
|
|
$queryBuilder->createNamedParameter(0, Connection::PARAM_INT)
|
|
)
|
|
)
|
|
->executeQuery()
|
|
->fetchOne();
|
|
}
|
|
|
|
/**
|
|
* Get the count of standalone translated content elements
|
|
*/
|
|
protected function getStandAloneContentElementTranslationsCount(int $page, int $column, int $language): int
|
|
{
|
|
$queryBuilder = $this->getQueryBuilderForTranslationMode($page, $column, $language);
|
|
return (int)$queryBuilder
|
|
->andWhere(
|
|
$queryBuilder->expr()->eq(
|
|
$this->tcaSchemaFactory->get('tt_content')->getCapability(TcaSchemaCapability::Language)->getTranslationOriginPointerField()->getName(),
|
|
$queryBuilder->createNamedParameter(0, Connection::PARAM_INT)
|
|
)
|
|
)
|
|
->executeQuery()
|
|
->fetchOne();
|
|
}
|
|
|
|
/**
|
|
* Get the query builder for the translation mode
|
|
*/
|
|
protected function getQueryBuilderForTranslationMode(int $page, int $column, int $language): QueryBuilder
|
|
{
|
|
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
|
|
$queryBuilder->getRestrictions()
|
|
->removeAll()
|
|
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
|
|
->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->getBackendUser()->workspace));
|
|
return $queryBuilder
|
|
->count('uid')
|
|
->from('tt_content')
|
|
->where(
|
|
$queryBuilder->expr()->eq(
|
|
'pid',
|
|
$queryBuilder->createNamedParameter($page, Connection::PARAM_INT)
|
|
),
|
|
$queryBuilder->expr()->eq(
|
|
$this->tcaSchemaFactory->get('tt_content')->getCapability(TcaSchemaCapability::Language)->getLanguageField()->getName(),
|
|
$queryBuilder->createNamedParameter($language, Connection::PARAM_INT)
|
|
),
|
|
$queryBuilder->expr()->eq(
|
|
'colPos',
|
|
$queryBuilder->createNamedParameter($column, Connection::PARAM_INT)
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Update expanded/collapsed states on new inline records if any within backendUser->uc.
|
|
*
|
|
* @param array|null $uc The uc array to be processed and saved - uc[inlineView][...]
|
|
* @param DataHandler $dataHandler Instance of DataHandler that saved data before
|
|
*/
|
|
protected function updateInlineView(?array $uc, DataHandler $dataHandler): void
|
|
{
|
|
if (!is_array($uc['inlineView'] ?? null)) {
|
|
return;
|
|
}
|
|
$backendUser = $this->getBackendUser();
|
|
$inlineView = (array)json_decode(is_string($backendUser->uc['inlineView'] ?? false) ? $backendUser->uc['inlineView'] : '', true);
|
|
foreach ($uc['inlineView'] as $topTable => $topRecords) {
|
|
foreach ($topRecords as $topUid => $childElements) {
|
|
foreach ($childElements as $childTable => $childRecords) {
|
|
$uids = array_keys($dataHandler->substNEWwithIDs_table, $childTable);
|
|
if (!empty($uids)) {
|
|
$newExpandedChildren = [];
|
|
foreach ($childRecords as $childUid => $state) {
|
|
if ($state && in_array($childUid, $uids)) {
|
|
$newChildUid = $dataHandler->substNEWwithIDs[$childUid];
|
|
$newExpandedChildren[] = $newChildUid;
|
|
}
|
|
}
|
|
// Add new expanded child records to UC (if any):
|
|
if (!empty($newExpandedChildren)) {
|
|
$inlineViewCurrent = &$inlineView[$topTable][$topUid][$childTable];
|
|
if (is_array($inlineViewCurrent)) {
|
|
$inlineViewCurrent = array_unique(array_merge($inlineViewCurrent, $newExpandedChildren));
|
|
} else {
|
|
$inlineViewCurrent = $newExpandedChildren;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$backendUser->uc['inlineView'] = json_encode($inlineView);
|
|
$backendUser->writeUC();
|
|
}
|
|
|
|
/**
|
|
* Returns if delete for the current table is disabled by configuration.
|
|
* For sys_file_metadata in default language delete is always disabled.
|
|
*/
|
|
protected function getDisableDelete(): bool
|
|
{
|
|
$disableDelete = false;
|
|
if ($this->firstEl?->table === 'sys_file_metadata') {
|
|
$row = $this->firstEl->record;
|
|
if ((int)($row['language_tag'] ?? 0) === 0) {
|
|
// Always disable for default language
|
|
$disableDelete = true;
|
|
}
|
|
} else {
|
|
$disableDelete = (bool)$this->getTsConfigOption($this->firstEl->table, 'disableDelete');
|
|
}
|
|
return $disableDelete;
|
|
}
|
|
|
|
/**
|
|
* Return true in case the current record is the current backend user
|
|
*/
|
|
protected function isRecordCurrentBackendUser(): bool
|
|
{
|
|
$backendUser = $this->getBackendUser();
|
|
return $this->firstEl?->table === 'be_users' && (int)($this->firstEl->uid) === $backendUser->getUserId();
|
|
}
|
|
|
|
/**
|
|
* Returns the URL (usually for the "returnUrl") which closes the current window.
|
|
* Used when editing a record in a popup.
|
|
*/
|
|
protected function getCloseUrl(ServerRequestInterface $request): string
|
|
{
|
|
return (string)PathUtility::getSystemResourceUri('EXT:backend/Resources/Public/Html/Close.html', $request);
|
|
}
|
|
|
|
/**
|
|
* Make selector box for creating new translation for a record or switching to edit the record
|
|
* in an existing language. Displays only languages which are available for the current page.
|
|
*/
|
|
protected function languageSwitch(ModuleTemplate $view, FormElementData $formElement): void
|
|
{
|
|
$backendUser = $this->getBackendUser();
|
|
if (!$this->tcaSchemaFactory->has($formElement->table)) {
|
|
return;
|
|
}
|
|
$schema = $this->tcaSchemaFactory->get($formElement->table);
|
|
if (!$schema->isLanguageAware()) {
|
|
return;
|
|
}
|
|
$languageCapability = $schema->getCapability(TcaSchemaCapability::Language);
|
|
$languageField = $languageCapability->getLanguageField()->getName();
|
|
$transOrigPointerField = $languageCapability->getTranslationOriginPointerField()->getName();
|
|
|
|
$table = $formElement->table;
|
|
if (!$backendUser->check('tables_modify', $table)) {
|
|
return;
|
|
}
|
|
$uid = $formElement->uid;
|
|
|
|
// Get all available languages for the page
|
|
// If editing a page, the translations of the current UID need to be fetched
|
|
if ($table === 'pages') {
|
|
if (is_array($formElement->record[$transOrigPointerField] ?? null)) {
|
|
$l10nParent = $formElement->record[$transOrigPointerField];
|
|
$l10nParent = reset($l10nParent);
|
|
} else {
|
|
$l10nParent = $formElement->record[$transOrigPointerField] ?? 0;
|
|
}
|
|
// Ensure the check is always done against the default language page
|
|
$availableLanguages = $this->getLanguages(
|
|
(int)($l10nParent ?: $uid),
|
|
$table
|
|
);
|
|
} else {
|
|
$availableLanguages = $this->getLanguages($formElement->pid, $table);
|
|
}
|
|
// Remove default language, if user does not have access. This is necessary, since
|
|
// the default language is always added when fetching the system languages (#88504).
|
|
if (isset($availableLanguages[0]) && !$this->getBackendUser()->checkLanguageAccess(0)) {
|
|
unset($availableLanguages[0]);
|
|
}
|
|
// Page available in other languages than default language?
|
|
if (count($availableLanguages) > 1) {
|
|
$rowsByLang = [];
|
|
$fetchFields = ['uid', $languageField, $transOrigPointerField];
|
|
// Get record in current language
|
|
$rowCurrent = BackendUtility::getLiveVersionOfRecord($table, $uid, $fetchFields);
|
|
if (!is_array($rowCurrent)) {
|
|
$rowCurrent = BackendUtility::getRecord($table, $uid, $fetchFields);
|
|
}
|
|
$currentLanguage = (int)$rowCurrent[$languageField];
|
|
// Disabled for records with [all] language!
|
|
if ($currentLanguage > -1) {
|
|
// Get record in default language if needed
|
|
if ($currentLanguage && $rowCurrent[$transOrigPointerField]) {
|
|
$rowsByLang[0] = BackendUtility::getLiveVersionOfRecord(
|
|
$table,
|
|
$rowCurrent[$transOrigPointerField],
|
|
$fetchFields
|
|
);
|
|
if (!is_array($rowsByLang[0])) {
|
|
$rowsByLang[0] = BackendUtility::getRecord(
|
|
$table,
|
|
$rowCurrent[$transOrigPointerField],
|
|
$fetchFields
|
|
);
|
|
}
|
|
} else {
|
|
$rowsByLang[$rowCurrent[$languageField]] = $rowCurrent;
|
|
}
|
|
// List of language id's that should not be added to the selector
|
|
$noAddOption = [];
|
|
if ($rowCurrent[$transOrigPointerField] || $currentLanguage === 0) {
|
|
// Get record in other languages to see what's already available
|
|
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
|
|
$queryBuilder->getRestrictions()
|
|
->removeAll()
|
|
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
|
|
->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $backendUser->workspace));
|
|
$result = $queryBuilder->select(...$fetchFields)
|
|
->from($table)
|
|
->where(
|
|
$queryBuilder->expr()->eq(
|
|
'pid',
|
|
$queryBuilder->createNamedParameter($formElement->pid, Connection::PARAM_INT)
|
|
),
|
|
$queryBuilder->expr()->gt(
|
|
$languageField,
|
|
$queryBuilder->createNamedParameter(0, Connection::PARAM_INT)
|
|
),
|
|
$queryBuilder->expr()->eq(
|
|
$transOrigPointerField,
|
|
$queryBuilder->createNamedParameter($rowsByLang[0]['uid'], Connection::PARAM_INT)
|
|
)
|
|
)
|
|
->executeQuery();
|
|
while ($row = $result->fetchAssociative()) {
|
|
if ($backendUser->workspace !== 0 && $schema->isWorkspaceAware()) {
|
|
$workspaceVersion = BackendUtility::getWorkspaceVersionOfRecord($backendUser->workspace, $table, $row['uid'], 'uid,t3ver_state');
|
|
if (!empty($workspaceVersion)) {
|
|
$versionState = VersionState::tryFrom($workspaceVersion['t3ver_state'] ?? 0);
|
|
if ($versionState === VersionState::DELETE_PLACEHOLDER) {
|
|
// If a workspace delete placeholder exists for this translation: Mark
|
|
// this language as "don't add to selector" and continue with next row,
|
|
// otherwise an edit link to a delete placeholder would be created, which
|
|
// does not make sense.
|
|
$noAddOption[] = (int)$row[$languageField];
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
$rowsByLang[$row[$languageField]] = $row;
|
|
}
|
|
}
|
|
$languageDropDownButton = $this->componentFactory->createDropDownButton()
|
|
->setLabel($this->getLanguageService()->sL('core.core:labels.language'))
|
|
->setShowActiveLabelText(true)
|
|
->setShowLabelText(true);
|
|
|
|
$existingLanguageItems = [];
|
|
$newLanguageItems = [];
|
|
|
|
foreach ($availableLanguages as $languageId => $language) {
|
|
$selectorOptionLabel = $language['title'];
|
|
// Create url for creating a localized record
|
|
$addOption = true;
|
|
$createNewLanguageLink = '';
|
|
|
|
if (!isset($rowsByLang[$languageId])) {
|
|
// Translation in this language does not exist
|
|
if ($this->columnsOnly[$table] ?? false) {
|
|
// Don't add option since we are in a view with just a subset of fields, those views
|
|
// are specific editing fields only views and are not meant for translation handling.
|
|
$addOption = false;
|
|
} elseif (!isset($rowsByLang[0]['uid'])) {
|
|
// Don't add option since no default row to localize from exists
|
|
// TODO: Actually tt_content is able to localize from another l10n_source then L=0.
|
|
// This however is currently only possible via the translation wizard.
|
|
$addOption = false;
|
|
}
|
|
} else {
|
|
$params = [
|
|
'edit[' . $table . '][' . $rowsByLang[$languageId]['uid'] . ']' => 'edit',
|
|
'module' => $this->module?->getIdentifier() ?? '',
|
|
'returnUrl' => $this->retUrl,
|
|
];
|
|
if ($this->columnsOnly[$table] ?? false) {
|
|
$params['columnsOnly'] = [$table => $this->columnsOnly[$table]];
|
|
}
|
|
if ($table === 'pages') {
|
|
// Disallow manual adjustment of the language field for pages
|
|
$params['overrideVals'] = [
|
|
'pages' => [
|
|
'sys_language_uid' => $languageId,
|
|
],
|
|
];
|
|
}
|
|
$createNewLanguageLink = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $params);
|
|
}
|
|
if ($addOption && !in_array($languageId, $noAddOption, true)) {
|
|
if (!$createNewLanguageLink) {
|
|
$languageItem = $this->componentFactory->createDropDownItem()
|
|
->setTag('typo3-backend-localization-button')
|
|
->setAttribute('record-type', $table)
|
|
->setAttribute('record-uid', (string)$rowsByLang[0]['uid'])
|
|
->setAttribute('target-language', (string)$languageId)
|
|
->setLabel($selectorOptionLabel);
|
|
if (!empty($language['flagIcon'])) {
|
|
$languageItem->setIcon($this->iconFactory->getIcon($language['flagIcon']));
|
|
}
|
|
$newLanguageItems[] = $languageItem;
|
|
} else {
|
|
$isActive = $languageId === $currentLanguage;
|
|
$languageItem = $this->componentFactory->createDropDownRadio()
|
|
->setLabel($selectorOptionLabel)
|
|
->setHref($createNewLanguageLink)
|
|
->setActive($isActive);
|
|
if (!empty($language['flagIcon'])) {
|
|
$languageItem->setIcon($this->iconFactory->getIcon($language['flagIcon']));
|
|
}
|
|
$existingLanguageItems[] = $languageItem;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add existing languages first
|
|
foreach ($existingLanguageItems as $item) {
|
|
$languageDropDownButton->addItem($item);
|
|
}
|
|
|
|
// Add separator and new languages if any
|
|
if (!empty($newLanguageItems)) {
|
|
$languageDropDownButton->addItem($this->componentFactory->createDropDownDivider());
|
|
$languageDropDownButton->addItem(
|
|
$this->componentFactory->createDropDownHeader()
|
|
->setLabel($this->getLanguageService()->sL('core.core:labels.new_page_translation'))
|
|
);
|
|
foreach ($newLanguageItems as $item) {
|
|
$languageDropDownButton->addItem($item);
|
|
}
|
|
}
|
|
|
|
$view->getDocHeaderComponent()->setLanguageSelector($languageDropDownButton);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns languages available for record translations on given page.
|
|
*
|
|
* @param int $id Page id: If zero, all available system languages will be returned. If set to
|
|
* another value, only languages, a page translation exists for, will be returned.
|
|
* @param string $table For pages we want all languages, for other records the languages of the page translations
|
|
* @return array Array with languages (uid, title, ISOcode, flagIcon)
|
|
*/
|
|
protected function getLanguages(int $id, string $table): array
|
|
{
|
|
// This usually happens when a non-pages record is added after another, so we are fetching the proper page ID
|
|
if ($id < 0 && $table !== 'pages') {
|
|
$pageId = $this->pageinfo['uid'] ?? null;
|
|
if ($pageId !== null) {
|
|
$pageId = (int)$pageId;
|
|
} else {
|
|
$fullRecord = BackendUtility::getRecord($table, abs($id));
|
|
$pageId = (int)$fullRecord['pid'];
|
|
}
|
|
} else {
|
|
if ($table === 'pages' && $id > 0) {
|
|
$fullRecord = BackendUtility::getRecordWSOL('pages', $id);
|
|
$id = (int)($fullRecord['t3ver_oid'] ?: $fullRecord['uid']);
|
|
}
|
|
$pageId = $id;
|
|
}
|
|
// Fetch the current translations of this page, to only show the ones where there is a page translation
|
|
$allLanguages = array_filter(
|
|
GeneralUtility::makeInstance(TranslationConfigurationProvider::class)->getSystemLanguages($pageId),
|
|
static fn(array $language): bool => (int)$language['uid'] !== -1
|
|
);
|
|
if ($table !== 'pages' && $id > 0) {
|
|
$translatedPages = $this->localizationRepository->getPageTranslations($pageId, [], $this->getBackendUser()->workspace);
|
|
$availableLanguages = [];
|
|
if ($allLanguages[0] ?? false) {
|
|
$availableLanguages = [
|
|
0 => $allLanguages[0],
|
|
];
|
|
}
|
|
foreach ($translatedPages as $translatedPage) {
|
|
$languageId = $translatedPage->get('sys_language_uid');
|
|
if (isset($allLanguages[$languageId])) {
|
|
$availableLanguages[$languageId] = $allLanguages[$languageId];
|
|
}
|
|
}
|
|
return $availableLanguages;
|
|
}
|
|
return $allLanguages;
|
|
}
|
|
|
|
/**
|
|
* Fix $this->editconf if versioning applies to any of the records
|
|
*
|
|
* @param array|null $mapArray Mapping between old and new ids if auto-versioning has been performed.
|
|
*/
|
|
protected function fixWSversioningInEditConf(array $editConf, ?array $mapArray = null): array
|
|
{
|
|
$finalConfiguration = [];
|
|
foreach ($editConf as $table => $conf) {
|
|
// Traverse the keys/comments of each table (keys can be a comma list of uids)
|
|
$newConf = [];
|
|
foreach ($conf as $theUid => $cmd) {
|
|
if ($cmd === 'edit') {
|
|
if (is_array($mapArray)) {
|
|
if ($mapArray[$table][$theUid] ?? false) {
|
|
$theUid = $mapArray[$table][$theUid];
|
|
}
|
|
} else {
|
|
// Default, look for versions in workspace for record:
|
|
$calcPRec = $this->getRecordForEdit($table, (int)$theUid);
|
|
if (is_array($calcPRec)) {
|
|
// Setting UID again if it had changed, due to workspace versioning.
|
|
$theUid = (int)$calcPRec['uid'];
|
|
}
|
|
}
|
|
// Add the possibly manipulated IDs to the new-build newConf array:
|
|
$newConf[$theUid] = $cmd;
|
|
} else {
|
|
$newConf[$theUid] = $cmd;
|
|
}
|
|
}
|
|
$finalConfiguration[$table] = $newConf;
|
|
}
|
|
return $finalConfiguration;
|
|
}
|
|
|
|
/**
|
|
* Get record for editing.
|
|
*
|
|
* @return array|false Returns record to edit, false if none
|
|
*/
|
|
protected function getRecordForEdit(string $table, int $recordId): array|bool
|
|
{
|
|
$schema = $this->tcaSchemaFactory->get($table);
|
|
// Fetch requested record:
|
|
$reqRecord = BackendUtility::getRecord($table, $recordId, 'uid,pid' . ($schema->isWorkspaceAware() ? ',t3ver_oid' : ''));
|
|
if (is_array($reqRecord)) {
|
|
// If workspace is OFFLINE:
|
|
if ($this->getBackendUser()->workspace !== 0) {
|
|
// Check for versioning support of the table:
|
|
if ($schema->isWorkspaceAware()) {
|
|
// If the record is already a version of "something" pass it by.
|
|
if ($reqRecord['t3ver_oid'] > 0 || VersionState::tryFrom($reqRecord['t3ver_state'] ?? 0) === VersionState::NEW_PLACEHOLDER) {
|
|
// (If it turns out not to be a version of the current workspace there will be trouble, but
|
|
// that is handled inside DataHandler then and in the interface it would clearly be an error of
|
|
// links if the user accesses such a scenario)
|
|
return $reqRecord;
|
|
}
|
|
// The input record was online and an offline version must be found or made:
|
|
// Look for version of this workspace:
|
|
$versionRec = BackendUtility::getWorkspaceVersionOfRecord(
|
|
$this->getBackendUser()->workspace,
|
|
$table,
|
|
$reqRecord['uid'],
|
|
'uid,pid,t3ver_oid'
|
|
);
|
|
return is_array($versionRec) ? $versionRec : $reqRecord;
|
|
}
|
|
// This means that editing cannot occur on this record because it was not supporting versioning
|
|
// which is required inside an offline workspace.
|
|
return false;
|
|
}
|
|
// In ONLINE workspace, just return the originally requested record:
|
|
return $reqRecord;
|
|
}
|
|
// Return FALSE because the table/uid was not found anyway.
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* The return value is used for the variable $this->storeArray to prepare 'open documents' urls
|
|
*/
|
|
protected function compileStoreData(ServerRequestInterface $request, array $overriddenValues): array
|
|
{
|
|
$queryParams = $request->getQueryParams();
|
|
$parsedBody = $request->getParsedBody();
|
|
|
|
$storeArray = [];
|
|
foreach (['edit', 'defVals', 'overrideVals' , 'columnsOnly'] as $key) {
|
|
$value = $overriddenValues[$key] ?? $parsedBody[$key] ?? $queryParams[$key] ?? null;
|
|
if ($value !== null) {
|
|
$storeArray[$key] = $value;
|
|
}
|
|
}
|
|
return $storeArray;
|
|
}
|
|
|
|
/**
|
|
* Get a TSConfig 'option.' array, possibly for a specific table.
|
|
*/
|
|
protected function getTsConfigOption(string $table, string $key, string $defaultValue = ''): string
|
|
{
|
|
return trim((string)(
|
|
$this->getBackendUser()->getTSConfig()['options.'][$key . '.'][$table]
|
|
?? $this->getBackendUser()->getTSConfig()['options.'][$key]
|
|
?? $defaultValue
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Called when someone is done finishing editing - either by just hitting "close" or "save + close",
|
|
* but also for New/AddController when using returnEditConf.
|
|
*
|
|
* At this time, "closing" open documents in the session and unlocking should be done already.
|
|
*
|
|
* @return ResponseInterface|null Redirect response if needed
|
|
*/
|
|
protected function closeAndPossiblyRedirectAction(FormAction $requestAction): ?ResponseInterface
|
|
{
|
|
if ($requestAction->shouldCloseWithARedirect()) {
|
|
// If ->returnEditConf is set, then add the current content of editconf to the ->retUrl variable: used by
|
|
// other scripts, like wizard_add, to know which records was created or so...
|
|
if ($this->returnEditConf && !$this->shouldRedirectToEmptyPage()) {
|
|
$this->retUrl .= '&returnEditConf=' . rawurlencode((string)json_encode($this->editconf));
|
|
}
|
|
return new RedirectResponse($this->retUrl, 303);
|
|
}
|
|
if ($this->retUrl === '') {
|
|
return null;
|
|
}
|
|
return new RedirectResponse((string)$this->returnUrl, 303);
|
|
}
|
|
|
|
protected function shouldRedirectToEmptyPage(): bool
|
|
{
|
|
return $this->retUrl === (string)$this->uriBuilder->buildUriFromRoute('dummy');
|
|
}
|
|
|
|
/**
|
|
* Close the currently open document(s) by dispatching the appropriate event.
|
|
* This is called when the user explicitly closes, or when transitioning to a new/duplicated record.
|
|
*/
|
|
/**
|
|
* Notify extensions that document(s) have been opened for editing.
|
|
* Dispatches one event per record being opened.
|
|
*/
|
|
protected function openCurrentDocuments(): void
|
|
{
|
|
// Dispatch one event per record
|
|
foreach ($this->elementsData as $element) {
|
|
$this->eventDispatcher->dispatch(
|
|
new AfterRecordOpenedEvent(
|
|
table: $element->table,
|
|
uid: $element->uid,
|
|
record: $element->record,
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resolves the document title used for the browser tab and shortcut.
|
|
*
|
|
* Examples:
|
|
* - No form: "Edit form could not be loaded"
|
|
* - New record: "Create new {table}"
|
|
* - Single record: "{record title} · {table} · #{uid}"
|
|
* - Record with subtype: "{record title} · {subtype} · #{uid}"
|
|
* - Multiple records: "Edit multiple · {table} · N records"
|
|
*/
|
|
protected function resolveDocumentTitle(LanguageService $languageService): string
|
|
{
|
|
$firstEl = $this->elementsData[0] ?? null;
|
|
if ($firstEl === null) {
|
|
return $languageService->sL('backend.alt_doc:noEditForm');
|
|
}
|
|
|
|
$typeLabel = htmlspecialchars($this->resolveTypeLabel($firstEl->table, $firstEl->record));
|
|
|
|
if ($firstEl->command === 'new') {
|
|
return $languageService->sL('core.core:labels.createNew') . ' ' . $typeLabel;
|
|
}
|
|
|
|
$recordCount = count($this->elementsData);
|
|
if ($recordCount > 1) {
|
|
return implode(' · ', [
|
|
$languageService->sL('core.core:labels.editMultiple'),
|
|
$typeLabel,
|
|
$languageService->translate('labels.records', 'core.core', ['count' => $recordCount]),
|
|
]);
|
|
}
|
|
|
|
$recordTitle = trim($firstEl->title) !== ''
|
|
? $firstEl->title
|
|
: '[' . $languageService->sL('core.core:labels.no_title') . ']';
|
|
$recordTitle = BackendUtility::cropToTitleLength($recordTitle);
|
|
$recordTitle = htmlspecialchars($recordTitle);
|
|
|
|
return implode(' · ', array_filter([$recordTitle, $typeLabel, '#' . $firstEl->uid]));
|
|
}
|
|
|
|
/**
|
|
* Returns an HTML snippet showing the record type icon, table title and uid.
|
|
*/
|
|
protected function getRecordIdentityHtml(string $table, array $row): string
|
|
{
|
|
$icon = $this->iconFactory->getIconForRecord($table, $row, IconSize::SMALL)->render();
|
|
$tableTitle = $this->resolveTypeLabel($table, $row);
|
|
$uid = (string)($row['uid'] ?? '');
|
|
|
|
$recordType = '<span class="recordidentity-type">' . $icon . htmlspecialchars($tableTitle) . '</span>';
|
|
|
|
$debugInfo = $this->getBackendUser()->shallDisplayDebugInformation() ? $table . ':' : '';
|
|
$recordIdentity = sprintf(
|
|
'<small class="recordidentity-id" title="%s">[%s%s]</small>',
|
|
htmlspecialchars($this->getLanguageService()->sL('core.core:labels.uid') . ' ' . $uid),
|
|
htmlspecialchars($debugInfo),
|
|
htmlspecialchars($uid),
|
|
);
|
|
|
|
return '<div class="recordidentity mb-3">' . $recordType . $recordIdentity . '</div>';
|
|
}
|
|
|
|
/**
|
|
* Resolves a human-readable type label for a given table and record.
|
|
*/
|
|
protected function resolveTypeLabel(string $table, array $record): string
|
|
{
|
|
$languageService = $this->getLanguageService();
|
|
$schema = $this->tcaSchemaFactory->has($table)
|
|
? $this->tcaSchemaFactory->get($table)
|
|
: null;
|
|
$typeLabel = $schema !== null
|
|
? $schema->getTitle($languageService->sL(...))
|
|
: $table;
|
|
|
|
if ($schema !== null && $schema->supportsSubSchema()) {
|
|
$fieldName = $schema->getSubSchemaTypeInformation()->getFieldName();
|
|
$rawTypeValue = $record[$fieldName] ?? '';
|
|
$typeValue = is_array($rawTypeValue) ? (string)($rawTypeValue[0] ?? '') : (string)$rawTypeValue;
|
|
if ($typeValue !== '') {
|
|
$label = $languageService->sL($this->schemaLabelResolver->getLabelForFieldValue($table, $fieldName, $typeValue, $record));
|
|
if ($label === '' && $schema->hasSubSchema($typeValue)) {
|
|
$label = $schema->getSubSchema($typeValue)->getTitle($languageService->sL(...));
|
|
}
|
|
if ($label !== '') {
|
|
$typeLabel = $label;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $typeLabel;
|
|
}
|
|
|
|
protected function resolveDefaultReturnUrl(): string
|
|
{
|
|
$module = $this->moduleProvider->getFirstAccessibleModule($this->getBackendUser());
|
|
$routeName = $module ? $module->getIdentifier() : 'dummy';
|
|
return (string)$this->uriBuilder->buildUriFromRoute($routeName);
|
|
}
|
|
|
|
/**
|
|
* Whether a single record view is requested. This
|
|
* means, only one element exists in $elementsData.
|
|
*/
|
|
protected function isSingleRecordView(): bool
|
|
{
|
|
return count($this->elementsData) === 1;
|
|
}
|
|
|
|
protected function getBackendUser(): BackendUserAuthentication
|
|
{
|
|
return $GLOBALS['BE_USER'];
|
|
}
|
|
|
|
protected function getLanguageService(): LanguageService
|
|
{
|
|
return $GLOBALS['LANG'];
|
|
}
|
|
}
|