Skip to content

Commit

Permalink
feat: Add VariablesService, remove support for TYPO3 10 (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
dhuf authored Sep 26, 2024
1 parent 2e21bf2 commit 4a94ef7
Show file tree
Hide file tree
Showing 21 changed files with 393 additions and 280 deletions.
21 changes: 8 additions & 13 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
check-composer:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- name: Install PHP
uses: shivammathur/setup-php@v2
Expand All @@ -29,12 +29,11 @@ jobs:
strategy:
matrix:
php-version:
- 7.4
- 8.0
- 8.1
- 8.2
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3

- name: Install PHP
uses: shivammathur/setup-php@v2
Expand All @@ -50,7 +49,7 @@ jobs:
needs:
- check-composer
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- name: Install PHP
uses: shivammathur/setup-php@v2
Expand Down Expand Up @@ -81,7 +80,7 @@ jobs:
needs:
- check-composer
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- name: Install PHP
uses: shivammathur/setup-php@v2
Expand All @@ -106,16 +105,12 @@ jobs:
strategy:
matrix:
include:
- php-version: '7.4'
typo3-version: '^10.4'
- php-version: '7.4'
typo3-version: '^11.5'
- php-version: '8.0'
typo3-version: '^11.5'
- php-version: '8.1'
typo3-version: '^11.5'
- php-version: '8.2'
typo3-version: '^11.5'
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- name: Install PHP
uses: shivammathur/setup-php@v2
Expand Down
31 changes: 31 additions & 0 deletions Classes/Domain/Model/Marker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with TYPO3 source code.
*
* The TYPO3 project - inspiring people to share!
*/

namespace Sinso\Variables\Domain\Model;

class Marker
{
public function __construct(
public int $uid,
public string $key,
public string $replacement,
// public ?int $smallestValueFromTimeFields,
) {
}

public function getMarkerWithBrackets(): string
{
return '{{' . $this->key . '}}';
}
}
47 changes: 47 additions & 0 deletions Classes/Domain/Model/MarkerCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with TYPO3 source code.
*
* The TYPO3 project - inspiring people to share!
*/

namespace Sinso\Variables\Domain\Model;

use Ramsey\Collection\AbstractArray;

class MarkerCollection extends AbstractArray
{
public function getType(): string
{
return Marker::class;
}

public function add(Marker $marker): void
{
$this[$marker->getMarkerWithBrackets()] = $marker;
}

/**
* @throws \RuntimeException
*/
public function get(string $markerKey): Marker
{
if (!isset($this[$markerKey])) {
throw new \RuntimeException('Marker not found');
}

return $this[$markerKey];
}

public function getMarkerKeys(): array
{
return array_keys($this->data);
}
}
171 changes: 9 additions & 162 deletions Classes/Hooks/ContentProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,180 +14,27 @@

namespace Sinso\Variables\Hooks;

use Doctrine\DBAL\Connection;
use Sinso\Variables\Utility\CacheKeyUtility;
use Sinso\Variables\Service\VariablesService;
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;

class ContentProcessor
{
protected VariablesService $variablesService;

/**
* Dynamically replaces variables by user content.
*/
public function replaceContent(array &$parameters, TypoScriptFrontendController $parentObject): void
public function __construct()
{
$extConfig = GeneralUtility::makeInstance(ExtensionConfiguration::class);

$content = $parentObject->content;

$markers = $this->getMarkers($parentObject);
$markerKeys = array_keys($markers);
$markerRegexp = '/(' . implode('|', $markerKeys) . ')/';

$usedMarkerKeys = [];
$cacheTags = [];
$loops = 0;
while (preg_match($markerRegexp, $content) && $loops++ < 100) {
foreach ($markerKeys as $markerKey) {
$newContent = str_replace($markerKey, $markers[$markerKey]['replacement'], $content);
if ($newContent !== $content) {
// Assign a cache key associated with the marker
$cacheTags[] = CacheKeyUtility::getCacheKey($markerKey);
$usedMarkerKeys[] = $markers[$markerKey]['markerKey'];
$content = $newContent;
}
}
}

$usedMarkerKeys = array_unique($usedMarkerKeys);

$minLifetime = min(
$this->getSmallestLifetimeForMarkers($usedMarkerKeys),
$parentObject->page['cache_timeout'] ?: PHP_INT_MAX
);

$parentObject->page['cache_timeout'] = $minLifetime;

if (count($cacheTags) > 0) {
$parentObject->addCacheTags($cacheTags);
}

// Remove all markers (avoids empty entries)
if ($extConfig->get('variables', 'removeUnreplacedMarkers')) {
$content = preg_replace('/{{.*?}}/', '', $content);
}

$parentObject->content = $content;
}

protected function getSmallestLifetimeForMarkers(array $usedMarkerKeys): int
{
$tableName = 'tx_variables_marker';
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($tableName)->createQueryBuilder();
$queryBuilder->getRestrictions()->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class));

// Code heavily inspired by:
// \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->getFirstTimeValueForRecord
$now = (int)$GLOBALS['ACCESS_TIME'];
// Max value possible to keep an int \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->realPageCacheContent ($timeOutTime = $GLOBALS['EXEC_TIME'] + $cacheTimeout;)
$result = PHP_INT_MAX - $GLOBALS['EXEC_TIME'];
$timeFields = [];
$timeConditions = $queryBuilder->expr()->orX();
foreach (['starttime', 'endtime'] as $field) {
if (isset($GLOBALS['TCA'][$tableName]['ctrl']['enablecolumns'][$field])) {
$timeFields[$field] = $GLOBALS['TCA'][$tableName]['ctrl']['enablecolumns'][$field];
$queryBuilder->addSelectLiteral(
'MIN('
. 'CASE WHEN '
. $queryBuilder->expr()->lte(
$timeFields[$field],
$queryBuilder->createNamedParameter($now, \PDO::PARAM_INT)
)
. ' THEN NULL ELSE ' . $queryBuilder->quoteIdentifier($timeFields[$field]) . ' END'
. ') AS ' . $queryBuilder->quoteIdentifier($timeFields[$field])
);
$timeConditions->add(
$queryBuilder->expr()->gt(
$timeFields[$field],
$queryBuilder->createNamedParameter($now, \PDO::PARAM_INT)
)
);
}
}

// if starttime or endtime are defined, evaluate them
if (!empty($timeFields)) {
// find the timestamp, when the current page's content changes the next time
$queryBuilder
->from($tableName)
->where(
$queryBuilder->expr()->in('marker', $queryBuilder->createNamedParameter($usedMarkerKeys, Connection::PARAM_STR_ARRAY)),
$timeConditions
);
$row = $queryBuilder
->execute()
->fetch();

if ($row) {
foreach ($timeFields as $timeField => $_) {
// if a MIN value is found, take it into account for the
// cache lifetime we have to filter out start/endtimes < $now,
// as the SQL query also returns rows with starttime < $now
// and endtime > $now (and using a starttime from the past
// would be wrong)
if ($row[$timeField] !== null && (int)$row[$timeField] > $now) {
$result = min($result, (int)$row[$timeField]);
}
}
}
}

return $result;
$this->variablesService = GeneralUtility::makeInstance(VariablesService::class);
}

/**
* Returns the markers available in the current root line.
* Dynamically replaces variables by user content.
*/
protected function getMarkers(TypoScriptFrontendController $frontendController): array
public function replaceContent(array &$parameters, TypoScriptFrontendController $parentObject): void
{
$table = 'tx_variables_marker';
$parentPages = array_map(function ($page) {
return $page['uid'];
}, $frontendController->rootLine);

if (!empty($frontendController->tmpl->setup['plugin.']['tx_variables.']['persistence.']['storagePid'])) {
$parentPages[] = (int)$frontendController->tmpl->setup['plugin.']['tx_variables.']['persistence.']['storagePid'];
}

$rows = $frontendController->cObj->getRecords(
$table,
[
'selectFields' => 'marker, replacement',
'pidInList' => implode(',', $parentPages),
'orderBy' => 'uid ASC',
]
);

$markers = [];
foreach ($rows as $row) {
$marker = '{{' . $row['marker'] . '}}';
$markers[$marker] = [
'uid' => $row['uid'],
'marker' => $marker,
'markerKey' => $row['marker'],
'replacement' => $row['replacement'],
];
}

if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['variables']['postProcessMarkers'] ?? null)) {
foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['variables']['postProcessMarkers'] as $classRef) {
$hookObj = GeneralUtility::makeInstance($classRef);
if (!($hookObj instanceof MarkersProcessorInterface)) {
throw new \RuntimeException($classRef . ' does not implement ' . MarkersProcessorInterface::class, 1512391205);
}
$hookObj->postProcessMarkers($markers);
}
}

// Sort markers
ksort($markers);

return $markers;
$extensionConfiguration = GeneralUtility::makeInstance(ExtensionConfiguration::class);
$this->variablesService->initialize($extensionConfiguration, $parentObject);
$this->variablesService->replaceMarkersInStructureAndAdjustCaching($parentObject->content);
}

}
5 changes: 1 addition & 4 deletions Classes/Hooks/DataHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ class DataHandler
public function clearCachePostProc(array $params): void
{
if (
isset($params['table'])
&& isset($params['uid'])
&& $params['table'] === 'tx_variables_marker'
isset($params['table'], $params['uid']) && $params['table'] === 'tx_variables_marker'
) {
$cacheTagsToFlush = [];
if (isset($params['marker'])) {
Expand All @@ -41,5 +39,4 @@ public function clearCachePostProc(array $params): void
}
}
}

}
6 changes: 3 additions & 3 deletions Classes/Hooks/MarkersProcessorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@

namespace Sinso\Variables\Hooks;

use Sinso\Variables\Domain\Model\MarkerCollection;

interface MarkersProcessorInterface
{

function postProcessMarkers(array &$markers);

public function postProcessMarkers(MarkerCollection $markers);
}
Loading

0 comments on commit 4a94ef7

Please sign in to comment.