TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
<?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()]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?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 string 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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?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 Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
#[Autoconfigure(public: true)]
|
||||
class SoftReferenceParserFactory
|
||||
{
|
||||
protected array $softReferenceParsers = [];
|
||||
|
||||
public function __construct(
|
||||
#[Autowire(service: 'cache.runtime')]
|
||||
protected readonly FrontendInterface $runtimeCache,
|
||||
protected readonly LoggerInterface $logger,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Adds a parser via DI.
|
||||
*
|
||||
* @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 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 ?? [];
|
||||
}
|
||||
|
||||
if (!$this->hasSoftReferenceParser($parserKey)) {
|
||||
$this->logger->warning('No soft reference parser exists for the key "{parserKey}".', ['parserKey' => $parserKey]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$parser = $this->getSoftReferenceParser($parserKey);
|
||||
$parser->setParserKey($parserKey, $parameters);
|
||||
|
||||
yield $parser;
|
||||
}
|
||||
}
|
||||
|
||||
public function hasSoftReferenceParser(string $softReferenceParserKey): bool
|
||||
{
|
||||
return isset($this->softReferenceParsers[$softReferenceParserKey]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function getSoftReferenceParser(string $softReferenceParserKey): SoftReferenceParserInterface
|
||||
{
|
||||
if ($softReferenceParserKey === '') {
|
||||
throw new \InvalidArgumentException(
|
||||
'The soft reference parser key cannot be empty.',
|
||||
1627899274
|
||||
);
|
||||
}
|
||||
|
||||
if (!$this->hasSoftReferenceParser($softReferenceParserKey)) {
|
||||
throw new \OutOfRangeException(
|
||||
sprintf('No soft reference parser found for "%s".', $softReferenceParserKey),
|
||||
1627899342
|
||||
);
|
||||
}
|
||||
|
||||
return $this->softReferenceParsers[$softReferenceParserKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered soft reference parsers
|
||||
*/
|
||||
public function getSoftReferenceParsers(): array
|
||||
{
|
||||
return $this->softReferenceParsers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?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.
|
||||
* which are found in-text in content. The <a href="t3://page?[page_id]> tag from typical bodytext fields
|
||||
* is an example of this.
|
||||
* This interface defines the "parse" method, which parsers have to implement.
|
||||
* TYPO3 has already implemented parsers for the most well-known types. Soft Reference Parsers can also be user-defined.
|
||||
* The Soft Reference Parsers are used by the system to find these references and process them accordingly in import/export actions and copy operations.
|
||||
*/
|
||||
interface SoftReferenceParserInterface
|
||||
{
|
||||
/**
|
||||
* Main function through which can parse content for a specific field.
|
||||
*
|
||||
* @param string $table Database table name
|
||||
* @param string $field Field name for which processing occurs
|
||||
* @param int $uid UID of the record
|
||||
* @param string $content The content/value of the field
|
||||
* @param string $structurePath If running from inside a FlexForm structure, this is the path of the tag.
|
||||
* @return SoftReferenceParserResult Result object on positive matches, see description above.
|
||||
* @see SoftReferenceParserResult
|
||||
*/
|
||||
public function parse(string $table, string $field, int $uid, string $content, string $structurePath = ''): SoftReferenceParserResult;
|
||||
|
||||
/**
|
||||
* The two properties parserKey and parameters may be set to generate a unique token ID from them.
|
||||
* This is not needed for every parser, but useful if a parser can deal with multiple parser keys.
|
||||
*
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* Returns the parser key, which was previously set by "setParserKey"
|
||||
*/
|
||||
public function getParserKey(): string;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* The result object has two properties: "content" and matched elements.
|
||||
*
|
||||
* content:
|
||||
* Is a string containing the input content but possibly with tokens inside.
|
||||
* Tokens are strings like {softref:[tokenID]}, which is a placeholder for a value extracted by a softref parser.
|
||||
* For each token there MUST be an entry in the "elements" key, which has a "subst" key defining the tokenID and the tokenValue. See below.
|
||||
*
|
||||
* matched elements:
|
||||
* is an array where the keys are insignificant, but the values are arrays with these keys:
|
||||
* "matchString" => // The value of the match. This is only for informational purposes to show what was found.
|
||||
* "error" => // An error message can be set here, like "file not found" etc.
|
||||
* "subst" => [ // If this array is found there MUST be a token in the output content as well!
|
||||
* "tokenID" => // The tokenID string corresponding to the token in output content, {softref:[tokenID]}. This is typically an md5 hash of a string defining uniquely the position of the element.
|
||||
* "tokenValue" => // The value that the token substitutes in the text. Basically, if this value is inserted instead of the token the content should match what was inputted originally.
|
||||
* "type" => // file / db / string = the type of substitution. "file" means it is a relative file [automatically mapped], "db" means a database record reference [automatically mapped], "string" means it is manually modified string content (eg. an email address)
|
||||
* "relFileName" => // (for "file" type): Relative filename. May not necessarily exist. This could be noticed in the error key.
|
||||
* "recordRef" => // (for "db" type) : Reference to DB record on the form [table]:[uid]. May not necessarily exist.
|
||||
* "title" => // Title of element (for backend information)
|
||||
* "description" => // Description of element (for backend information)
|
||||
* ]
|
||||
*/
|
||||
final class SoftReferenceParserResult
|
||||
{
|
||||
private string $content = '';
|
||||
private array $elements = [];
|
||||
private bool $hasMatched = false;
|
||||
|
||||
public static function create(string $content, array $elements): self
|
||||
{
|
||||
if ($elements === []) {
|
||||
return self::createWithoutMatches();
|
||||
}
|
||||
|
||||
$obj = new self();
|
||||
$obj->content = $content;
|
||||
$obj->elements = $elements;
|
||||
$obj->hasMatched = true;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public static function createWithoutMatches(): self
|
||||
{
|
||||
// @todo: set protected, use create() with empty elements instead.
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function hasMatched(): bool
|
||||
{
|
||||
return $this->hasMatched;
|
||||
}
|
||||
|
||||
public function hasContent(): bool
|
||||
{
|
||||
return $this->content !== '';
|
||||
}
|
||||
|
||||
public function getContent(): string
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
public function getMatchedElements(): array
|
||||
{
|
||||
return $this->elements;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?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 full field value targeted for manual substitution (for import /export features)
|
||||
*/
|
||||
class SubstituteSoftReferenceParser extends AbstractSoftReferenceParser
|
||||
{
|
||||
public function parse(string $table, string $field, int $uid, string $content, string $structurePath = ''): SoftReferenceParserResult
|
||||
{
|
||||
$this->setTokenIdBasePrefix($table, (string)$uid, $field, $structurePath);
|
||||
$tokenID = $this->makeTokenID();
|
||||
|
||||
return SoftReferenceParserResult::create(
|
||||
'{softref:' . $tokenID . '}',
|
||||
[
|
||||
[
|
||||
'matchString' => $content,
|
||||
'subst' => [
|
||||
'type' => 'string',
|
||||
'tokenID' => $tokenID,
|
||||
'tokenValue' => $content,
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
<?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 Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\DataHandling\Event\AppendLinkHandlerElementsEvent;
|
||||
use TYPO3\CMS\Core\LinkHandling\Exception\UnknownLinkHandlerException;
|
||||
use TYPO3\CMS\Core\LinkHandling\LinkService;
|
||||
use TYPO3\CMS\Core\LinkHandling\TypoLinkCodecService;
|
||||
use TYPO3\CMS\Core\Resource\AbstractFile;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* TypoLink value processing.
|
||||
* Will process input value as a TypoLink value.
|
||||
* References to page id or file, possibly with anchor/target, possibly commaseparated list.
|
||||
*/
|
||||
class TypolinkSoftReferenceParser extends AbstractSoftReferenceParser
|
||||
{
|
||||
protected EventDispatcherInterface $eventDispatcher;
|
||||
|
||||
public function __construct(EventDispatcherInterface $eventDispatcher)
|
||||
{
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
public function parse(string $table, string $field, int $uid, string $content, string $structurePath = ''): SoftReferenceParserResult
|
||||
{
|
||||
$this->setTokenIdBasePrefix($table, (string)$uid, $field, $structurePath);
|
||||
|
||||
// First, split the input string by a comma if the "linkList" parameter is set.
|
||||
// An example: the link field for images in content elements of type "textpic" or "image". This field CAN be configured to define a link per image, separated by comma.
|
||||
if (in_array('linkList', $this->parameters, true)) {
|
||||
// Preserving whitespace on purpose.
|
||||
$linkElement = explode(',', $content);
|
||||
} else {
|
||||
// If only one element, just set in this array to make it easy below.
|
||||
$linkElement = [$content];
|
||||
}
|
||||
// Traverse the links now:
|
||||
$elements = [];
|
||||
foreach ($linkElement as $k => $typolinkValue) {
|
||||
$tLP = $this->getTypoLinkParts($typolinkValue, $table, $uid);
|
||||
$linkElement[$k] = $this->setTypoLinkPartsElement($tLP, $elements, $typolinkValue, $k);
|
||||
}
|
||||
|
||||
return SoftReferenceParserResult::create(
|
||||
implode(',', $linkElement),
|
||||
$elements
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze content as a TypoLink value and return an array with properties.
|
||||
* TypoLinks format is: <link [typolink] [browser target] [css class] [title attribute] [additionalParams]>.
|
||||
* See TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::typolink()
|
||||
* The syntax of the [typolink] part is: [typolink] = [page id][,[type value]][#[anchor, if integer = tt_content uid]]
|
||||
* The extraction is based on how \TYPO3\CMS\Frontend\ContentObject::typolink() behaves.
|
||||
*
|
||||
* @param string $typolinkValue TypoLink value.
|
||||
* @param string $referenceTable The reference table
|
||||
* @param int $referenceUid The UID of the reference record
|
||||
* @return array Array with the properties of the input link specified. The key "type" will reveal the type. If that is blank it could not be determined.
|
||||
* @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::typolink()
|
||||
* @see setTypoLinkPartsElement()
|
||||
*/
|
||||
protected function getTypoLinkParts(string $typolinkValue, string $referenceTable, int $referenceUid)
|
||||
{
|
||||
$finalTagParts = GeneralUtility::makeInstance(TypoLinkCodecService::class)->decode($typolinkValue);
|
||||
|
||||
$link_param = $finalTagParts['url'];
|
||||
// we define various keys below, "url" might be misleading
|
||||
unset($finalTagParts['url']);
|
||||
|
||||
if (stripos(rawurldecode(trim($link_param)), 'phar://') === 0) {
|
||||
throw new \RuntimeException(
|
||||
'phar scheme not allowed as soft reference target',
|
||||
1530030672
|
||||
);
|
||||
}
|
||||
|
||||
$linkService = GeneralUtility::makeInstance(LinkService::class);
|
||||
try {
|
||||
$linkData = $linkService->resolve($link_param);
|
||||
switch ($linkData['type']) {
|
||||
case LinkService::TYPE_RECORD:
|
||||
$referencePageId = $referenceTable === 'pages'
|
||||
? $referenceUid
|
||||
: (int)(BackendUtility::getRecord($referenceTable, $referenceUid)['pid'] ?? 0);
|
||||
if ($referencePageId) {
|
||||
$pageTsConfig = BackendUtility::getPagesTSconfig($referencePageId);
|
||||
$table = $pageTsConfig['TCEMAIN.']['linkHandler.'][$linkData['identifier'] . '.']['configuration.']['table'] ?? $linkData['identifier'];
|
||||
} else {
|
||||
// Backwards compatibility for the old behaviour, where the identifier was saved as the table.
|
||||
$table = $linkData['identifier'];
|
||||
}
|
||||
$finalTagParts['table'] = $table;
|
||||
$finalTagParts['uid'] = $linkData['uid'];
|
||||
break;
|
||||
case LinkService::TYPE_PAGE:
|
||||
$linkData['pageuid'] = (int)($linkData['pageuid'] ?? 0);
|
||||
if (isset($linkData['pagetype'])) {
|
||||
$linkData['pagetype'] = (int)$linkData['pagetype'];
|
||||
}
|
||||
if (isset($linkData['fragment'])) {
|
||||
$finalTagParts['anchor'] = $linkData['fragment'];
|
||||
}
|
||||
break;
|
||||
case LinkService::TYPE_FILE:
|
||||
case LinkService::TYPE_UNKNOWN:
|
||||
if (isset($linkData['file'])) {
|
||||
$finalTagParts['type'] = LinkService::TYPE_FILE;
|
||||
$linkData['file'] = $linkData['file'] instanceof AbstractFile ? $linkData['file']->getUid() : $linkData['file'];
|
||||
} else {
|
||||
$pU = parse_url($link_param);
|
||||
parse_str($pU['query'] ?? '', $query);
|
||||
if (isset($query['uid'])) {
|
||||
$finalTagParts['type'] = LinkService::TYPE_FILE;
|
||||
$finalTagParts['file'] = (int)$query['uid'];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return array_merge($finalTagParts, $linkData);
|
||||
} catch (UnknownLinkHandlerException $e) {
|
||||
// Cannot handle anything
|
||||
return $finalTagParts;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recompile a TypoLink value from the array of properties made with getTypoLinkParts() into an elements array
|
||||
*
|
||||
* @param array $tLP TypoLink properties
|
||||
* @param array $elements Array of elements to be modified with substitution / information entries.
|
||||
* @param string $content The content to process.
|
||||
* @param int $idx Index value of the found element - user to make unique but stable tokenID
|
||||
* @return string The input content, possibly containing tokens now according to the added substitution entries in $elements
|
||||
* @see getTypoLinkParts()
|
||||
*/
|
||||
protected function setTypoLinkPartsElement($tLP, &$elements, $content, $idx)
|
||||
{
|
||||
// Initialize, set basic values. In any case a link will be shown
|
||||
$tokenID = $this->makeTokenID('setTypoLinkPartsElement:' . $idx);
|
||||
$elements[$tokenID . ':' . $idx] = [];
|
||||
$elements[$tokenID . ':' . $idx]['matchString'] = $content;
|
||||
// Based on link type, maybe do more:
|
||||
switch ((string)($tLP['type'] ?? '')) {
|
||||
case LinkService::TYPE_EMAIL:
|
||||
// Mail addresses can be substituted manually:
|
||||
$elements[$tokenID . ':' . $idx]['subst'] = [
|
||||
'type' => 'string',
|
||||
'tokenID' => $tokenID,
|
||||
'tokenValue' => (string)($tLP['email'] ?? ''),
|
||||
];
|
||||
// Output content will be the token instead:
|
||||
$content = '{softref:' . $tokenID . '}';
|
||||
break;
|
||||
case LinkService::TYPE_TELEPHONE:
|
||||
// phone number can be substituted manually:
|
||||
$elements[$tokenID . ':' . $idx]['subst'] = [
|
||||
'type' => 'string',
|
||||
'tokenID' => $tokenID,
|
||||
'tokenValue' => (string)($tLP['telephone'] ?? ''),
|
||||
];
|
||||
// Output content will be the token instead:
|
||||
$content = '{softref:' . $tokenID . '}';
|
||||
break;
|
||||
case LinkService::TYPE_URL:
|
||||
// URLs can be substituted manually
|
||||
$elements[$tokenID . ':' . $idx]['subst'] = [
|
||||
'type' => 'external',
|
||||
'tokenID' => $tokenID,
|
||||
'tokenValue' => (string)($tLP['url'] ?? ''),
|
||||
];
|
||||
// Output content will be the token instead:
|
||||
$content = '{softref:' . $tokenID . '}';
|
||||
break;
|
||||
case LinkService::TYPE_FOLDER:
|
||||
// This is a link to a folder...
|
||||
unset($elements[$tokenID . ':' . $idx]);
|
||||
return $content;
|
||||
case LinkService::TYPE_FILE:
|
||||
// Process files referenced by their FAL uid
|
||||
if (isset($tLP['file'])) {
|
||||
$fileId = $tLP['file'] instanceof AbstractFile ? $tLP['file']->getUid() : $tLP['file'];
|
||||
// Token and substitute value
|
||||
$elements[$tokenID . ':' . $idx]['subst'] = [
|
||||
'type' => 'db',
|
||||
'recordRef' => 'sys_file:' . $fileId,
|
||||
'tokenID' => $tokenID,
|
||||
'tokenValue' => 'file:' . $fileId,
|
||||
];
|
||||
// Output content will be the token instead:
|
||||
$content = '{softref:' . $tokenID . '}';
|
||||
} elseif ($tLP['identifier'] ?? false) {
|
||||
$linkHandlerValue = explode(':', trim($tLP['identifier']), 2)[1];
|
||||
if (MathUtility::canBeInterpretedAsInteger($linkHandlerValue)) {
|
||||
// Token and substitute value
|
||||
$elements[$tokenID . ':' . $idx]['subst'] = [
|
||||
'type' => 'db',
|
||||
'recordRef' => 'sys_file:' . $linkHandlerValue,
|
||||
'tokenID' => $tokenID,
|
||||
'tokenValue' => (string)$tLP['identifier'],
|
||||
];
|
||||
// Output content will be the token instead:
|
||||
$content = '{softref:' . $tokenID . '}';
|
||||
} else {
|
||||
// This is a link to a folder...
|
||||
return $content;
|
||||
}
|
||||
} else {
|
||||
return $content;
|
||||
}
|
||||
break;
|
||||
case LinkService::TYPE_PAGE:
|
||||
// Rebuild page reference typolink part:
|
||||
$content = '';
|
||||
// Set page id:
|
||||
if ($tLP['pageuid']) {
|
||||
$content .= '{softref:' . $tokenID . '}';
|
||||
$elements[$tokenID . ':' . $idx]['subst'] = [
|
||||
'type' => 'db',
|
||||
'recordRef' => 'pages:' . $tLP['pageuid'],
|
||||
'tokenID' => $tokenID,
|
||||
'tokenValue' => (string)$tLP['pageuid'],
|
||||
];
|
||||
}
|
||||
// Add type if applicable
|
||||
if ((string)($tLP['pagetype'] ?? '') !== '') {
|
||||
$content .= ',' . $tLP['pagetype'];
|
||||
}
|
||||
// Add anchor if applicable
|
||||
if ((string)($tLP['anchor'] ?? '') !== '') {
|
||||
// Anchor is assumed to point to a content elements:
|
||||
if (MathUtility::canBeInterpretedAsInteger($tLP['anchor'])) {
|
||||
// Initialize a new entry because we have a new relation:
|
||||
$newTokenID = $this->makeTokenID('setTypoLinkPartsElement:anchor:' . $idx);
|
||||
$elements[$newTokenID . ':' . $idx] = [];
|
||||
$elements[$newTokenID . ':' . $idx]['matchString'] = 'Anchor Content Element: ' . $tLP['anchor'];
|
||||
$content .= '#{softref:' . $newTokenID . '}';
|
||||
$elements[$newTokenID . ':' . $idx]['subst'] = [
|
||||
'type' => 'db',
|
||||
'recordRef' => 'tt_content:' . $tLP['anchor'],
|
||||
'tokenID' => $newTokenID,
|
||||
'tokenValue' => (string)$tLP['anchor'],
|
||||
];
|
||||
} else {
|
||||
// Anchor is a hardcoded string
|
||||
$content .= '#' . $tLP['anchor'];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case LinkService::TYPE_RECORD:
|
||||
$elements[$tokenID . ':' . $idx]['subst'] = [
|
||||
'type' => 'db',
|
||||
'recordRef' => $tLP['table'] . ':' . $tLP['uid'],
|
||||
'tokenID' => $tokenID,
|
||||
'tokenValue' => (string)$content,
|
||||
];
|
||||
|
||||
$content = '{softref:' . $tokenID . '}';
|
||||
break;
|
||||
default:
|
||||
$event = new AppendLinkHandlerElementsEvent($tLP, $content, $elements, $idx, $tokenID);
|
||||
$this->eventDispatcher->dispatch($event);
|
||||
|
||||
$elements = $event->getElements();
|
||||
$tLP = $event->getLinkParts();
|
||||
$content = $event->getContent();
|
||||
|
||||
if (!$event->isResolved()) {
|
||||
$elements[$tokenID . ':' . $idx]['error'] = 'Couldn\'t decide typolink mode.';
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
// Finally, for all entries that was rebuild with tokens, add target, class, title and additionalParams in the end
|
||||
$tLP['url'] = $content;
|
||||
// Return rebuilt typolink value
|
||||
return GeneralUtility::makeInstance(TypoLinkCodecService::class)->encode($tLP);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?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 Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use TYPO3\CMS\Core\DataHandling\Event\AppendLinkHandlerElementsEvent;
|
||||
use TYPO3\CMS\Core\Html\HtmlParser;
|
||||
use TYPO3\CMS\Core\LinkHandling\LinkService;
|
||||
use TYPO3\CMS\Core\Resource\File;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* TypoLink tag processing.
|
||||
* Will search for <link ...> and <a> tags in the content string and process any found.
|
||||
*/
|
||||
class TypolinkTagSoftReferenceParser extends AbstractSoftReferenceParser
|
||||
{
|
||||
protected EventDispatcherInterface $eventDispatcher;
|
||||
|
||||
public function __construct(EventDispatcherInterface $eventDispatcher)
|
||||
{
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
public function parse(string $table, string $field, int $uid, string $content, string $structurePath = ''): SoftReferenceParserResult
|
||||
{
|
||||
$this->setTokenIdBasePrefix($table, (string)$uid, $field, $structurePath);
|
||||
|
||||
// Parse string for special TYPO3 <link> tag:
|
||||
$htmlParser = GeneralUtility::makeInstance(HtmlParser::class);
|
||||
$linkService = GeneralUtility::makeInstance(LinkService::class);
|
||||
$linkTags = $htmlParser->splitTags('a', $content);
|
||||
// Traverse result:
|
||||
$elements = [];
|
||||
foreach ($linkTags as $key => $foundValue) {
|
||||
if ($key % 2 && preg_match('/href="([^"]+)"/', $foundValue, $matches)) {
|
||||
try {
|
||||
$linkDetails = $linkService->resolve($matches[1]);
|
||||
if ($linkDetails['type'] === LinkService::TYPE_FILE && preg_match('/file\?uid=(\d+)/', $matches[1], $fileIdMatch)) {
|
||||
$token = $this->makeTokenID((string)$key);
|
||||
$elements[$key]['matchString'] = $foundValue;
|
||||
$linkTags[$key] = str_replace($matches[1], '{softref:' . $token . '}', $foundValue);
|
||||
$elements[$key]['subst'] = [
|
||||
'type' => 'db',
|
||||
'recordRef' => 'sys_file:' . $fileIdMatch[1],
|
||||
'tokenID' => $token,
|
||||
'tokenValue' => 'file:' . ($linkDetails['file'] instanceof File ? $linkDetails['file']->getUid() : $fileIdMatch[1]),
|
||||
];
|
||||
} elseif ($linkDetails['type'] === LinkService::TYPE_PAGE && preg_match('/page\?[^#]*\buid=(\d+)(?:[^#]*#(\d+))?/', $matches[1], $pageAndAnchorMatches)) {
|
||||
$token = $this->makeTokenID((string)$key);
|
||||
$content = '{softref:' . $token . '}';
|
||||
$elements[$key]['matchString'] = $foundValue;
|
||||
$elements[$key]['subst'] = [
|
||||
'type' => 'db',
|
||||
'recordRef' => 'pages:' . ($linkDetails['pageuid'] ?? 0),
|
||||
'tokenID' => $token,
|
||||
'tokenValue' => $linkDetails['pageuid'] ?? '',
|
||||
];
|
||||
if (isset($pageAndAnchorMatches[2])) {
|
||||
// Anchor is assumed to point to a content elements:
|
||||
if (MathUtility::canBeInterpretedAsInteger($pageAndAnchorMatches[2])) {
|
||||
// Initialize a new entry because we have a new relation:
|
||||
$newTokenID = $this->makeTokenID('setTypoLinkPartsElement:anchor:' . $key);
|
||||
$elements[$newTokenID . ':' . $key] = [];
|
||||
$elements[$newTokenID . ':' . $key]['matchString'] = 'Anchor Content Element: ' . $pageAndAnchorMatches[2];
|
||||
$content .= '#{softref:' . $newTokenID . '}';
|
||||
$elements[$newTokenID . ':' . $key]['subst'] = [
|
||||
'type' => 'db',
|
||||
'recordRef' => 'tt_content:' . $pageAndAnchorMatches[2],
|
||||
'tokenID' => $newTokenID,
|
||||
'tokenValue' => $pageAndAnchorMatches[2],
|
||||
];
|
||||
} else {
|
||||
// Anchor is a hardcoded string
|
||||
$content .= '#' . $pageAndAnchorMatches[2];
|
||||
}
|
||||
}
|
||||
$linkTags[$key] = str_replace($matches[1], $content, $foundValue);
|
||||
} elseif ($linkDetails['type'] === LinkService::TYPE_URL) {
|
||||
$token = $this->makeTokenID((string)$key);
|
||||
$elements[$key]['matchString'] = $foundValue;
|
||||
$linkTags[$key] = str_replace($matches[1], '{softref:' . $token . '}', $foundValue);
|
||||
$elements[$key]['subst'] = [
|
||||
'type' => 'external',
|
||||
'tokenID' => $token,
|
||||
'tokenValue' => (string)($linkDetails['url'] ?? ''),
|
||||
];
|
||||
} elseif ($linkDetails['type'] === LinkService::TYPE_EMAIL) {
|
||||
$token = $this->makeTokenID((string)$key);
|
||||
$elements[$key]['matchString'] = $foundValue;
|
||||
$linkTags[$key] = str_replace($matches[1], '{softref:' . $token . '}', $foundValue);
|
||||
$elements[$key]['subst'] = [
|
||||
'type' => 'string',
|
||||
'tokenID' => $token,
|
||||
'tokenValue' => (string)($linkDetails['email'] ?? ''),
|
||||
];
|
||||
} elseif ($linkDetails['type'] === LinkService::TYPE_TELEPHONE) {
|
||||
$token = $this->makeTokenID((string)$key);
|
||||
$elements[$key]['matchString'] = $foundValue;
|
||||
$linkTags[$key] = str_replace($matches[1], '{softref:' . $token . '}', $foundValue);
|
||||
$elements[$key]['subst'] = [
|
||||
'type' => 'string',
|
||||
'tokenID' => $token,
|
||||
'tokenValue' => (string)($linkDetails['telephone'] ?? ''),
|
||||
];
|
||||
} else {
|
||||
$token = $this->makeTokenID((string)$key);
|
||||
$event = new AppendLinkHandlerElementsEvent($linkDetails, $content, $elements, $key, $token);
|
||||
$this->eventDispatcher->dispatch($event);
|
||||
|
||||
if (!$event->isResolved()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$elements = $event->getElements();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// skip invalid links
|
||||
}
|
||||
}
|
||||
}
|
||||
// Return output:
|
||||
return SoftReferenceParserResult::create(
|
||||
implode('', $linkTags),
|
||||
$elements
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?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 URLs in content
|
||||
*/
|
||||
class UrlSoftReferenceParser extends AbstractSoftReferenceParser
|
||||
{
|
||||
/**
|
||||
* We do not use a-z for letters, so we can also allow unicode characters. For this purpose we use \p{L}, for example.
|
||||
* And we must use the modifier "u" in the regex.
|
||||
* Domains may contain umlauts (ä,ö,ü).
|
||||
*
|
||||
* PHP regex with unicode character properties:
|
||||
* \p{L}: letter
|
||||
* \p{Ll}: lower case letter
|
||||
* @see https://www.php.net/manual/en/regexp.reference.unicode.php
|
||||
*/
|
||||
protected const REGEXP = '/([^[:alnum:]\'"=]+|\\s+)((https?|ftp):\\/\\/[!#$&-;=?-\[\]_\\p{L}~](?:[!#$&-;=?-\[\]_\\p{L}~]+|%[0-9\\p{L}]{2})*)/u';
|
||||
|
||||
public function parse(string $table, string $field, int $uid, string $content, string $structurePath = ''): SoftReferenceParserResult
|
||||
{
|
||||
$elements = [];
|
||||
$modifiedContent = ' ' . $content . ' ';
|
||||
|
||||
// Find all URLs using preg_match_all
|
||||
$matches = [];
|
||||
if (preg_match_all(self::REGEXP, $modifiedContent, $matches, PREG_SET_ORDER)) {
|
||||
// Process each match
|
||||
foreach ($matches as $idx => $match) {
|
||||
$prefix = $match[1];
|
||||
$url = $match[2];
|
||||
|
||||
$tokenID = $this->makeTokenID((string)$idx);
|
||||
$elements[$idx] = [];
|
||||
$elements[$idx]['matchString'] = $url;
|
||||
|
||||
if (in_array('subst', $this->parameters, true)) {
|
||||
// Replace the URL with a token in the content
|
||||
$modifiedContent = str_replace($prefix . $url, $prefix . '{softref:' . $tokenID . '}', $modifiedContent);
|
||||
$elements[$idx]['subst'] = [
|
||||
'type' => 'string',
|
||||
'tokenID' => $tokenID,
|
||||
'tokenValue' => $url,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SoftReferenceParserResult::create(
|
||||
substr($modifiedContent, 1, -1),
|
||||
$elements
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user