TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user