Commit 0943a9c5 authored by Oliver Bartsch's avatar Oliver Bartsch
Browse files

[TASK] Add convenience method for extbase JSON responses

Since #92784 extbase actions require to return a PSR-7 Response.
To ease the migration path, the convenience method htmlResponse()
was added. This method creates a new PSR-7 Response with the correct
header and status code. Furthermore, in case no $html content is
given explicitly, the current view is rendered and the result
passed as response body.

Extbase however also features the JsonView, which is widely
used in third-party extensions as well as in multiple core
controllers itself. Also TSFE has to be respected when adding
the content-type header with the correct charset. Therefore,
a new method jsonResponse(), with similar functionality to
htmlResponse(), is now added along with adaptations of all
places in core controllers.

Resolves: #94440
Related: #92784
Releases: master
Change-Id: Ic591a8788f586050578db8b4187e29325d180f49
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/69664

Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Tested-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
parent 92999a6e
......@@ -95,5 +95,25 @@ Example:
Since Extbase uses PSR-7 responses, you should make yourself familiar with its API.
Documentation and more information regarding PSR-7 responses can be found here: https://www.php-fig.org/psr/psr-7/#33-psrhttpmessageresponseinterface
In case you are using the :php:`JsonView` in your extbase controller, you may
want to ease the migration path with the new :php:`jsonResponse(string $json = null)`
method. Similar to :php:`htmlResponse()`, this method creates a PSR-7 Response
with the :html:`Content-Type: application/json` header and http code `200 Ok`.
If argument :php:`$json` is omitted, the current view is rendered automatically.
Example:
.. code-block:: php
public function listApiAction(): ResponseInterface
{
$items = $this->itemRepository->findAll();
$this->view->assign('value', [
'items' => $items
]);
return $this->jsonResponse();
}
.. index:: PHP-API, NotScanned, ext:extbase
......@@ -34,6 +34,7 @@ use TYPO3\CMS\Extbase\Persistence\ClassesConfigurationFactory;
use TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface;
use TYPO3\CMS\Extbase\Service\CacheService;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
/**
* Creates a request and dispatches it to the controller which was specified
......@@ -191,6 +192,27 @@ class Bootstrap
if ($response->getStatusCode() === 400) {
$this->clearCacheOnError();
}
// In case TSFE is available and this is a json response, we have
// to take the TypoScript settings regarding charset into account.
// @todo Since HTML5 only utf-8 is a valid charset, this settings should be deprecated
if (($typoScriptFrontendController = ($GLOBALS['TSFE'] ?? null)) instanceof TypoScriptFrontendController
&& strpos($response->getHeaderLine('Content-Type'), 'application/json') === 0
) {
// Unset the already defined Content-Type
$response = $response->withoutHeader('Content-Type');
if (empty($typoScriptFrontendController->config['config']['disableCharsetHeader'])) {
// If the charset header is *not* disabled in configuration,
// TypoScriptFrontendController will send the header later with the Content-Type which we set here.
$typoScriptFrontendController->setContentType('application/json');
} else {
// Although the charset header is disabled in configuration, we *must* send a Content-Type header here.
// Content-Type headers optionally carry charset information at the same time.
// Since we have the information about the charset, there is no reason to not include the charset information although disabled in TypoScript.
$response = $response->withHeader('Content-Type', 'application/json; charset=' . trim($typoScriptFrontendController->metaCharset));
}
}
if (headers_sent() === false) {
foreach ($response->getHeaders() as $name => $values) {
foreach ($values as $value) {
......
......@@ -55,7 +55,6 @@ use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
use TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator;
use TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface;
use TYPO3\CMS\Extbase\Validation\ValidatorResolver;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
use TYPO3Fluid\Fluid\View\TemplateView;
/**
......@@ -571,27 +570,11 @@ abstract class ActionController implements ControllerInterface
$body = new Stream('php://temp', 'rw');
if ($actionResult === null && $this->view instanceof ViewInterface) {
if ($this->view instanceof JsonView) {
// this is just a temporary solution until Extbase uses PSR-7 responses and users are forced to return a
// response object in their controller actions.
if (!empty($GLOBALS['TSFE']) && $GLOBALS['TSFE'] instanceof TypoScriptFrontendController) {
/** @var TypoScriptFrontendController $typoScriptFrontendController */
$typoScriptFrontendController = $GLOBALS['TSFE'];
if (empty($typoScriptFrontendController->config['config']['disableCharsetHeader'])) {
// If the charset header is *not* disabled in configuration,
// TypoScriptFrontendController will send the header later with the Content-Type which we set here.
$typoScriptFrontendController->setContentType('application/json');
} else {
// Although the charset header is disabled in configuration, we *must* send a Content-Type header here.
// Content-Type headers optionally carry charset information at the same time.
// Since we have the information about the charset, there is no reason to not include the charset information although disabled in TypoScript.
$response = $response->withHeader('Content-Type', 'application/json; charset=' . trim($typoScriptFrontendController->metaCharset));
}
} else {
$response = $response->withHeader('Content-Type', 'application/json');
}
// This is just a fallback solution until v12, when Extbase requires PSR-7 responses to be
// returned in their controller actions. The header, added below, may gets overwritten in
// the Extbase bootstrap, depending on the context (FE/BE) and TypoScript configuration.
$response = $response->withHeader('Content-Type', 'application/json; charset=utf-8');
}
$body->write($this->view->render());
} elseif (is_string($actionResult) && $actionResult !== '') {
$body->write($actionResult);
......@@ -1094,4 +1077,21 @@ abstract class ActionController implements ControllerInterface
$response->getBody()->write($html ?? $this->view->render());
return $response;
}
/**
* Returns a response object with either the given json string or the current rendered
* view as content. Mainly to be used for actions / controllers using the JsonView.
*
* @param string|null $json
* @return ResponseInterface
*/
protected function jsonResponse(string $json = null): ResponseInterface
{
$response = $this->responseFactory
->createResponse()
->withHeader('Content-Type', 'application/json; charset=utf-8');
$response->getBody()->write($json ?? $this->view->render());
return $response;
}
}
......@@ -77,10 +77,7 @@ class ContentController extends ActionController
]]);
$this->view->assign('value', $value);
$response = $this->responseFactory->createResponse()
->withAddedHeader('Content-Type', 'application/json; charset=utf-8');
$response->getBody()->write($this->view->render());
return $response;
return $this->jsonResponse();
}
/**
......
......@@ -159,12 +159,7 @@ class DownloadController extends AbstractController
'title' => $title
]);
$response = $this->responseFactory
->createResponse()
->withAddedHeader('Content-Type', 'application/json; charset=utf-8');
$response->getBody()->write($this->view->render());
return $response;
return $this->jsonResponse();
}
/**
......@@ -286,12 +281,7 @@ class DownloadController extends AbstractController
$this->addFlashMessage($e->getMessage(), '', FlashMessage::ERROR);
}
$response = $this->responseFactory
->createResponse()
->withAddedHeader('Content-Type', 'application/json; charset=utf-8');
$response->getBody()->write('');
return $response;
return $this->jsonResponse();
}
/**
......@@ -329,12 +319,7 @@ class DownloadController extends AbstractController
)
]);
$response = $this->responseFactory
->createResponse()
->withAddedHeader('Content-Type', 'application/json; charset=utf-8');
$response->getBody()->write($this->view->render());
return $response;
return $this->jsonResponse();
}
/**
......
......@@ -118,12 +118,7 @@ class UpdateFromTerController extends AbstractController
'errorMessage' => $errorMessage
]);
$response = $this->responseFactory
->createResponse()
->withAddedHeader('Content-Type', 'application/json; charset=utf-8');
$response->getBody()->write($this->view->render());
return $response;
return $this->jsonResponse();
}
/**
......
......@@ -210,12 +210,7 @@ class FormEditorController extends AbstractBackendController
'response',
]);
$response = $this->responseFactory
->createResponse()
->withAddedHeader('Content-Type', 'application/json; charset=utf-8');
$response->getBody()->write($this->view->render());
return $response;
return $this->jsonResponse();
}
/**
......
......@@ -185,12 +185,7 @@ class FormManagerController extends AbstractBackendController
'response',
]);
$response = $this->responseFactory
->createResponse()
->withAddedHeader('Content-Type', 'application/json; charset=utf-8');
$response->getBody()->write($this->view->render());
return $response;
return $this->jsonResponse();
}
/**
......@@ -261,12 +256,7 @@ class FormManagerController extends AbstractBackendController
'response',
]);
$response = $this->responseFactory
->createResponse()
->withAddedHeader('Content-Type', 'application/json; charset=utf-8');
$response->getBody()->write($this->view->render());
return $response;
return $this->jsonResponse();
}
/**
......@@ -303,12 +293,7 @@ class FormManagerController extends AbstractBackendController
'formPersistenceIdentifier'
]);
$response = $this->responseFactory
->createResponse()
->withAddedHeader('Content-Type', 'application/json; charset=utf-8');
$response->getBody()->write($this->view->render());
return $response;
return $this->jsonResponse();
}
/**
......
Markdown is supported
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