Commit 07bfbc84 authored by Oliver Bartsch's avatar Oliver Bartsch Committed by Benni Mack
Browse files

[!!!][FEATURE] Introduce URL rewrites for Backend links

This change removes the &route parameter from all
Backend URLs, and changes them to speaking URLs
like /typo3/main?token=123123123.

For this change, it is necessary to adapt the
installations' webserver configuration rewrite
rules to enable URL rewriting in TYPO3 Backend.

An silent upgrader will automatically update
existing webserver configuration files like
.htaccess or web.config to adapt to the TYPO3
Backend navigation concepts.

Further steps will be using the browsers pushState
functionality to update the URL when a module changes,
and further deep-linking into TYPO3 Backend.

Resolves: #93048
Releases: master
Change-Id: Icdccfb4dbba71d2ecd08d619ed17ff49f6225b3e
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/67083

Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
parent 8e4875c6
......@@ -232,13 +232,26 @@ class Installer {
.then(async (response: AjaxResponse): Promise<any> => {
const data = await response.resolve();
if (data.success === true) {
this.checkDatabaseConnect();
this.executeSilentTemplateFileUpdate();
} else {
this.executeSilentConfigurationUpdate();
}
});
}
private executeSilentTemplateFileUpdate(): void {
(new AjaxRequest(this.getUrl('executeSilentTemplateFileUpdate')))
.get({cache: 'no-cache'})
.then(async (response: AjaxResponse): Promise<any> => {
const data = await response.resolve();
if (data.success === true) {
this.checkDatabaseConnect();
} else {
this.executeSilentTemplateFileUpdate();
}
});
}
private checkDatabaseConnect(): void {
this.setProgress(2);
(new AjaxRequest(this.getUrl('checkDatabaseConnect')))
......
......@@ -445,7 +445,7 @@ class LanguagePacks extends AbstractInteractableModule {
extensionTitle = $('<span>').append(
$('<img>', {
'style': 'max-height: 16px; max-width: 16px;',
'src': '../' + extension.icon,
'src': extension.icon,
'alt': extension.title,
}),
$('<span>').text(' ' + extension.title),
......
......@@ -126,7 +126,7 @@ class Router {
async (response: AjaxResponse): Promise<any> => {
const data = await response.resolve();
if (data.success === true) {
this.executeSilentExtensionConfigurationSynchronization();
this.executeSilentTemplateFileUpdate();
} else {
this.executeSilentConfigurationUpdate();
}
......@@ -137,6 +137,25 @@ class Router {
);
}
public executeSilentTemplateFileUpdate(): void {
this.updateLoadingInfo('Checking session and executing silent template file update');
(new AjaxRequest(this.getUrl('executeSilentTemplateFileUpdate', 'layout')))
.get({cache: 'no-cache'})
.then(
async (response: AjaxResponse): Promise<any> => {
const data = await response.resolve();
if (data.success === true) {
this.executeSilentExtensionConfigurationSynchronization();
} else {
this.executeSilentTemplateFileUpdate();
}
},
(error: AjaxResponse): void => {
this.handleAjaxError(error)
}
);
}
/**
* Extensions which come with new default settings in ext_conf_template.txt extension
* configuration files get their new defaults written to LocalConfiguration.
......
......@@ -390,7 +390,7 @@ class Clipboard
$size = $folder ? '' : '(' . GeneralUtility::formatSize((int)$fileObject->getSize()) . 'bytes)';
if (!$folder && $fileObject->isImage()) {
$thumb = [
'image' => $fileObject->process(ProcessedFile::CONTEXT_IMAGEPREVIEW, [])->getPublicUrl(true),
'image' => $fileObject->process(ProcessedFile::CONTEXT_IMAGEPREVIEW, [])->getPublicUrl(),
'title' => htmlspecialchars($fileObject->getName())
];
}
......
......@@ -43,6 +43,7 @@ use TYPO3\CMS\Core\Resource\Rendering\RendererRegistry;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Type\Bitmask\Permission;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Fluid\View\StandaloneView;
/**
......@@ -310,7 +311,7 @@ class ElementInformationController
} else {
$rendererRegistry = GeneralUtility::makeInstance(RendererRegistry::class);
$fileRenderer = $rendererRegistry->getRenderer($this->fileObject);
$preview['url'] = $this->fileObject->getPublicUrl(true);
$preview['url'] = PathUtility::getAbsoluteWebPath($this->fileObject->getPublicUrl());
$width = '590m';
$height = '400m';
......
......@@ -36,6 +36,7 @@ use TYPO3\CMS\Core\Resource\ProcessedFile;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Utility\File\ExtendedFileUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
/**
* Gateway for TCE (TYPO3 Core Engine) file-handling through POST forms.
......@@ -283,7 +284,7 @@ class FileController
if ($result->isImage()) {
$processedFile = $result->process(ProcessedFile::CONTEXT_IMAGEPREVIEW, []);
if ($processedFile) {
$thumbUrl = $processedFile->getPublicUrl(true);
$thumbUrl = PathUtility::getAbsoluteWebPath($processedFile->getPublicUrl());
}
}
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
......
......@@ -274,7 +274,7 @@ class TableManualRepository
// File reference
$fileName = GeneralUtility::getFileAbsFileName(substr($referenceUrl[1], 5));
if ($fileName && @is_file($fileName)) {
$fileName = '../' . PathUtility::stripPathSitePrefix($fileName);
$fileName = PathUtility::getAbsoluteWebPath($fileName);
$lines[] = [
'url' => $fileName,
'title' => $referenceUrl[0],
......@@ -355,7 +355,7 @@ class TableManualRepository
$descriptions = $descrArray[$k];
$absImagePath = GeneralUtility::getFileAbsFileName($image);
if ($absImagePath && @is_file($absImagePath)) {
$imgFile = PathUtility::stripPathSitePrefix($absImagePath);
$imgFile = PathUtility::getAbsoluteWebPath($absImagePath);
$imageInfo = GeneralUtility::makeInstance(ImageInfo::class, $absImagePath);
if ($imageInfo->getWidth()) {
$imageData[] = [
......
......@@ -32,6 +32,7 @@ use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Type\Bitmask\Permission;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\MathUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
/**
* Render a single inline record relation.
......@@ -386,8 +387,8 @@ class InlineRecordContainer extends AbstractContainer
$processedImage = $fileObject->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $imageSetup);
// Only use a thumbnail if the processing process was successful by checking if image width is set
if ($processedImage->getProperty('width')) {
$imageUrl = $processedImage->getPublicUrl(true);
$thumbnail = '<img src="' . $imageUrl . '" ' .
$imageUrl = $processedImage->getPublicUrl();
$thumbnail = '<img src="' . PathUtility::getAbsoluteWebPath($imageUrl) . '" ' .
'width="' . $processedImage->getProperty('width') . '" ' .
'height="' . $processedImage->getProperty('height') . '" ' .
'alt="' . htmlspecialchars($altText) . '" ' .
......
......@@ -23,6 +23,7 @@ use TYPO3\CMS\Core\Resource\File;
use TYPO3\CMS\Core\Resource\ProcessedFile;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
/**
* This renderType is used with type=user in FAL for table sys_file and
......@@ -76,9 +77,9 @@ class FileInfoElement extends AbstractFormElement
}
if ($file->isImage()) {
$processedFile = $file->process(ProcessedFile::CONTEXT_IMAGEPREVIEW, ['width' => 150, 'height' => 150]);
$previewImage = $processedFile->getPublicUrl(true);
$previewImage = $processedFile->getPublicUrl();
if ($previewImage) {
$content .= '<img src="' . htmlspecialchars($previewImage) . '" ' .
$content .= '<img src="' . htmlspecialchars(PathUtility::getAbsoluteWebPath($previewImage)) . '" ' .
'width="' . $processedFile->getProperty('width') . '" ' .
'height="' . $processedFile->getProperty('height') . '" ' .
'alt="" class="t3-tceforms-sysfile-imagepreview" />';
......
......@@ -62,7 +62,7 @@ class Application extends AbstractApplication
// Add applicationType attribute to request: This is backend and maybe backend ajax.
$applicationType = SystemEnvironmentBuilder::REQUESTTYPE_BE;
if (strpos($request->getQueryParams()['route'] ?? '', '/ajax/') === 0) {
if (strpos($request->getUri()->getPath(), '/typo3/ajax/') !== false || strpos($request->getQueryParams()['route'] ?? '', '/ajax/') === 0) {
$applicationType |= SystemEnvironmentBuilder::REQUESTTYPE_AJAX;
}
$request = $request->withAttribute('applicationType', $applicationType);
......
......@@ -69,6 +69,10 @@ class BackendRouteInitialization implements MiddlewareInterface
$route = $this->router->matchRequest($request);
$request = $request->withAttribute('route', $route);
$request = $request->withAttribute('target', $route->getOption('target'));
// add the GET parameter "route" for backwards-compatibility
$queryParams = $request->getQueryParams();
$queryParams['route'] = $route->getPath();
$request = $request->withQueryParams($queryParams);
} catch (ResourceNotFoundException $e) {
// Route not found in system
$uri = GeneralUtility::makeInstance(UriBuilder::class)->buildUriFromRoute('login');
......
......@@ -108,20 +108,28 @@ class Router implements SingletonInterface
*/
public function matchRequest(ServerRequestInterface $request)
{
// Allow the login page to be displayed if routing is not used and on index.php
// (consolidate RouteDispatcher::evaluateReferrer() when changing 'login' to something different)
$path = $request->getQueryParams()['route'] ?? $request->getParsedBody()['route'] ?? '/login';
$path = $request->getUri()->getPath();
if (($normalizedParams = $request->getAttribute('normalizedParams')) !== null) {
// Remove the directory name of the script from the path. This will usually be `/typo3` in this context.
$path = substr($path, strlen(dirname($normalizedParams->getScriptName())));
}
if ($path === '' || $path === '/' || $path === '/index.php') {
// Allow the login page to be displayed if routing is not used and on index.php
// (consolidate RouteDispatcher::evaluateReferrer() when changing 'login' to something different)
$path = $request->getQueryParams()['route'] ?? $request->getParsedBody()['route'] ?? '/login';
}
$context = new RequestContext(
'',
$path,
$request->getMethod(),
(string)HttpUtility::idn_to_ascii($request->getUri()->getHost()),
$request->getUri()->getScheme()
);
$result = (new UrlMatcher($this->routeCollection, $context))->match($path);
$matchedSymfonyRoute = $this->routeCollection->get($result['_route']);
$route = new Route($matchedSymfonyRoute->getPath(), $matchedSymfonyRoute->getOptions());
$route->setOption('_identifier', $result['_route']);
return $route;
if ($matchedSymfonyRoute === null) {
throw new ResourceNotFoundException('The requested resource "' . $path . '" was not found.', 1607596900);
}
return (new Route($matchedSymfonyRoute->getPath(), $matchedSymfonyRoute->getOptions()))
->setOption('_identifier', $result['_route']);
}
}
......@@ -119,36 +119,30 @@ class UriBuilder implements SingletonInterface
] + $parameters;
}
// Add the Route path as &route=XYZ
$parameters = [
'route' => $route->getPath()
] + $parameters;
$this->generated[$cacheIdentifier] = $this->buildUri($parameters, $referenceType);
$this->generated[$cacheIdentifier] = $this->buildUri($route->getPath(), $parameters, (string)$referenceType);
return $this->generated[$cacheIdentifier];
}
/**
* Internal method building a Uri object, merging the GET parameters array into a flat queryString
*
* @param string $route The route path to prepend
* @param array $parameters An array of GET parameters
* @param string $referenceType The type of reference to be generated (one of the constants)
*
* @return Uri
*/
protected function buildUri($parameters, $referenceType)
protected function buildUri(string $route, array $parameters, string $referenceType): Uri
{
$uri = 'index.php' . HttpUtility::buildQueryString($parameters, '?');
$path = ltrim($route . HttpUtility::buildQueryString($parameters, '?'), '/');
if ($referenceType === self::ABSOLUTE_PATH) {
$uri = PathUtility::getAbsoluteWebPath(Environment::getBackendPath() . '/' . $uri);
$uri = PathUtility::getAbsoluteWebPath(Environment::getBackendPath() . '/' . $path);
} elseif (($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface
&& $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams') instanceof NormalizedParams
) {
$uri = $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestDir() . $path;
} else {
if (isset($GLOBALS['TYPO3_REQUEST'])
&& $GLOBALS['TYPO3_REQUEST'] instanceof ServerRequestInterface
&& $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams') instanceof NormalizedParams) {
$uri = $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestDir() . $uri;
} else {
$uri = GeneralUtility::getIndpEnv('TYPO3_REQUEST_DIR') . $uri;
}
$uri = GeneralUtility::getIndpEnv('TYPO3_REQUEST_DIR') . $path;
}
return GeneralUtility::makeInstance(Uri::class, $uri);
}
......
......@@ -1057,7 +1057,7 @@ class BackendUtility
}
$processedImage = $fileObject->process($taskType, $processingConfiguration);
$attributes = [
'src' => $processedImage->getPublicUrl(true),
'src' => PathUtility::getAbsoluteWebPath($processedImage->getPublicUrl()),
'width' => $processedImage->getProperty('width'),
'height' => $processedImage->getProperty('height'),
'alt' => $fileReferenceObject->getName(),
......
......@@ -37,7 +37,7 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;
*
* Output::
*
* <a href="/typo3/index.php?route=/record/edit&edit[a_table][42]=edit&returnUrl=foo/bar">
* <a href="/typo3/record/edit?edit[a_table][42]=edit&returnUrl=foo/bar">
* Edit record
* </a>
*
......@@ -53,7 +53,7 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;
*
* Output::
*
* <a href="/typo3/index.php?route=/record/edit&edit[pages][42]=edit&returnUrl=foo/bar&columnsOnly=title,subtitle">
* <a href="/typo3/record/edit?edit[pages][42]=edit&returnUrl=foo/bar&columnsOnly=title,subtitle">
* Edit record
* </a>
*/
......
......@@ -49,7 +49,7 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;
*
* Output::
*
* <a href="/typo3/index.php?route=/record/edit&edit[a_table][-17]=new&returnUrl=foo/bar">
* <a href="/typo3/record/edit?edit[a_table][-17]=new&returnUrl=foo/bar">
* Edit record
* </a>
*
......@@ -59,7 +59,7 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;
*
* Output::
*
* <a href="/typo3/index.php?route=/record/edit&edit[a_table][]=new&returnUrl=foo/bar">
* <a href="/typo3/record/edit?edit[a_table][]=new&returnUrl=foo/bar">
* Edit record
* </a>
*
......@@ -69,7 +69,7 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;
*
* Output::
*
* <a href="/typo3/index.php?route=/record/edit&edit[a_table][17]=new&returnUrl=foo/bar">
* <a href="/typo3/record/edit?edit[a_table][17]=new&returnUrl=foo/bar">
* Edit record
* </a>
*
......@@ -79,7 +79,7 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;
*
* Output::
*
* <a href="/typo3/index.php?route=/record/edit&edit[a_table][17]=new&returnUrl=/typo3/index.php?route=/module/web/MyextensionList">
* <a href="/typo3/record/edit?edit[a_table][17]=new&returnUrl=/typo3/module/web/MyextensionList">
* Edit record
* </a>
*
......@@ -89,7 +89,7 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;
*
* Output::
*
* <a href="/typo3/index.php?route=/record/edit&edit[a_table][17]=new&returnUrl=foo/bar&defVals[a_table][a_field]=value">
* <a href="/typo3/record/edit?edit[a_table][17]=new&returnUrl=foo/bar&defVals[a_table][a_field]=value">
* Edit record
* </a>
*/
......
......@@ -18,6 +18,7 @@ namespace TYPO3\CMS\Backend\ViewHelpers;
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
use TYPO3\CMS\Core\Resource\ProcessedFile;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Fluid\ViewHelpers\ImageViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Exception;
......@@ -36,7 +37,7 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\Exception;
*
* Output::
*
* <img src="/typo3/index.php?route=/thumbnails&token=&parameters={"fileId":1,"configuration":{"_context":"Image.Preview","maxWidth":64,"maxHeight":64}}&hmac="
* <img src="/typo3/thumbnails?token=&parameters={"fileId":1,"configuration":{"_context":"Image.Preview","maxWidth":64,"maxHeight":64}}&hmac="
* width="64"
* height="64"
* alt="alt set in image record"
......@@ -51,7 +52,7 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\Exception;
*
* Output::
*
* <img src="/typo3/index.php?route=/thumbnails&token=&parameters={"fileId":1,"configuration":{"_context":"Image.Preview","maxWidth":64,"maxHeight":64}}&hmac="
* <img src="/typo3/thumbnails?token=&parameters={"fileId":1,"configuration":{"_context":"Image.Preview","maxWidth":64,"maxHeight":64}}&hmac="
* width="64"
* height="64"
* alt="alt set in image record"
......@@ -104,7 +105,7 @@ class ThumbnailViewHelper extends ImageViewHelper
}
$processedFile = $image->process($this->arguments['context'], $processingInstructions);
$imageUri = $processedFile->getPublicUrl(true);
$imageUri = PathUtility::getAbsoluteWebPath($processedFile->getPublicUrl());
if (!$this->tag->hasAttribute('data-focus-area')) {
$focusArea = $cropVariantCollection->getFocusArea($cropVariant);
......
......@@ -37,14 +37,14 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
*
* <be:uri.editRecord uid="42" table="a_table" returnUrl="foo/bar" />
*
* ``/typo3/index.php?route=/record/edit&edit[a_table][42]=edit&returnUrl=foo/bar``
* ``/typo3/record/edit?edit[a_table][42]=edit&returnUrl=foo/bar``
*
* URI to the edit record action: edit only the fields title and subtitle of
* page uid=42 and return to foo/bar::
*
* <be:uri.editRecord uid="42" table="pages" fields="title,subtitle" returnUrl="foo/bar" />
*
* ``<a href="/typo3/index.php?route=/record/edit&edit[pages][42]=edit&returnUrl=foo/bar&columnsOnly=title,subtitle">``
* ``<a href="/typo3/record/edit&edit[pages][42]=edit&returnUrl=foo/bar&columnsOnly=title,subtitle">``
*/
class EditRecordViewHelper extends AbstractViewHelper
{
......
......@@ -49,25 +49,25 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
*
* <be:uri.newRecord table="a_table" returnUrl="foo/bar" uid="-17"/>
*
* ``/typo3/index.php?route=/record/edit&edit[a_table][-17]=new&returnUrl=foo/bar``
* ``/typo3/record/edit?edit[a_table][-17]=new&returnUrl=foo/bar``
*
* Uri to create a new record of a_table on root page::
*
* <be:uri.newRecord table="a_table" returnUrl="foo/bar""/>
*
* ``/typo3/index.php?route=/record/edit&edit[a_table][]=new&returnUrl=foo/bar``
* ``/typo3/record/edit?edit[a_table][]=new&returnUrl=foo/bar``
*
* Uri to create a new record of a_table on page 17::
*
* <be:uri.newRecord table="a_table" returnUrl="foo/bar" pid="17"/>
*
* ``/typo3/index.php?route=/record/edit&edit[a_table][17]=new&returnUrl=foo/bar``
* ``/typo3/record/edit?edit[a_table][17]=new&returnUrl=foo/bar``
*
* Uri to create a new record of a_table on page 17 with a default value::
*
* <be:uri.newRecord table="a_table" returnUrl="foo/bar" pid="17" defaultValues="{a_table: {a_field: 'value'}}"/>
*
* ``/typo3/index.php?route=/record/edit&edit[a_table][17]=new&returnUrl=foo/bar&defVals[a_table][a_field]=value``
* ``/typo3/record/edit?edit[a_table][17]=new&returnUrl=foo/bar&defVals[a_table][a_field]=value``
*/
class NewRecordViewHelper extends AbstractTagBasedViewHelper
{
......
......@@ -19,7 +19,7 @@
<f:if condition="{manual.images}">
<f:for each="{manual.images}" as="image">
<div>
<img src="../{image.image}" class="img-responsive" />
<img src="{image.image}" class="img-responsive" />
<f:if condition="{image.description}">
<p>{image.description}</p>
</f:if>
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment