Commit 48810cb7 authored by Nikita Hovratov's avatar Nikita Hovratov Committed by Benni Mack
Browse files

[FEATURE] Register SoftReference parsers via DI

The concept for registration and usage of soft reference parsers
received a complete overhaul.

Starting with the registration, it is now possible to register soft
reference parsers by dependency injection in the extension's
Services.(yaml|php) file. For this, the new tag name
"softreference.parser" has been introduced. One has to provide the
additional attribute "parserKey" to identify the parser. This
replaces the old way of registering these parsers in the $GLOBALS
array. If a parser is registered with the same key in both ways,
the old way takes precedence for b/w compatibility.

This comes with a completely new factory service class
TYPO3\CMS\Core\DataHandling\SoftReference\SoftReferenceParserFactory.
This classes' responsibilities are collecting all registered soft
reference parsers and serving them to the consumer by calling the
method "getSoftReferenceParser" with the desired parser key as the
only argument. There is a compatibility layer for the old way of
registration and for classes not implementing the new interface.

Soft reference parsers now have to implement
TYPO3\CMS\Core\DataHandling\SoftReference\SoftReferenceParserInterface.
The interface defines the implementation of the "parse" method.
The first 4 and the last parameter stay the same as in the old method
"findRef". The remaining two parameters "spKey" and "spParams" have to
be set with the "setParserKey" method, if they are needed. The key can
be retrieved by using the "getParserKey" method.

The different parser implementations in the old class
TYPO3\CMS\Core\Database\SoftReferenceIndex have been extracted and
moved into dedicated classes in the
TYPO3\CMS\Core\DataHandling\SoftReference namespace. Missing tests
for parsers other than "typolink" and "typolink_tag" are added.

The method makeTokenID of SoftReferenceIndex has been moved into
TYPO3\CMS\Core\DataHandling\SoftReference\AbstractSoftReferenceParser.
A parser can extend this abstract class, if this method is needed.

The methods of BackendUtility "softRefParserObj" and
"explodeSoftRefParserList" are now deprecated and the replacement
TYPO3\CMS\Core\DataHandling\SoftReference\SoftReferenceParserFactory
should be used instead.

Resolves: #94687
Resolves: #94741
Releases: master
Change-Id: I460bfdd4478194fa4b4111fc44871f7225c6c084
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/70177

Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Reviewed-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
parent 7aa30014
......@@ -37,6 +37,8 @@ use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction;
use TYPO3\CMS\Core\Database\RelationHandler;
use TYPO3\CMS\Core\DataHandling\SoftReference\SoftReferenceParserFactory;
use TYPO3\CMS\Core\DataHandling\SoftReference\SoftReferenceParserInterface;
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
use TYPO3\CMS\Core\Imaging\Icon;
......@@ -2920,12 +2922,47 @@ class BackendUtility
* @param string $spKey softRef parser key
* @return mixed If available, returns Soft link parser object, otherwise false.
* @internal should only be used from within TYPO3 Core
* @deprecated will be removed in TYPO3 v12.0. Use SoftReferenceParserFactory->getParserByKey instead.
*/
public static function softRefParserObj($spKey)
{
trigger_error(
'BackendUtility::softRefParserObj will be removed in TYPO3 v12.0, use TYPO3\CMS\Core\DataHandling\SoftReference\SoftReferenceParserFactory->getParserByKey instead.',
E_USER_DEPRECATED
);
$className = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['softRefParser'][$spKey] ?? false;
$obj = null;
if ($className) {
return GeneralUtility::makeInstance($className);
$obj = GeneralUtility::makeInstance($className);
}
if (!$obj) {
try {
$obj = GeneralUtility::makeInstance(SoftReferenceParserFactory::class)->getSoftReferenceParser($spKey);
} catch (\OutOfBoundsException|\InvalidArgumentException $e) {
return false;
}
}
// Build a wrapper to call the API with the legacy findRef() call.
if ($obj) {
if ($obj instanceof SoftReferenceParserInterface && !method_exists($obj, 'findRef')) {
// Build a temporary class acting as a wrapper to call findRef() with the new API.
return new class($obj) {
private SoftReferenceParserInterface $parser;
public function __construct(SoftReferenceParserInterface $obj)
{
$this->parser = $obj;
}
public function findRef($table, $field, $uid, $content, $parserKey, $spParams, $structurePath)
{
$this->parser->setParserKey($parserKey, $spParams);
return $this->parser->parse($table, $field, $uid, $content, $structurePath)->toNullableArray();
}
};
}
return $obj;
}
return false;
}
......@@ -2947,9 +2984,15 @@ class BackendUtility
* @return array|bool Array where the parser key is the key and the value is the parameter string, FALSE if no parsers were found
* @throws \InvalidArgumentException
* @internal should only be used from within TYPO3 Core
* @deprecated will be removed in TYPO3 v12.0. Use SoftReferenceParserFactory->getParsersBySoftRefParserList instead.
*/
public static function explodeSoftRefParserList($parserList)
{
trigger_error(
'BackendUtility::explodeSoftRefParserList will be removed in TYPO3 v12.0, use TYPO3\CMS\Core\DataHandling\SoftReference\SoftReferenceParserFactory->getParsersBySoftRefParserList instead.',
E_USER_DEPRECATED
);
// Return immediately if list is blank:
if ((string)$parserList === '') {
return false;
......
......@@ -137,3 +137,8 @@ services:
tags:
- name: event.listener
identifier: 'backend-empty-colpos'
TYPO3\CMS\Core\DataHandling\SoftReference\SoftReferenceParserFactory:
public: true
arguments:
$runtimeCache: '@cache.runtime'
<?php
declare(strict_types=1);
/*
* 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 this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\DataHandling\SoftReference;
/**
* A generic parser class useful if tokenID prefixes are needed.
*/
abstract class AbstractSoftReferenceParser implements SoftReferenceParserInterface
{
protected string $tokenID_basePrefix = '';
protected string $parserKey = '';
protected array $parameters = [];
/**
* Make Token ID for input index.
*
* @param string $index Suffix value.
* @return string Token ID
*/
public function makeTokenID(string $index = ''): string
{
return md5($this->tokenID_basePrefix . ':' . $index);
}
/**
* @param string $parserKey The softref parser key.
* @param array $parameters Parameters of the softlink parser. Basically this is the content inside optional []-brackets after the softref keys. Parameters are exploded by ";
*/
public function setParserKey(string $parserKey, array $parameters): void
{
$this->parserKey = $parserKey;
$this->parameters = $parameters;
}
public function getParserKey(): string
{
return $this->parserKey;
}
protected function setTokenIdBasePrefix(string $table, string $uid, string $field, string $structurePath): void
{
$this->tokenID_basePrefix = implode(':', [$table, $uid, $field, $structurePath, $this->getParserKey()]);
}
/**
* @internal will be removed in favor of ->parse() in TYPO3 v12.0.
*/
public function findRef(string $table, string $field, int $uid, string $content, string $spKey, array $spParams, string $structurePath = '')
{
$this->setParserKey($spKey, $spParams);
return $this->parse($table, $field, $uid, $content, $structurePath)->toNullableArray();
}
}
<?php
declare(strict_types=1);
/*
* 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 this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\DataHandling\SoftReference;
/**
* Finding email addresses in content and making them substitutable.
*/
class EmailSoftReferenceParser extends AbstractSoftReferenceParser
{
public function parse(string $table, string $field, int $uid, string $content, string $structurePath = ''): SoftReferenceParserResult
{
$this->setTokenIdBasePrefix($table, (string)$uid, $field, $structurePath);
$elements = [];
// Email:
$parts = preg_split('/([\s\'":<>]+)([A-Za-z0-9._-]+[^-][@][A-Za-z0-9._-]+[.].[A-Za-z0-9]+)/', ' ' . $content . ' ', 10000, PREG_SPLIT_DELIM_CAPTURE);
foreach ($parts as $idx => $value) {
if ($idx % 3 === 2) {
// Ignore invalid emails, which haven't been filtered out by regex.
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
continue;
}
$tokenID = $this->makeTokenID((string)$idx);
$elements[$idx] = [];
$elements[$idx]['matchString'] = $value;
if (in_array('subst', $this->parameters, true)) {
$parts[$idx] = '{softref:' . $tokenID . '}';
$elements[$idx]['subst'] = [
'type' => 'string',
'tokenID' => $tokenID,
'tokenValue' => $value
];
}
}
}
return SoftReferenceParserResult::create(
substr(implode('', $parts), 1, -1),
$elements
);
}
}
<?php
declare(strict_types=1);
/*
* 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 this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\DataHandling\SoftReference;
/**
* Finding reference to files from extensions in content, but only to notify about their existence. No substitution
*/
class ExtensionPathSoftReferenceParser implements SoftReferenceParserInterface
{
private const REGEXP = '/([^[:alnum:]]+)(EXT:[[:alnum:]_]+\\/[^[:space:]"\',]*)/';
protected string $parserKey = '';
protected array $parameters = [];
public function parse(string $table, string $field, int $uid, string $content, string $structurePath = ''): SoftReferenceParserResult
{
$elements = [];
// Files starting with EXT:
$parts = preg_split(self::REGEXP, ' ' . $content . ' ', 10000, PREG_SPLIT_DELIM_CAPTURE) ?: [];
foreach ($parts as $idx => $value) {
if ($idx % 3 === 2) {
$elements[$idx] = [];
$elements[$idx]['matchString'] = $value;
}
}
return SoftReferenceParserResult::create(
substr(implode('', $parts), 1, -1),
$elements
);
}
/**
* @internal will be removed in favor of ->parse() in TYPO3 v12.0.
*/
public function findRef(string $table, string $field, int $uid, string $content, string $spKey, array $spParams, string $structurePath = '')
{
return $this->parse($table, $field, $uid, $content, $structurePath)->toNullableArray();
}
/**
* @param string $parserKey The softref parser key.
* @param array $parameters Parameters of the softlink parser. Basically this is the content inside optional []-brackets after the softref keys. Parameters are exploded by ";
*/
public function setParserKey(string $parserKey, array $parameters): void
{
$this->parserKey = $parserKey;
$this->parameters = $parameters;
}
public function getParserKey(): string
{
return $this->parserKey;
}
}
<?php
declare(strict_types=1);
/*
* 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 this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\DataHandling\SoftReference;
/**
* Just report if a value is found, nothing more.
*/
class NotifySoftReferenceParser implements SoftReferenceParserInterface
{
protected string $parserKey = '';
protected array $parameters = [];
/**
* @param string $parserKey The softref parser key.
* @param array $parameters Parameters of the softlink parser. Basically this is the content inside optional []-brackets after the softref keys. Parameters are exploded by ";
*/
public function setParserKey(string $parserKey, array $parameters): void
{
$this->parserKey = $parserKey;
$this->parameters = $parameters;
}
public function getParserKey(): string
{
return $this->parserKey;
}
public function parse(string $table, string $field, int $uid, string $content, string $structurePath = ''): SoftReferenceParserResult
{
// Simple notification
return SoftReferenceParserResult::create(
'',
[
[
'matchString' => $content
]
]
);
}
/**
* @internal will be removed in favor of ->parse() in TYPO3 v12.0.
*/
public function findRef(string $table, string $field, int $uid, string $content, string $spKey, array $spParams, string $structurePath = '')
{
return $this->parse($table, $field, $uid, $content, $structurePath)->toNullableArray();
}
}
<?php
declare(strict_types=1);
/*
* 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 this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\DataHandling\SoftReference;
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Factory class for soft reference parsers
*/
class SoftReferenceParserFactory
{
protected array $softReferenceParsers = [];
protected FrontendInterface $runtimeCache;
public function __construct(FrontendInterface $runtimeCache)
{
$this->runtimeCache = $runtimeCache;
// TODO remove in TYPO3 v12.0
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['softRefParser'] ?? [] as $parserKey => $className) {
trigger_error(
sprintf(
'The soft reference parser %s for the key "%s" is registered in the global array $GLOBALS[TYPO3_CONF_VARS][SC_OPTIONS][GLOBAL][softRefParser]. This way of registration will stop working in TYPO3 v12.0. Register the class in Configuration/Services.yaml instead.',
$className,
$parserKey
),
E_USER_DEPRECATED
);
$this->softReferenceParsers[$parserKey] = GeneralUtility::makeInstance($className);
}
}
/**
* Adds a parser via DI.
*
* @param SoftReferenceParserInterface $softReferenceParser
* @param string $parserKey
* @internal
*/
public function addParser(SoftReferenceParserInterface $softReferenceParser, string $parserKey): void
{
if (!isset($this->softReferenceParsers[$parserKey])) {
$this->softReferenceParsers[$parserKey] = $softReferenceParser;
}
}
/**
* Returns array of soft parser references
*
* @param string $parserList softRef parser list
* @return array|null Array where the parser key is the key and the value is the parameter string, FALSE if no parsers were found
*/
protected function explodeSoftRefParserList(string $parserList): ?array
{
// Return immediately if list is blank:
if ($parserList === '') {
return null;
}
$cacheId = 'backend-softRefList-' . md5($parserList);
$parserListCache = $this->runtimeCache->get($cacheId);
if ($parserListCache !== false) {
return $parserListCache;
}
// Otherwise parse the list:
$keyList = GeneralUtility::trimExplode(',', $parserList, true);
$output = [];
foreach ($keyList as $val) {
$reg = [];
if (preg_match('/^([[:alnum:]_-]+)\\[(.*)\\]$/', $val, $reg)) {
$output[$reg[1]] = GeneralUtility::trimExplode(';', $reg[2], true);
} else {
$output[$val] = '';
}
}
$this->runtimeCache->set($cacheId, $output);
return $output;
}
/**
* @param string $softRefParserList
* @param array|null $forcedParameters
* @return iterable<SoftReferenceParserInterface>
*/
public function getParsersBySoftRefParserList(string $softRefParserList, array $forcedParameters = null): iterable
{
foreach ($this->explodeSoftRefParserList($softRefParserList) ?? [] as $parserKey => $parameters) {
if (!is_array($parameters)) {
$parameters = $forcedParameters ?? [];
}
$parser = $this->getSoftReferenceParser($parserKey);
$parser->setParserKey($parserKey, $parameters);
yield $parser;
}
}
/**
* Get a Soft Reference Parser by the given soft reference key.
* Implementation must be registered in Configuration/Services.yaml
*
* VENDOR\YourExtension\SoftReference\UserDefinedSoftReferenceParser:
* tags:
* - name: softreference.parser
* parserKey: userdefined
*
*
* @param string $softReferenceParserKey
* @return SoftReferenceParserInterface
*/
public function getSoftReferenceParser(string $softReferenceParserKey): SoftReferenceParserInterface
{
if ($softReferenceParserKey === '') {
throw new \InvalidArgumentException(
'The soft reference parser key cannot be empty.',
1627899274
);
}
if (!isset($this->softReferenceParsers[$softReferenceParserKey])) {
throw new \OutOfRangeException(
sprintf('No soft reference parser found for "%s".', $softReferenceParserKey),
1627899342
);
}
$softReferenceParser = $this->softReferenceParsers[$softReferenceParserKey];
// @todo in v12 soft reference parsers, not implementing SoftReferenceParserInterface should throw an exception
if ($softReferenceParser instanceof SoftReferenceParserInterface) {
return $softReferenceParser;
}
// @todo everything below is deprecated and will be removed in v12
trigger_error(
sprintf('The class %s does not implement %s. The compatibility layer will be dropped in TYPO3 v12.0.', get_class($softReferenceParser), SoftReferenceParserInterface::class),
E_USER_DEPRECATED
);
if (!method_exists($softReferenceParser, 'findRef')) {
throw new \RuntimeException(
sprintf('The class %s must implement the findRef method.', get_class($softReferenceParser)),
1627899708
);
}
// Build a temporary class acting as a wrapper to call findRef() with the new API.
/** @var object $softReferenceParser */
return new class($softReferenceParser, $softReferenceParserKey) implements SoftReferenceParserInterface {
private object $parser;
private string $parserKey;
private array $parameters = [];
public function __construct(object $softReferenceParser, $parserKey)
{
$this->parser = $softReferenceParser;
$this->parserKey = $parserKey;
}
public function setParserKey(string $parserKey, array $parameters): void
{
$this->parserKey = $parserKey;
$this->parameters = $parameters;
}
public function getParserKey(): string
{
return $this->parserKey;
}
public function parse(
string $table,
string $field,
int $uid,
string $content,
string $structurePath = ''
): SoftReferenceParserResult {
$result = $this->parser->findRef($table, $field, $uid, $content, $this->parserKey, $this->parameters, $structurePath);
if (is_array($result)) {
return SoftReferenceParserResult::create($result['content'] ?? '', $result['elements'] ?? []);
}
return SoftReferenceParserResult::createWithoutMatches();
}
};
}
/**
* Get all registered soft reference parsers
*/
public function getSoftReferenceParsers(): array
{
return $this->softReferenceParsers;
}
}
<?php
declare(strict_types=1);
/*
* 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 this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\DataHandling\SoftReference;
/**
* Soft Reference parsing interface
*
* "Soft References" are references to database elements, files, email addresses, URLs etc.