Skip to content

Commit

Permalink
[FEATURE] Introduce mixed mode for headless
Browse files Browse the repository at this point in the history
Patch introduces new way for checking if we are in headless mode, you can set not enabled, mixed mode (fluid & headless at the same time), or full headless mode.

Mixed mode depends on `Accept` header sent when requesting the site.

To configure headless mode, please use site configuration flag, for example:
`
   'headless': 0|1|2
`
Legacy flag (true|false) is respected, but please migrate to int notation

Where:
 0 (old: false) = headless disabled
 1 (old: true) = headless fully enabled
 2 = headless in mixed mode

 In mixed mode also you need to load special "Headless - Mixed mode JSON response" typoscript in order to work properly, default one overrides fluid one.
  • Loading branch information
twoldanski committed Apr 11, 2023
1 parent 6cec289 commit 413abb4
Show file tree
Hide file tree
Showing 39 changed files with 707 additions and 260 deletions.
12 changes: 4 additions & 8 deletions Classes/Hooks/FileOrFolderLinkBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@

namespace FriendsOfTYPO3\Headless\Hooks;

use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
use FriendsOfTYPO3\Headless\Utility\HeadlessMode;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\Typolink\LinkResultInterface;
use TYPO3\CMS\Frontend\Typolink\UnableToLinkException;

Expand All @@ -26,14 +27,9 @@ class FileOrFolderLinkBuilder extends \TYPO3\CMS\Frontend\Typolink\FileOrFolderL
*/
public function build(array &$linkDetails, string $linkText, string $target, array $conf): LinkResultInterface
{
$setup = ($GLOBALS['TSFE'] ?? null) instanceof TypoScriptFrontendController ? $GLOBALS['TSFE']->tmpl->setup : null;
$headlessMode = GeneralUtility::makeInstance(HeadlessMode::class)->withRequest($GLOBALS['TYPO3_REQUEST']);

if (
array_key_exists('type', $linkDetails)
&& $linkDetails['type'] === 'file'
&& isset($setup['plugin.']['tx_headless.']['staticTemplate'])
&& (bool)$setup['plugin.']['tx_headless.']['staticTemplate'] === true
) {
if ($headlessMode->isEnabled()) {
$conf['forceAbsoluteUrl'] = 1;
}

Expand Down
9 changes: 5 additions & 4 deletions Classes/Middleware/ElementBodyResponseMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace FriendsOfTYPO3\Headless\Middleware;

use FriendsOfTYPO3\Headless\Json\JsonEncoder;
use FriendsOfTYPO3\Headless\Utility\HeadlessMode;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
Expand All @@ -26,10 +27,12 @@
class ElementBodyResponseMiddleware implements MiddlewareInterface
{
private JsonEncoder $jsonEncoder;
private HeadlessMode $headlessMode;

public function __construct(JsonEncoder $jsonEncoder = null)
public function __construct(JsonEncoder $jsonEncoder = null, HeadlessMode $headlessMode)
{
$this->jsonEncoder = $jsonEncoder;
$this->headlessMode = $headlessMode;
}

public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
Expand All @@ -45,9 +48,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
return $response;
}

$siteConf = $request->getAttribute('site')->getConfiguration();

if (!($siteConf['headless'] ?? false)) {
if (!$this->headlessMode->withRequest($request)->isEnabled()) {
return $response;
}

Expand Down
45 changes: 45 additions & 0 deletions Classes/Middleware/HeadlessModeSetter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

/*
* This file is part of the "headless" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.md file that was distributed with this source code.
*/

declare(strict_types=1);

namespace FriendsOfTYPO3\Headless\Middleware;

use FriendsOfTYPO3\Headless\Utility\Headless;
use FriendsOfTYPO3\Headless\Utility\HeadlessMode;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Core\Site\Entity\Site;

class HeadlessModeSetter implements MiddlewareInterface
{
public function __construct()
{
}

public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$mode = HeadlessMode::NONE;

/**
* @var Site $site
*/
$site = $request->getAttribute('site');
if ($site) {
$mode = (int)($site->getConfiguration()['headless'] ?? HeadlessMode::NONE);
}

$request = $request->withAttribute('headless', new Headless($mode));

$GLOBALS['TYPO3_REQUEST'] = $request;
return $handler->handle($request);
}
}
10 changes: 6 additions & 4 deletions Classes/Middleware/RedirectHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use FriendsOfTYPO3\Headless\Event\RedirectUrlEvent;
use FriendsOfTYPO3\Headless\Utility\HeadlessFrontendUrlInterface;
use FriendsOfTYPO3\Headless\Utility\HeadlessMode;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
Expand All @@ -31,15 +32,18 @@ final class RedirectHandler extends \TYPO3\CMS\Redirects\Http\Middleware\Redirec
private EventDispatcherInterface $eventDispatcher;
private ServerRequestInterface $request;
private HeadlessFrontendUrlInterface $urlUtility;
private HeadlessMode $headlessMode;

public function __construct(
RedirectService $redirectService,
HeadlessFrontendUrlInterface $urlUtility,
EventDispatcher $eventDispatcher
EventDispatcher $eventDispatcher,
HeadlessMode $headlessMode
) {
parent::__construct($redirectService);
$this->urlUtility = $urlUtility;
$this->eventDispatcher = $eventDispatcher;
$this->headlessMode = $headlessMode;
}

/**
Expand All @@ -65,9 +69,7 @@ protected function buildRedirectResponse(UriInterface $uri, array $redirectRecor
return parent::buildRedirectResponse($uri, $redirectRecord);
}

$siteConf = $this->request->getAttribute('site')->getConfiguration();

if (!($siteConf['headless'] ?? false)) {
if (!$this->headlessMode->withRequest($this->request)->isEnabled()) {
return parent::buildRedirectResponse($uri, $redirectRecord);
}

Expand Down
20 changes: 8 additions & 12 deletions Classes/Middleware/ShortcutAndMountPointRedirect.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace FriendsOfTYPO3\Headless\Middleware;

use FriendsOfTYPO3\Headless\Utility\HeadlessMode;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
Expand All @@ -28,6 +29,12 @@
class ShortcutAndMountPointRedirect implements MiddlewareInterface
{
private ?TypoScriptFrontendController $controller;
private HeadlessMode $headlessMode;

public function __construct(HeadlessMode $headlessMode)
{
$this->headlessMode = $headlessMode;
}

public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
Expand Down Expand Up @@ -121,17 +128,6 @@ protected function prefixExternalPageUrl(string $redirectTo, string $sitePrefix)

private function isHeadlessEnabled(ServerRequestInterface $request): bool
{
/**
* @var Site
*/
$site = $request->getAttribute('site');

if (!($site instanceof Site)) {
return false;
}

$siteConf = $request->getAttribute('site')->getConfiguration();

return $siteConf['headless'] ?? false;
return $this->headlessMode->withRequest($request)->isEnabled();
}
}
25 changes: 7 additions & 18 deletions Classes/Middleware/UserIntMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,44 +11,33 @@

namespace FriendsOfTYPO3\Headless\Middleware;

use FriendsOfTYPO3\Headless\Utility\HeadlessMode;
use FriendsOfTYPO3\Headless\Utility\HeadlessUserInt;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Core\Http\Stream;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;

class UserIntMiddleware implements MiddlewareInterface
{
/**
* @var TypoScriptFrontendController
*/
private $tsfe;
/**
* @var HeadlessUserInt
*/
private $headlessUserInt;
private HeadlessUserInt $headlessUserInt;
private HeadlessMode $headlessMode;

public function __construct(
HeadlessUserInt $headlessUserInt = null
HeadlessUserInt $headlessUserInt = null,
HeadlessMode $headlessMode
) {
$this->headlessUserInt = $headlessUserInt ?? GeneralUtility::makeInstance(HeadlessUserInt::class);
$this->headlessMode = $headlessMode;
}

public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$response = $handler->handle($request);

$this->tsfe = $request->getAttribute('frontend.controller');
if ($this->tsfe === null && isset($GLOBALS['TSFE'])) {
$this->tsfe = $GLOBALS['TSFE'];
}

if (!isset($this->tsfe->tmpl->setup['plugin.']['tx_headless.']['staticTemplate'])
|| (bool)$this->tsfe->tmpl->setup['plugin.']['tx_headless.']['staticTemplate'] === false
) {
if (!$this->headlessMode->withRequest($request)->isEnabled()) {
return $response;
}

Expand Down
30 changes: 30 additions & 0 deletions Classes/Utility/Headless.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

/*
* This file is part of the "headless" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.md file that was distributed with this source code.
*/

declare(strict_types=1);

namespace FriendsOfTYPO3\Headless\Utility;

/**
* @codeCoverageIgnore
*/
final class Headless
{
private int $mode;

public function __construct(int $mode = HeadlessMode::NONE)
{
$this->mode = $mode;
}

public function getMode(): int
{
return $this->mode;
}
}
44 changes: 44 additions & 0 deletions Classes/Utility/HeadlessMode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

/*
* This file is part of the "headless" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.md file that was distributed with this source code.
*/

declare(strict_types=1);

namespace FriendsOfTYPO3\Headless\Utility;

use Psr\Http\Message\ServerRequestInterface;

final class HeadlessMode
{
public const NONE = 0;
public const FULL = 1;
public const MIXED = 2;

private ?ServerRequestInterface $request = null;

public function withRequest(ServerRequestInterface $request): self
{
$this->request = $request;
return $this;
}
public function isEnabled(): bool
{
if ($this->request === null) {
return false;
}

$headless = $this->request->getAttribute('headless') ?? new Headless();

if ($headless->getMode() === self::NONE) {
return false;
}

return $headless->getMode() === self::FULL ||
($headless->getMode() === self::MIXED && ($this->request->getHeader('Accept')[0] ?? '') === 'application/json');
}
}
9 changes: 7 additions & 2 deletions Classes/Utility/UrlUtility.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,19 @@ class UrlUtility implements LoggerAwareInterface, HeadlessFrontendUrlInterface
private SiteFinder $siteFinder;
private array $conf = [];
private array $variants = [];
private HeadlessMode $headlessMode;

public function __construct(
?Features $features = null,
?Resolver $resolver = null,
?SiteFinder $siteFinder = null,
?ServerRequestInterface $serverRequest = null
?ServerRequestInterface $serverRequest = null,
?HeadlessMode $headlessMode = null
) {
$this->features = $features ?? GeneralUtility::makeInstance(Features::class);
$this->resolver = $resolver ?? GeneralUtility::makeInstance(Resolver::class, 'site', []);
$this->siteFinder = $siteFinder ?? GeneralUtility::makeInstance(SiteFinder::class);
$this->headlessMode = $headlessMode ?? GeneralUtility::makeInstance(HeadlessMode::class);
$request = $serverRequest ?? ($GLOBALS['TYPO3_REQUEST'] ?? null);

if ($request instanceof ServerRequestInterface) {
Expand All @@ -73,7 +76,7 @@ public function withLanguage(SiteLanguage $language): HeadlessFrontendUrlInterfa

public function getFrontendUrlForPage(string $url, int $pageUid, string $returnField = 'frontendBase'): string
{
if (!$this->features->isFeatureEnabled('headless.frontendUrls')) {
if (!$this->headlessMode->isEnabled()) {
return $url;
}

Expand Down Expand Up @@ -253,6 +256,8 @@ private function extractConfigurationFromRequest(ServerRequestInterface $request
$object->handleLanguageConfiguration($language, $object);
}

$object->headlessMode = $object->headlessMode->withRequest($request);

return $object;
}
}
10 changes: 4 additions & 6 deletions Classes/XClass/Controller/FormFrontendController.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use FriendsOfTYPO3\Headless\Form\Decorator\DefinitionDecoratorInterface;
use FriendsOfTYPO3\Headless\Form\Decorator\FormDefinitionDecorator;
use FriendsOfTYPO3\Headless\Form\Translator;
use FriendsOfTYPO3\Headless\Utility\HeadlessMode;
use FriendsOfTYPO3\Headless\XClass\FormRuntime;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Utility\ArrayUtility;
Expand All @@ -23,7 +24,6 @@
use TYPO3\CMS\Extbase\Security\Cryptography\HashService;
use TYPO3\CMS\Form\Domain\Factory\ArrayFormFactory;
use TYPO3\CMS\Form\Domain\Model\FormDefinition;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;

use function array_merge;
use function array_pop;
Expand Down Expand Up @@ -62,11 +62,9 @@ public function __construct()
*/
public function renderAction(): ResponseInterface
{
// check if headless not loaded & call original method in case of fluid configuration
$typoScriptSetup = $GLOBALS['TSFE'] instanceof TypoScriptFrontendController ? $GLOBALS['TSFE']->tmpl->setup : [];
if (!isset($typoScriptSetup['plugin.']['tx_headless.']['staticTemplate'])
|| (bool)$typoScriptSetup['plugin.']['tx_headless.']['staticTemplate'] === false
) {
$headlessMode = GeneralUtility::makeInstance(HeadlessMode::class);

if (!$headlessMode->withRequest($GLOBALS['TYPO3_REQUEST'])->isEnabled()) {
return parent::renderAction();
}

Expand Down
Loading

0 comments on commit 413abb4

Please sign in to comment.