Skip to content

Commit

Permalink
Merge branch 'master' into refactor-autogenerate-feature
Browse files Browse the repository at this point in the history
  • Loading branch information
kitzberger authored Dec 6, 2023
2 parents ef9c173 + 2a7a694 commit bd702b3
Show file tree
Hide file tree
Showing 14 changed files with 211 additions and 68 deletions.
2 changes: 0 additions & 2 deletions Classes/ContentObject/JsonContentObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
use Psr\Log\LoggerAwareTrait;
use RecursiveArrayIterator;
use RecursiveIteratorIterator;
use TYPO3\CMS\Core\Configuration\Features;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\AbstractContentObject;
use TYPO3\CMS\Frontend\ContentObject\ContentDataProcessor;
Expand Down Expand Up @@ -177,7 +176,6 @@ protected function processFieldWithDataProcessing(array $dataProcessing): mixed
);

$dataProcessingData = null;
$features = GeneralUtility::makeInstance(Features::class);

foreach ($this->recursiveFind($dataProcessing, 'as') as $value) {
if (isset($data[$value])) {
Expand Down
6 changes: 4 additions & 2 deletions Classes/Event/Listener/AfterLinkIsGeneratedListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,18 @@ public function __invoke(AfterLinkIsGeneratedEvent $event): void
$pageId = (int)($this->linkService->resolve($event->getContentObjectRenderer()->parameters['href'] ?? '')['pageuid'] ?? 0);
}

$urlUtility = $this->urlUtility->withRequest($event->getContentObjectRenderer()->getRequest());

if ($pageId) {
$href = $this->urlUtility->getFrontendUrlForPage(
$href = $urlUtility->getFrontendUrlForPage(
$event->getLinkResult()->getUrl(),
(int)$pageId
);
} else {
$site = $event->getContentObjectRenderer()->getRequest()->getAttribute('site');

if (!$site instanceof NullSite) {
$href = $this->urlUtility->getFrontendUrlWithSite($event->getLinkResult()->getUrl(), $site);
$href = $urlUtility->getFrontendUrlWithSite($event->getLinkResult()->getUrl(), $site);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ public function __invoke(AfterPagePreviewUriGeneratedEvent $event): void
}

$request = $GLOBALS['TYPO3_REQUEST'];
$request = $request->withAttribute('language', $site->getLanguageById($event->getLanguageId()));
$request = $request->withAttribute('headless', new Headless($mode));

$this->urlUtility = $this->urlUtility->withRequest($request);
$event->setPreviewUri(new Uri($this->urlUtility->getFrontendUrlWithSite($event->getPreviewUri()->__toString(), $site)));
$urlUtility = $this->urlUtility->withRequest($request);
$event->setPreviewUri(new Uri($urlUtility->getFrontendUrlWithSite($event->getPreviewUri()->__toString(), $site)));
} catch (SiteNotFoundException) {
}
}
Expand Down
6 changes: 6 additions & 0 deletions Classes/Middleware/SiteBaseRedirectResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Core\Http\JsonResponse;
use TYPO3\CMS\Core\Http\RedirectResponse;
use TYPO3\CMS\Core\Site\Entity\NullSite;
use TYPO3\CMS\Core\Utility\GeneralUtility;

class SiteBaseRedirectResolver extends \TYPO3\CMS\Frontend\Middleware\SiteBaseRedirectResolver
Expand All @@ -26,6 +27,11 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
$response = parent::process($request, $handler);

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

if ($site instanceof NullSite) {
return $response;
}

$siteConf = $site->getConfiguration();

if (!($siteConf['headless'] ?? false)) {
Expand Down
6 changes: 4 additions & 2 deletions Classes/Utility/UrlUtility.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,17 @@ public function withLanguage(SiteLanguage $language): HeadlessFrontendUrlInterfa

public function getFrontendUrlWithSite($url, SiteInterface $site, string $returnField = 'frontendBase'): string
{
$this->handleSiteConfiguration($site, $this);

if (!$this->headlessMode->isEnabled()) {
return $url;
}

try {
$base = $site->getBase()->getHost();
$port = $site->getBase()->getPort();
$configuration = $site->getConfiguration();
$frontendBaseUrl = $this->resolveWithVariants(
$configuration[$returnField] ?? '',
$this->conf[$returnField] ?? '',
$this->variants,
$returnField
);
Expand Down Expand Up @@ -262,6 +263,7 @@ private function handleSiteConfiguration(Site $site, UrlUtility $object): self
private function extractConfigurationFromRequest(ServerRequestInterface $request, HeadlessFrontendUrlInterface $object): HeadlessFrontendUrlInterface
{
$site = $request->getAttribute('site');

if ($site instanceof Site) {
$object->handleSiteConfiguration($site, $object);
}
Expand Down
11 changes: 7 additions & 4 deletions Configuration/Services.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,16 @@
'event.listener',
['identifier' => 'headless/AfterLinkIsGenerated']
);
$services->set(AfterCacheableContentIsGeneratedListener::class)->tag(
'event.listener',
['identifier' => 'headless/AfterCacheableContentIsGenerated']
);

$features = GeneralUtility::makeInstance(Features::class);

if ($features->isFeatureEnabled('headless.pageTitleProviders')) {
$services->set(AfterCacheableContentIsGeneratedListener::class)->tag(
'event.listener',
['identifier' => 'headless/AfterCacheableContentIsGenerated']
);
}

if ($feloginInstalled) {
$services->set(LoginConfirmedEventListener::class)->tag(
'event.listener',
Expand Down
9 changes: 9 additions & 0 deletions Configuration/TCA/Overrides/sys_template.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,13 @@
'Configuration/TypoScript',
'Headless'
);

/**
* Mixed-Mode TypoScript for Headless
*/
ExtensionManagementUtility::addStaticFile(
'headless',
'Configuration/TypoScript/Mixed',
'Headless - Mixed mode JSON response'
);
});
2 changes: 1 addition & 1 deletion Configuration/TypoScript/setup.typoscript
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ plugin.tx_headless {
@import "EXT:headless/Configuration/TypoScript/ContentElement/*.typoscript"
## Include configuration
@import "EXT:headless/Configuration/TypoScript/Configuration/*.typoscript"
@import "EXT:headless/Configuration/TypoScript/Configuration/LoggedUser/FeLogin.typoscript"
@import "EXT:headless/Configuration/TypoScript/LoggedUser/FeLogin.typoscript"
24 changes: 24 additions & 0 deletions Documentation/Configuration/Index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,15 @@ To enable headless support for `EXT:workspaces` please also add to you site(s) c
headless: true
**headless.pageTitleProviders**

Enable support for PageTitle API

.. code-block:: php
$GLOBALS['TYPO3_CONF_VARS']['SYS']['features']['headless.pageTitleProviders'] = true;
**Availability of feature toggles by version**

.. t3-field-list-table::
Expand All @@ -132,42 +141,57 @@ To enable headless support for `EXT:workspaces` please also add to you site(s) c
- :Header1: Flag
:Header2: 2.x
:Header3: 3.x
:Header3: 4.x

- :Header1: FrontendBaseUrlInPagePreview
:Header2: available
:Header3: removed
:Header4: removed

- :Header1: headless.frontendUrls
:Header2: >= 2.5
:Header3: available
:Header4: removed

- :Header1: headless.storageProxy
:Header2: >= 2.4
:Header3: available
:Header4: available

- :Header1: headless.redirectMiddlewares
:Header2: >= 2.5
:Header3: available
:Header4: available

- :Header1: headless.nextMajor
:Header2: >= 2.2
:Header3: currently not used
:Header4: currently not used

- :Header1: headless.elementBodyResponse
:Header2: >= 2.6
:Header3: available
:Header4: available

- :Header1: headless.simplifiedLinkTarget
:Header2: >= 2.6
:Header3: removed
:Header4: not available

- :Header1: headless.jsonViewModule
:Header2: not available
:Header3: >= 3.0
:Header4: >= 3.0

- :Header1: headless.workspaces
:Header2: not available
:Header3: >= 3.1
:Header4: >= 3.1

- :Header1: headless.pageTitleProviders
:Header2: not available
:Header3: not available
:Header4: >= 4.2.3

.. _configuration-ext-form:

Expand Down
89 changes: 45 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,54 @@ If you want to take a look at working demo including frontend, backend and demo
[https://github.com/TYPO3-Initiatives/pwa-demo](https://github.com/TYPO3-Headless/pwa-demo)

## Installation
Install extension using composer\
``composer require friendsoftypo3/headless``
Install extension using composer

Then, you should include extension typoscript template, and you are ready to go. Also, please remember to don't use fluid styled content on the same page tree together with ext:headless.
``composer require friendsoftypo3/headless``

## Documentation
[Extension documentation](https://docs.typo3.org/p/friendsoftypo3/headless/main/en-us/Index.html)

## How to start with TYPO3 Headless video tutorial

## Contributing
![Alt](https://repobeats.axiom.co/api/embed/197db91cad9195bb15a06c91fda5a215bff26cba.svg)
Whether you are a developer, content manager, or a tech enthusiast, this tutorial is tailored to provide a comprehensive introduction to TYPO3 Headless, helping you to get started on your journey with confidence.

[![video still](https://i.ytimg.com/vi/7MOwugAyHkY/hq720.jpg)](https://www.youtube.com/watch?v=7MOwugAyHkY)

## Configuration

Since versions: `4.2` | `3.5` Flag `headless` is required to configure in site configuration!

This flag instructs how `EXT:headless` should behave in multisite instance.

For each site you can set in which mode site is operated (standard aka HTML response, headless, or mixed mode).

You can set `headless` flag manually in yaml file or via site configuration in the backend:

```yaml
'headless': 0|1|2
```
### Possible values:
While the legacy flag (`true`|`false`) is still recognized, transitioning to the integer notation is recommended.
- **0** (formerly: `false`) = headless mode is deactivated for the site within the TYPO3 instance. **Default value!**
- **1** (formerly: `true`) = headless mode is fully activated for the site within the TYPO3 instance.
- **2** = mixed mode headless is activated (both fluid & json API are accessible within a single site in the TYPO3 instance).

### Configuration steps
For a chosen site in TYPO3, follow these steps:

#### To enable Headless Mode:
- In the typoscript template for the site, load the "Headless" setup file.
- Set `headless` flag to a value of `1` in the site configuration file or configure the flag via editor in the Site's management backend.

#### To enable Mixed Mode:
- In the typoscript template for the site, load the "Headless - Mixed mode JSON response" setup file instead of the default headless one.
- Set `headless` flag to a value of `2` in the site configuration file or configure the flag via editor in the Site's management backend.

The mixed mode flag (value of `2`) instructs the EXT:headless extension to additionally check for the `Accept` header with a value of `application/json` when processing requests to the particular site in the TYPO3 instance.

- In cases where a request lacks the `Accept` header or `Accept` has a different value than `application/json`, TYPO3 will respond with HTML content (standard TYPO3's response).
- In cases where a request's header `Accept` matches the value of `application/json`, TYPO3 will respond with a JSON response.

## JSON Content Object
In headless extension we implemented new JSON Content Object, which allows you to specify what fields you want to output, and how they will look. First, let's take a look at simple example
Expand Down Expand Up @@ -166,18 +203,10 @@ Output
## Customizing
You can override every field in output using typoscript. This extension allows you to use standard typoscript objects such as TEXT, COA, CASE.
## Page response
In headless v3.0 we introduce a new, smaller, faster and more flat page response.
If you want to keep compatibility with your frontend application, you can load a deprecated typoscript template for version 2.x and keep the old structure of the response running.

#### Example page response ⬇️
since version 3.x & under active maintenance
## Example page response ⬇️
![image](https://user-images.githubusercontent.com/15106746/136414744-88d54d44-2f3c-4d7d-9911-832ceefcfe16.png)
#### Old response (version 2.x) ⬇️
![image](https://user-images.githubusercontent.com/15106746/136414370-a4bec856-5a95-4965-b60b-5a37be5ce5c9.png)

## DataProcessing
You can use Data Processors just like in `FLUIDTEMPLATE` Content Object, e.g.
Expand Down Expand Up @@ -219,36 +248,8 @@ Used for processing flexforms.
### RootSitesProcessor
Render your all headless sites configuration for your frontend application.
## Configuration
### Available Settings:
- **Not Enabled**: Headless mode is deactivated.
- **Mixed Mode**: Fluid and headless operate concurrently.
- **Fully Headless Mode**: Headless mode is fully activated.

To set up headless mode, utilize the site configuration flag as shown below:

```yaml
'headless': 0|1|2
```
While the legacy flag (true|false) is still recognized, transitioning to the integer notation is recommended.
### Options:
- **0** (formerly: false) = headless mode is deactivated for the site within the TYPO3 instance.
- **1** (formerly: true) = headless mode is fully activated for the site within the TYPO3 instance.
- **2** = mixed mode headless is activated (both fluid & json API are accessible within a single site in the TYPO3 instance).
Options **0** (formerly: false) or **1** (formerly: true) inform the extension to either fully disable or enable headless mode for a particular site.
### To Enable Mixed Mode:
For a chosen site in TYPO3, follow these steps:
- In the typoscript template for the site, load the "Headless - Mixed mode JSON response" setup file instead of the default headless one.
- Set `headless` flag to a value of `2` in the site configuration file or configure the flag via editor in the Site's management backend.

The mixed mode flag (value of `2`) instructs the EXT:headless extension to additionally check for the `Accept` header with a value of `application/json` when processing requests to the particular site in the TYPO3 instance.

- In cases where a request lacks the `Accept` header or `Accept` has a different value than `application/json`, TYPO3 will respond with HTML content (standard TYPO3's response).
- In cases where a request's header `Accept` matches the value of `application/json`, TYPO3 will respond with a JSON response.
## Contributing
![Alt](https://repobeats.axiom.co/api/embed/197db91cad9195bb15a06c91fda5a215bff26cba.svg)
## Development
Expand Down
25 changes: 16 additions & 9 deletions Tests/Unit/Event/Listener/AfterLinkIsGeneratedListenerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,14 @@ public function test__invokeModifingFromPageUid()
$urlUtility->getFrontendUrlForPage(Argument::is('/'), Argument::is(2))->willReturn('https://frontend-domain.tld/page');
$urlUtility->getFrontendUrlWithSite(Argument::is('/'), Argument::any())->willReturn('https://frontend-domain.tld/page');

$listener = new AfterLinkIsGeneratedListener($urlUtility->reveal(), $this->prophesize(LinkService::class)->reveal());

$site = new Site('test', 1, []);
$cObj = $this->prophesize(ContentObjectRenderer::class);
$cObj->getRequest()->willReturn((new ServerRequest())->withAttribute('site', $site));
$request = (new ServerRequest())->withAttribute('site', $site);
$cObj->getRequest()->willReturn($request);

$urlUtility->withRequest($request)->willReturn($urlUtility->reveal());

$listener = new AfterLinkIsGeneratedListener($urlUtility->reveal(), $this->prophesize(LinkService::class)->reveal());

$linkResult = new LinkResult('page', '/');
$linkResult = $linkResult->withLinkText('t3://page?uid=2');
Expand All @@ -101,11 +104,13 @@ public function test__invokeModifingWithoutPageId()
$urlUtility = $this->prophesize(UrlUtility::class);
$urlUtility->getFrontendUrlWithSite(Argument::is('/'), Argument::is($site))->willReturn('https://front.typo3.tld');

$listener = new AfterLinkIsGeneratedListener($urlUtility->reveal(), $this->prophesize(LinkService::class)->reveal());

$cObj = $this->prophesize(ContentObjectRenderer::class);
$cObj->getRequest()->willReturn((new ServerRequest())->withAttribute('site', $site));
$request = (new ServerRequest())->withAttribute('site', $site);
$cObj->getRequest()->willReturn($request);

$urlUtility->withRequest($request)->willReturn($urlUtility->reveal());

$listener = new AfterLinkIsGeneratedListener($urlUtility->reveal(), $this->prophesize(LinkService::class)->reveal());
$linkResult = new LinkResult('page', '/');
$linkResult = $linkResult->withLinkText('|');

Expand All @@ -128,11 +133,13 @@ public function test__invokeModifingExternalSite()
$linkService = $this->prophesize(LinkService::class);
$linkService->resolve(Argument::any())->willReturn(['pageuid' => 5]);

$listener = new AfterLinkIsGeneratedListener($urlUtility->reveal(), $linkService->reveal());

$cObj = $this->prophesize(ContentObjectRenderer::class);
$cObj->getRequest()->willReturn((new ServerRequest())->withAttribute('site', $site));
$request = (new ServerRequest())->withAttribute('site', $site);
$cObj->getRequest()->willReturn($request);

$urlUtility->withRequest($request)->willReturn($urlUtility->reveal());

$listener = new AfterLinkIsGeneratedListener($urlUtility->reveal(), $linkService->reveal());
$linkResult = new LinkResult('page', '/');
$linkResult = $linkResult->withLinkConfiguration(['parameter.' => ['data' => 'parameters:href']]);
$linkResult = $linkResult->withLinkText('|');
Expand Down
Loading

0 comments on commit bd702b3

Please sign in to comment.