mirror of
https://github.com/werkraum-media/events.git
synced 2024-12-22 07:26:10 +01:00
Extend TYPO3 page cache timout calculation based on rendered dates (#9)
Relates: #10349
This commit is contained in:
parent
afd8c59c9e
commit
adc8b30e6d
15 changed files with 470 additions and 10 deletions
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
|
@ -47,7 +47,7 @@ jobs:
|
|||
tools: composer:v2
|
||||
|
||||
- name: Install xmllint
|
||||
run: sudo apt-get install libxml2-utils
|
||||
run: sudo apt update && sudo apt install libxml2-utils
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --prefer-dist --no-progress --no-suggest
|
||||
|
|
92
Classes/Caching/PageCacheTimeout.php
Normal file
92
Classes/Caching/PageCacheTimeout.php
Normal file
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2023 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
namespace Wrm\Events\Caching;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use TYPO3\CMS\Core\SingletonInterface;
|
||||
use Wrm\Events\Events\Controller\DateListVariables;
|
||||
|
||||
/**
|
||||
* Teaches TYPO3 to set proper timeout for page cache.
|
||||
*
|
||||
* Takes timings of rendered dates into account.
|
||||
* Reduced timeout of page cache in case events (dates) might change before already calculated timeout.
|
||||
*/
|
||||
class PageCacheTimeout implements SingletonInterface
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $earliestTimeout = 0;
|
||||
|
||||
public function calculateCacheTimout(
|
||||
array $parameters
|
||||
): int {
|
||||
$timeout = $parameters['cacheTimeout'];
|
||||
|
||||
if ($this->earliestTimeout <= 0) {
|
||||
return $timeout;
|
||||
}
|
||||
|
||||
return min($timeout, $this->earliestTimeout);
|
||||
}
|
||||
|
||||
public function trackDates(DateListVariables $event): void
|
||||
{
|
||||
if ($event->getDemand()->shouldShowFromMidnight()) {
|
||||
$this->updateTimeout((int) (new DateTimeImmutable('tomorrow midnight'))->format('U'));
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($event->getDates() as $date) {
|
||||
$endDate = $date->getEnd();
|
||||
if (!$endDate instanceof DateTime) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->updateTimeout((int)DateTimeImmutable::createFromMutable($endDate)->format('U'));
|
||||
}
|
||||
}
|
||||
|
||||
private function updateTimeout(int $timestamp): void
|
||||
{
|
||||
$newTimeout = $timestamp - time();
|
||||
if ($newTimeout <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->earliestTimeout === 0) {
|
||||
$this->earliestTimeout = $newTimeout;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->earliestTimeout = min($this->earliestTimeout, $newTimeout);
|
||||
}
|
||||
|
||||
public static function register(): void
|
||||
{
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['get_cache_timeout']['events'] = self::class . '->calculateCacheTimout';
|
||||
}
|
||||
}
|
|
@ -39,3 +39,9 @@ services:
|
|||
|
||||
Wrm\Events\Updates\MigrateOldLocations:
|
||||
public: true
|
||||
|
||||
Wrm\Events\Caching\PageCacheTimeout:
|
||||
tags:
|
||||
- name: event.listener
|
||||
event: Wrm\Events\Events\Controller\DateListVariables
|
||||
method: 'trackDates'
|
||||
|
|
33
Documentation/Changelog/3.4.0.rst
Normal file
33
Documentation/Changelog/3.4.0.rst
Normal file
|
@ -0,0 +1,33 @@
|
|||
3.4.0
|
||||
=====
|
||||
|
||||
Breaking
|
||||
--------
|
||||
|
||||
Nothing
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
* Adjust TYPO3 page cache timeout based on rendered dates.
|
||||
The end time of each rendered date will be used.
|
||||
The lowest end date will be used to calculate the maximum time to life for the page cache.
|
||||
This is compared to the already calculated time to life.
|
||||
The lower value is then used by TYPO3.
|
||||
|
||||
That allows visitors to always see the next dates.
|
||||
|
||||
Fixes
|
||||
-----
|
||||
|
||||
Nothing
|
||||
|
||||
Tasks
|
||||
-----
|
||||
|
||||
Nothing
|
||||
|
||||
Deprecation
|
||||
-----------
|
||||
|
||||
Nothing
|
207
Tests/Functional/Frontend/CacheTest.php
Normal file
207
Tests/Functional/Frontend/CacheTest.php
Normal file
|
@ -0,0 +1,207 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2023 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
namespace Wrm\Events\Tests\Functional\Frontend;
|
||||
|
||||
use Codappix\Typo3PhpDatasets\PhpDataSet;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeZone;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
|
||||
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
|
||||
|
||||
/**
|
||||
* @covers \Wrm\Events\Caching\PageCacheTimeout
|
||||
*/
|
||||
class CacheTest extends FunctionalTestCase
|
||||
{
|
||||
protected $testExtensionsToLoad = [
|
||||
'typo3conf/ext/events',
|
||||
'typo3conf/ext/events/Tests/Functional/Frontend/Fixtures/Extensions/example',
|
||||
];
|
||||
|
||||
protected $coreExtensionsToLoad = [
|
||||
'fluid_styled_content',
|
||||
];
|
||||
|
||||
protected $pathsToProvideInTestInstance = [
|
||||
'typo3conf/ext/events/Tests/Functional/Frontend/Fixtures/Sites/' => 'typo3conf/sites',
|
||||
];
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->importCSVDataSet(__DIR__ . '/Fixtures/Database/SiteStructure.csv');
|
||||
(new PhpDataSet())->import(['tt_content' => [[
|
||||
'uid' => '1',
|
||||
'pid' => '1',
|
||||
'CType' => 'list',
|
||||
'list_type' => 'events_datelisttest',
|
||||
'header' => 'All Dates',
|
||||
]]]);
|
||||
$this->setUpFrontendRootPage(1, $this->getTypoScriptFiles());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function setupReturnsSystemDefaults(): void
|
||||
{
|
||||
$request = new InternalRequest();
|
||||
$request = $request->withPageId(1);
|
||||
$response = $this->executeFrontendRequest($request);
|
||||
|
||||
self::assertSame(200, $response->getStatusCode());
|
||||
self::assertSame('max-age=86400', $response->getHeaderLine('Cache-Control'));
|
||||
self::assertSame('public', $response->getHeaderLine('Pragma'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function setupReturnsDefaultsIfEventsEndLater(): void
|
||||
{
|
||||
(new PhpDataSet())->import([
|
||||
'tx_events_domain_model_event' => [
|
||||
[
|
||||
'uid' => '1',
|
||||
'title' => 'Test Event 1',
|
||||
],
|
||||
],
|
||||
'tx_events_domain_model_date' => [
|
||||
[
|
||||
'event' => '1',
|
||||
'start' => time(),
|
||||
'end' => time() + 86400 + 50,
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$request = new InternalRequest();
|
||||
$request = $request->withPageId(1);
|
||||
$response = $this->executeFrontendRequest($request);
|
||||
|
||||
self::assertSame(200, $response->getStatusCode());
|
||||
self::assertSame('max-age=86400', $response->getHeaderLine('Cache-Control'));
|
||||
self::assertSame('public', $response->getHeaderLine('Pragma'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function setupReturnsEarlierIfEventsChangeBeforeSystemDefault(): void
|
||||
{
|
||||
(new PhpDataSet())->import([
|
||||
'tx_events_domain_model_event' => [
|
||||
[
|
||||
'uid' => '1',
|
||||
'pid' => '2',
|
||||
'title' => 'Test Event 1',
|
||||
],
|
||||
],
|
||||
'tx_events_domain_model_date' => [
|
||||
[
|
||||
'pid' => '2',
|
||||
'event' => '1',
|
||||
'start' => time(),
|
||||
'end' => time() + 50,
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$request = new InternalRequest();
|
||||
$request = $request->withPageId(1);
|
||||
$response = $this->executeFrontendRequest($request);
|
||||
|
||||
self::assertSame(200, $response->getStatusCode());
|
||||
self::assertMaxAge(50, $response);
|
||||
self::assertSame('public', $response->getHeaderLine('Pragma'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function setupReturnsMidnightIfConfigured(): void
|
||||
{
|
||||
(new PhpDataSet())->import([
|
||||
'tx_events_domain_model_event' => [
|
||||
[
|
||||
'uid' => '1',
|
||||
'pid' => '2',
|
||||
'title' => 'Test Event 1',
|
||||
],
|
||||
],
|
||||
'tx_events_domain_model_date' => [
|
||||
[
|
||||
'pid' => '2',
|
||||
'event' => '1',
|
||||
'start' => time(),
|
||||
'end' => time() + 50,
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->setUpFrontendRootPage(1, array_merge_recursive($this->getTypoScriptFiles(), [
|
||||
'setup' => [
|
||||
'EXT:events/Tests/Functional/Frontend/Fixtures/TypoScript/CachingMidnight.typoscript'
|
||||
],
|
||||
]));
|
||||
|
||||
$request = new InternalRequest();
|
||||
$request = $request->withPageId(1);
|
||||
$response = $this->executeFrontendRequest($request);
|
||||
|
||||
self::assertSame(200, $response->getStatusCode());
|
||||
$midnight = (int) (new DateTimeImmutable('tomorrow midnight', new DateTimeZone('UTC')))->format('U');
|
||||
$age = $midnight - time();
|
||||
self::assertMaxAge($age, $response);
|
||||
self::assertSame('public', $response->getHeaderLine('Pragma'));
|
||||
}
|
||||
|
||||
private static function assertMaxAge(int $age, ResponseInterface $response): void
|
||||
{
|
||||
[$prefix, $value] = explode('=', $response->getHeaderLine('Cache-Control'));
|
||||
|
||||
self::assertSame('max-age', $prefix);
|
||||
|
||||
// We might be one sec off due to how fast code is executed, so add a small offset
|
||||
$value = (int)$value;
|
||||
self::assertLessThanOrEqual($age + 1, $value, 'Max age of cached response is higher than expected.');
|
||||
self::assertGreaterThanOrEqual($age - 1, $value, 'Max age of cached response is less than expected.');
|
||||
}
|
||||
|
||||
private function getTypoScriptFiles(): array
|
||||
{
|
||||
return [
|
||||
'constants' => [
|
||||
'EXT:events/Configuration/TypoScript/constants.typoscript',
|
||||
],
|
||||
'setup' => [
|
||||
'EXT:fluid_styled_content/Configuration/TypoScript/setup.typoscript',
|
||||
'EXT:events/Configuration/TypoScript/setup.typoscript',
|
||||
'EXT:events/Tests/Functional/Frontend/Fixtures/TypoScript/Rendering.typoscript'
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "wrm/events-example",
|
||||
"type": "typo3-cms-extension",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"require": {
|
||||
"wrm/events": "*"
|
||||
},
|
||||
"extra": {
|
||||
"typo3/cms": {
|
||||
"extension-key": "example"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
$EM_CONF['example'] = [
|
||||
'title' => 'Events Test',
|
||||
'category' => 'plugin',
|
||||
'description' => 'Example for tests',
|
||||
'state' => 'alpha',
|
||||
'version' => '1.0.0',
|
||||
'constraints' => [
|
||||
'depends' => [
|
||||
'events' => '',
|
||||
'typo3' => '',
|
||||
],
|
||||
'conflicts' => [],
|
||||
'suggests' => [],
|
||||
],
|
||||
];
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin(
|
||||
'Events',
|
||||
'DateListTest',
|
||||
[\Wrm\Events\Controller\DateController::class => 'list']
|
||||
);
|
|
@ -0,0 +1,6 @@
|
|||
plugin.tx_events {
|
||||
settings {
|
||||
start >
|
||||
useMidnight = 1
|
||||
}
|
||||
}
|
|
@ -1,3 +1,9 @@
|
|||
config {
|
||||
cache_period = 86400
|
||||
no_cache = 0
|
||||
sendCacheHeaders = 1
|
||||
}
|
||||
|
||||
page = PAGE
|
||||
page {
|
||||
10 < styles.content.get
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"codappix/typo3-php-datasets": "^1.2",
|
||||
"guzzlehttp/guzzle": "^6.3 || ^7.3",
|
||||
"jangregor/phpstan-prophecy": "1.0.0",
|
||||
"phpspec/prophecy-phpunit": "^1.0 || ^2.0",
|
||||
|
|
|
@ -9,7 +9,7 @@ $EM_CONF['events'] = [
|
|||
'state' => 'alpha',
|
||||
'createDirs' => '',
|
||||
'clearCacheOnLoad' => 0,
|
||||
'version' => '3.3.0',
|
||||
'version' => '3.4.0',
|
||||
'constraints' => [
|
||||
'depends' => [
|
||||
'typo3' => '10.4.00-11.5.99',
|
||||
|
|
|
@ -51,5 +51,6 @@ call_user_func(function () {
|
|||
['source' => 'EXT:events/Resources/Public/Icons/Folder.svg']
|
||||
);
|
||||
|
||||
\Wrm\Events\Caching\PageCacheTimeout::register();
|
||||
\Wrm\Events\Updates\MigrateOldLocations::register();
|
||||
});
|
||||
|
|
|
@ -75,3 +75,8 @@ parameters:
|
|||
count: 1
|
||||
path: Classes/Updates/MigrateOldLocations.php
|
||||
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getEnd\\(\\) on mixed\\.$#"
|
||||
count: 1
|
||||
path: Classes/Caching/PageCacheTimeout.php
|
||||
|
|
82
shell.nix
82
shell.nix
|
@ -1,7 +1,18 @@
|
|||
{ pkgs ? import <nixpkgs> { } }:
|
||||
{
|
||||
pkgs ? import <nixpkgs> { }
|
||||
,phps ? import <phps>
|
||||
}:
|
||||
|
||||
let
|
||||
php = pkgs.php82;
|
||||
php = phps.packages.x86_64-linux.php82.buildEnv {
|
||||
extensions = { enabled, all }: enabled ++ (with all; [
|
||||
xdebug
|
||||
]);
|
||||
extraConfig = ''
|
||||
xdebug.mode = debug
|
||||
memory_limit = 4G
|
||||
'';
|
||||
};
|
||||
inherit(pkgs.php82Packages) composer;
|
||||
|
||||
projectInstall = pkgs.writeShellApplication {
|
||||
|
@ -12,9 +23,10 @@ let
|
|||
];
|
||||
text = ''
|
||||
rm -rf vendor/ composer.lock .Build/
|
||||
composer install --prefer-dist --no-progress --working-dir="$PROJECT_ROOT"
|
||||
composer update --prefer-dist --no-progress --working-dir="$PROJECT_ROOT"
|
||||
'';
|
||||
};
|
||||
|
||||
projectValidateComposer = pkgs.writeShellApplication {
|
||||
name = "project-validate-composer";
|
||||
runtimeInputs = [
|
||||
|
@ -25,6 +37,7 @@ let
|
|||
composer validate
|
||||
'';
|
||||
};
|
||||
|
||||
projectValidateXml = pkgs.writeShellApplication {
|
||||
name = "project-validate-xml";
|
||||
runtimeInputs = [
|
||||
|
@ -40,25 +53,78 @@ let
|
|||
xmllint --schema xliff-core-1.2-strict.xsd --noout $(find Resources -name '*.xlf')
|
||||
'';
|
||||
};
|
||||
projectCodingGuideline = pkgs.writeShellApplication {
|
||||
name = "project-coding-guideline";
|
||||
|
||||
projectPhpstan = pkgs.writeShellApplication {
|
||||
name = "project-phpstan";
|
||||
|
||||
runtimeInputs = [
|
||||
php
|
||||
projectInstall
|
||||
];
|
||||
|
||||
text = ''
|
||||
./vendor/bin/phpstan
|
||||
'';
|
||||
};
|
||||
|
||||
projectCgl = pkgs.writeShellApplication {
|
||||
name = "project-cgl";
|
||||
|
||||
runtimeInputs = [
|
||||
php
|
||||
];
|
||||
|
||||
text = ''
|
||||
project-install
|
||||
./vendor/bin/ecs check --no-progress-bar --clear-cache
|
||||
'';
|
||||
};
|
||||
|
||||
projectCglFix = pkgs.writeShellApplication {
|
||||
name = "project-cgl-fix";
|
||||
|
||||
runtimeInputs = [
|
||||
php
|
||||
];
|
||||
|
||||
text = ''
|
||||
./vendor/bin/ecs check --fix --no-progress-bar --clear-cache
|
||||
'';
|
||||
};
|
||||
|
||||
projectTestsUnit = pkgs.writeShellApplication {
|
||||
name = "project-tests-unit";
|
||||
|
||||
runtimeInputs = [
|
||||
php
|
||||
];
|
||||
|
||||
text = ''
|
||||
./vendor/bin/phpunit --testsuite unit --color --testdox
|
||||
'';
|
||||
};
|
||||
|
||||
projectTestsFunctional = pkgs.writeShellApplication {
|
||||
name = "project-tests-functional";
|
||||
|
||||
runtimeInputs = [
|
||||
php
|
||||
];
|
||||
|
||||
text = ''
|
||||
./vendor/bin/phpunit --testsuite functional --color --testdox
|
||||
'';
|
||||
};
|
||||
|
||||
in pkgs.mkShell {
|
||||
name = "TYPO3 Extension Watchlist";
|
||||
buildInputs = [
|
||||
projectInstall
|
||||
projectValidateComposer
|
||||
projectValidateXml
|
||||
projectCodingGuideline
|
||||
projectPhpstan
|
||||
projectCgl
|
||||
projectCglFix
|
||||
projectTestsUnit
|
||||
projectTestsFunctional
|
||||
php
|
||||
composer
|
||||
];
|
||||
|
|
Loading…
Reference in a new issue