Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Classes/Dto/EditModeUrls.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace TYPO3\CMS\VisualEditor\Dto;

final readonly class EditModeUrls
{
public function __construct(
public string $backendEditUrl,
public string $newContentUrl,
public string $editContentUrl,
public ?string $editContentContextualUrl,
) {
}
}
50 changes: 50 additions & 0 deletions Classes/Events/ModifyNewContentElementWizardUrlParameterEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

namespace TYPO3\CMS\VisualEditor\Events;

use Psr\Http\Message\ServerRequestInterface;

final class ModifyNewContentElementWizardUrlParameterEvent
{
/**
* @param array<string, mixed> $parameters
* @param array<string, mixed> $usedArguments
*/
public function __construct(
private array $parameters,
private readonly array $usedArguments,
private readonly ServerRequestInterface $request,
) {
}

/**
* @return array<string, mixed>
*/
public function getParameters(): array
{
return $this->parameters;
}

/**
* @param array<string, mixed> $parameters
*/
public function setParameters(array $parameters): void
{
$this->parameters = $parameters;
}

/**
* @return array<string, mixed>
*/
public function getUsedArguments(): array
{
return $this->usedArguments;
}

public function getRequest(): ServerRequestInterface
{
return $this->request;
}
}
105 changes: 7 additions & 98 deletions Classes/Service/EditModeService.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
namespace TYPO3\CMS\VisualEditor\Service;

use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UriInterface;
use RuntimeException;
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Domain\Record;
use TYPO3\CMS\Core\Domain\RecordInterface;
Expand All @@ -16,21 +14,17 @@
use TYPO3\CMS\Core\Localization\LanguageServiceFactory;
use TYPO3\CMS\Core\Page\AssetCollector;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\Routing\PageArguments;
use TYPO3\CMS\Core\Schema\Capability\TcaSchemaCapability;
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Frontend\Page\PageInformation;

use function array_replace_recursive;
use function method_exists;

final readonly class EditModeService
{
public function __construct(
private AssetCollector $assetCollector,
private UriBuilder $uriBuilder,
private PageRenderer $pageRenderer,
private TcaSchemaFactory $tcaSchema,
private LanguageServiceFactory $languageServiceFactory,
Expand All @@ -39,6 +33,7 @@ public function __construct(
private FormProtectionFactory $formProtectionFactory,
private Typo3Version $typo3Version,
private AllowedOriginService $allowedOriginService,
private UrlGenerationService $urlGenerationService,
) {
}

Expand Down Expand Up @@ -86,39 +81,19 @@ public function init(ServerRequestInterface $request): void
throw new RuntimeException('Could not determine current site language', 3305745963);
}

$isExtContainerInstalled = ExtensionManagementUtility::isLoaded('container');

$backendEditUrl = (string)$this->getBackendEditUrl($request);

$newContentUrl = (string)$this->uriBuilder->buildUriFromRoute('new_content_element_wizard', [
'id' => $pageId,
'colPos' => '__COL_POS__',
'uid_pid' => '__UID_PID__',
...($isExtContainerInstalled ? ['tx_container_parent' => '__TX_CONTAINER_PARENT__'] : []),
'returnUrl' => $backendEditUrl,
]);

$editParams = [
'edit' => ['__TABLE__' => ['__UID__' => 'edit']],
'returnUrl' => $backendEditUrl,
'module' => 'web_edit',
];
$editContentUrl = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $editParams);
if ($this->typo3Version->getMajorVersion() >= 14) {
$editContentContextualUrl = (string)$this->uriBuilder->buildUriFromRoute('record_edit_contextual', $editParams);
}
$urls = $this->urlGenerationService->generateUrls($request);

$veInfo = [
'pageId' => $pageId,
'languageId' => $siteLanguage->getLanguageId(),
'showIdWithTitle' => !empty($this->getBeUser()->getTSConfig()['options.']['pageTree.']['showPageIdWithTitle']),
'backendEditUrl' => $backendEditUrl,
'newContentUrl' => $newContentUrl,
'editContentUrl' => $editContentUrl,
'editContentContextualUrl' => $editContentContextualUrl ?? null,
'backendEditUrl' => $urls->backendEditUrl,
'newContentUrl' => $urls->newContentUrl,
'editContentUrl' => $urls->editContentUrl,
'editContentContextualUrl' => $urls->editContentContextualUrl ?? null,
'allowNewContent' => $this->languageModeService->getAllowNewContent($pageInformation, $siteLanguage, $request),
'token' => $this->formProtectionFactory->createForType('backend')->generateToken('visual_editor', 'save'),
'routeArguments' => (object)$this->flattenBracketKeys(['params' => $this->getUsedArguments($request)]),
'routeArguments' => (object)$this->urlGenerationService->flattenBracketKeys(['params' => $this->urlGenerationService->getUsedArguments($request)]),
'allowedOrigins' => $this->allowedOriginService->getAllowedOrigins(),
];
$this->assetCollector->addInlineJavaScript(
Expand All @@ -143,27 +118,6 @@ public function init(ServerRequestInterface $request): void
}
}

/**
* @param array<array-key, string|float|int|bool|null|array<mixed>> $input
* @return array<string, string>
*/
private function flattenBracketKeys(array $input, string $prefix = ''): array
{
$result = [];

foreach ($input as $key => $value) {
$newKey = $prefix === '' ? (string)$key : $prefix . '[' . $key . ']';

if (is_array($value)) {
$result += $this->flattenBracketKeys($value, $newKey);
} else {
$result[$newKey] = (string)$value;
}
}

return $result;
}

public function canEditField(RecordInterface $record, string $field, ServerRequestInterface $request): bool
{
if (!$this->isEditMode($request)) {
Expand Down Expand Up @@ -230,51 +184,6 @@ private function loadLanguageLabelsInline(): void
}
}

public function getBackendEditUrl(ServerRequestInterface $request): UriInterface
{
// backend and Frontend Context: determine current page id
$pageInformation = $request->getAttribute('frontend.page.information');
if (!$pageInformation instanceof PageInformation) {
throw new RuntimeException('Could not determine current page information', 9965439961);
}

$pageId = $pageInformation->getId();
if (!$pageId) {
throw new RuntimeException('Could not determine current page id', 1768983081);
}

$siteLanguage = $request->getAttribute('language');
if (!$siteLanguage instanceof SiteLanguage) {
throw new RuntimeException('Could not determine current site language', 3305745963);
}

$usedArguments = $this->getUsedArguments($request);
return $this->uriBuilder->buildUriFromRoute('web_edit', [
'id' => $pageId,
// the selected viewMode and languages are saved in be_user->uc
'params' => $usedArguments,
]);
}

/**
* @return array<string|array<string|array<mixed>>>
*/
public function getUsedArguments(ServerRequestInterface $request): array
{
$routing = $request->getAttribute('routing');
if (!$routing instanceof PageArguments) {
throw new RuntimeException('Could not determine current routing context', 1773230232);
}

$usedArguments = array_replace_recursive(
$routing->getArguments(),
$routing->getRouteArguments(),
);
unset($usedArguments['cHash']);
unset($usedArguments['editMode']);
return $usedArguments;
}

private function getBeUser(): BackendUserAuthentication
{
$beUser = $GLOBALS['BE_USER'];
Expand Down
141 changes: 141 additions & 0 deletions Classes/Service/UrlGenerationService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
<?php

declare(strict_types=1);

namespace TYPO3\CMS\VisualEditor\Service;

use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UriInterface;
use RuntimeException;
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Core\Information\Typo3Version;
use TYPO3\CMS\Core\Routing\PageArguments;
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Frontend\Page\PageInformation;
use TYPO3\CMS\VisualEditor\Dto\EditModeUrls;
use TYPO3\CMS\VisualEditor\Events\ModifyNewContentElementWizardUrlParameterEvent;

final readonly class UrlGenerationService
{
public function __construct(
private EventDispatcherInterface $eventDispatcher,
private UriBuilder $uriBuilder,
private Typo3Version $typo3Version,
) {
}

public function generateUrls(ServerRequestInterface $request): EditModeUrls
{
$pageInformation = $request->getAttribute('frontend.page.information');
if (!$pageInformation instanceof PageInformation) {
throw new RuntimeException('Could not determine current page information', 9965439961);
}

$pageId = $pageInformation->getId();

if (!$pageId) {
throw new RuntimeException('Could not determine current page id', 1768983081);
}

$isExtContainerInstalled = ExtensionManagementUtility::isLoaded('container');

$backendEditUrl = (string)$this->getBackendEditUrl($request);

$event = $this->eventDispatcher->dispatch(
new ModifyNewContentElementWizardUrlParameterEvent([
'id' => $pageId,
'colPos' => '__COL_POS__',
'uid_pid' => '__UID_PID__',
...($isExtContainerInstalled ? ['tx_container_parent' => '__TX_CONTAINER_PARENT__'] : []),
'returnUrl' => $backendEditUrl,
], $this->getUsedArguments($request), $request),
);
assert($event instanceof ModifyNewContentElementWizardUrlParameterEvent);
$parameters = $event->getParameters();

$newContentUrl = (string)$this->uriBuilder->buildUriFromRoute('new_content_element_wizard', $parameters);

$editParams = [
'edit' => ['__TABLE__' => ['__UID__' => 'edit']],
'returnUrl' => $backendEditUrl,
'module' => 'web_edit',
];
$editContentUrl = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $editParams);
$editContentContextualUrl = null;
if ($this->typo3Version->getMajorVersion() >= 14) {
$editContentContextualUrl = (string)$this->uriBuilder->buildUriFromRoute('record_edit_contextual', $editParams);
}

return new EditModeUrls($backendEditUrl, $newContentUrl, $editContentUrl, $editContentContextualUrl);
}

public function getBackendEditUrl(ServerRequestInterface $request): UriInterface
{
// backend and Frontend Context: determine current page id
$pageInformation = $request->getAttribute('frontend.page.information');
if (!$pageInformation instanceof PageInformation) {
throw new RuntimeException('Could not determine current page information', 9965439961);
}

$pageId = $pageInformation->getId();
if (!$pageId) {
throw new RuntimeException('Could not determine current page id', 1768983081);
}

$siteLanguage = $request->getAttribute('language');
if (!$siteLanguage instanceof SiteLanguage) {
throw new RuntimeException('Could not determine current site language', 3305745963);
}

$usedArguments = $this->getUsedArguments($request);
return $this->uriBuilder->buildUriFromRoute('web_edit', [
'id' => $pageId,
// the selected viewMode and languages are saved in be_user->uc
'params' => $usedArguments,
]);
}

/**
* @return array<string|array<string|array<mixed>>>
*/
public function getUsedArguments(ServerRequestInterface $request): array
{
$routing = $request->getAttribute('routing');
if (!$routing instanceof PageArguments) {
throw new RuntimeException('Could not determine current routing context', 1773230232);
}

$usedArguments = array_replace_recursive(
$routing->getArguments(),
$routing->getRouteArguments(),
);
unset($usedArguments['cHash']);
unset($usedArguments['editMode']);
return $usedArguments;
}

/**
* @param array<array-key, string|float|int|bool|null|array<mixed>> $input
* @return array<string, string>
*/
public function flattenBracketKeys(array $input, string $prefix = ''): array
{
$result = [];

foreach ($input as $key => $value) {
$newKey = $prefix === '' ? (string)$key : $prefix . '[' . $key . ']';

if ($value === []) {
$result[$newKey] = '';
} elseif (is_array($value)) {
$result += $this->flattenBracketKeys($value, $newKey);
} else {
$result[$newKey] = (string)$value;
}
}

return $result;
}
}
Loading