Skip to content

Commit

Permalink
TASK: Revert possible performance optimizations and remigrate 8.3 Con…
Browse files Browse the repository at this point in the history
…vertUrisImplementation

the convert uris stuff was optimized while migrating the code to 9.0
it should behave more performant with the cost of being less accurate and not covering all edge-cases.

This change reverts those performance optimization and introduce all those additional preg_match and replaces again for more accuracy.

This is the even more expensive but more accurate 8.3 solution.

neos#4744 (comment)
  • Loading branch information
mhsdesign committed Nov 17, 2023
1 parent a508595 commit 94320b7
Showing 1 changed file with 86 additions and 80 deletions.
166 changes: 86 additions & 80 deletions Neos.Neos/Classes/Fusion/ConvertUrisImplementation.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public function evaluate()
if (!is_string($text)) {
throw new NeosException(sprintf(
'Only strings can be processed by this Fusion object, given: "%s".',
gettype($text)
get_debug_type($text)
), 1382624080);
}

Expand All @@ -120,7 +120,7 @@ public function evaluate()
if (!$node instanceof Node) {
throw new NeosException(sprintf(
'The current node must be an instance of Node, given: "%s".',
gettype($text)
get_debug_type($text)
), 1382624087);
}

Expand All @@ -139,53 +139,45 @@ public function evaluate()
$unresolvedUris = [];
$absolute = $this->fusionValue('absolute');

$processedContent = preg_replace_callback(
self::PATTERN_SUPPORTED_URIS,
function (array $matches) use (&$unresolvedUris, $absolute, $nodeAddress) {
$resolvedUri = null;
switch ($matches[1]) {
case 'node':
$nodeAddress = $nodeAddress->withNodeAggregateId(
NodeAggregateId::fromString($matches[2])
$processedContent = preg_replace_callback(self::PATTERN_SUPPORTED_URIS, function (array $matches) use ($node, $nodeAddress, &$unresolvedUris, $absolute) {
$resolvedUri = null;
switch ($matches[1]) {
case 'node':
$nodeAddress = $nodeAddress->withNodeAggregateId(
NodeAggregateId::fromString($matches[2])
);
$uriBuilder = new UriBuilder();
$uriBuilder->setRequest($this->runtime->getControllerContext()->getRequest());
$uriBuilder->setCreateAbsoluteUri($absolute);
try {
$resolvedUri = (string)NodeUriBuilder::fromUriBuilder($uriBuilder)->uriFor($nodeAddress);
} catch (NoMatchingRouteException) {
$this->systemLogger->info(sprintf('Could not resolve "%s" to a live node uri. The node was probably deleted.', $matches[0]), LogEnvironment::fromMethodName(__METHOD__));
}
$this->runtime->addCacheTag('node', $matches[2]);
break;
case 'asset':
$asset = $this->assetRepository->findByIdentifier($matches[2]);
if ($asset instanceof AssetInterface) {
$resolvedUri = $this->resourceManager->getPublicPersistentResourceUri(
$asset->getResource()
);
$uriBuilder = new UriBuilder();
$uriBuilder->setRequest($this->runtime->getControllerContext()->getRequest());
$uriBuilder->setCreateAbsoluteUri($absolute);
try {
$resolvedUri = (string)NodeUriBuilder::fromUriBuilder($uriBuilder)->uriFor($nodeAddress);
} catch (NoMatchingRouteException) {
$this->systemLogger->info(sprintf('Could not resolve "%s" to a live node uri. The node was probably deleted.', $matches[0]), LogEnvironment::fromMethodName(__METHOD__));
}
$this->runtime->addCacheTag('node', $matches[2]);
break;
case 'asset':
$asset = $this->assetRepository->findByIdentifier($matches[2]);
if ($asset instanceof AssetInterface) {
$resolvedUri = $this->resourceManager->getPublicPersistentResourceUri(
$asset->getResource()
);
$this->runtime->addCacheTag('asset', $matches[2]);
}
break;
}
$this->runtime->addCacheTag('asset', $matches[2]);
}
break;
}

if ($resolvedUri === null) {
$unresolvedUris[] = $matches[0];
return $matches[0];
}
if ($resolvedUri === null) {
$unresolvedUris[] = $matches[0];
return $matches[0];
}

return $resolvedUri;
},
$text
) ?: '';
return $resolvedUri;
}, $text);

if ($unresolvedUris !== []) {
$processedContent = preg_replace(
'/<a[^>]* href="(node|asset):\/\/[^"]+"[^>]*>(.*?)<\/a>/',
'$2',
$processedContent
) ?: '';
$processedContent = preg_replace(self::PATTERN_SUPPORTED_URIS, '', $processedContent) ?: '';
$processedContent = preg_replace('/<a(?:\s+[^>]*)?\s+href="(node|asset):\/\/[^"]+"[^>]*>(.*?)<\/a>/', '$2', $processedContent);
$processedContent = preg_replace(self::PATTERN_SUPPORTED_URIS, '', $processedContent);
}

$processedContent = $this->replaceLinkTargets($processedContent);
Expand All @@ -196,57 +188,71 @@ function (array $matches) use (&$unresolvedUris, $absolute, $nodeAddress) {
/**
* Replace the target attribute of link tags in processedContent with the target
* specified by externalLinkTarget and resourceLinkTarget options.
* Additionally sets rel="noopener" for links with target="_blank",
* and rel="noopener external" for external links.
* Additionally set rel="noopener external" for external links.
*
* @param string $processedContent
* @return string
*/
protected function replaceLinkTargets(string $processedContent): string
protected function replaceLinkTargets($processedContent)
{
$setNoOpener = $this->fusionValue('setNoOpener');
$setExternal = $this->fusionValue('setExternal');
$externalLinkTarget = trim($this->fusionValue('externalLinkTarget'));
$resourceLinkTarget = trim($this->fusionValue('resourceLinkTarget'));
if ($externalLinkTarget === '' && $resourceLinkTarget === '') {
return $processedContent;
}
$externalLinkTarget = \trim((string)$this->fusionValue('externalLinkTarget'));
$resourceLinkTarget = \trim((string)$this->fusionValue('resourceLinkTarget'));
$controllerContext = $this->runtime->getControllerContext();
$host = $controllerContext->getRequest()->getHttpRequest()->getUri()->getHost();
// todo optimize to only use one preg_replace_callback for uri converting
$processedContent = preg_replace_callback(
'~<a.*?href="(.*?)".*?>~i',
function ($matches) use ($externalLinkTarget, $resourceLinkTarget, $host, $setNoOpener, $setExternal) {
list($linkText, $linkHref) = $matches;
$uriHost = parse_url($linkHref, PHP_URL_HOST);
$processedContent = \preg_replace_callback(
'~<a\s+.*?href="(.*?)".*?>~i',
static function ($matches) use ($externalLinkTarget, $resourceLinkTarget, $host, $setNoOpener, $setExternal) {
[$linkText, $linkHref] = $matches;
$uriHost = \parse_url($linkHref, PHP_URL_HOST);
$target = null;
$isExternalLink = is_string($uriHost) && $uriHost !== $host;
if ($externalLinkTarget !== '' && $isExternalLink) {
$isExternalLink = \is_string($uriHost) && $uriHost !== $host;

if ($externalLinkTarget && $externalLinkTarget !== '' && $isExternalLink) {
$target = $externalLinkTarget;
}
if ($resourceLinkTarget !== '' && str_contains($linkHref, '_Resources')) {
if ($resourceLinkTarget && $resourceLinkTarget !== '' && str_contains($linkHref, '_Resources')) {
$target = $resourceLinkTarget;
}
if ($target === null) {
return $linkText;
if ($isExternalLink && $setNoOpener) {
$linkText = self::setAttribute('rel', 'noopener', $linkText);
}
// todo merge with "rel" attribute if already existent
$relValue = $isExternalLink && $setNoOpener ? 'noopener ' : '';
$relValue .= $isExternalLink && $setExternal ? 'external' : '';
$relValue = ltrim($relValue);
if (str_contains($linkText, 'target="')) {
// todo shouldn't we merge the current target value
return preg_replace(
'/target="[^"]*"/',
sprintf('target="%s"%s', $target, $relValue ? sprintf(' rel="%s"', $relValue) : $relValue),
$linkText
);
if ($isExternalLink && $setExternal) {
$linkText = self::setAttribute('rel', 'external', $linkText);
}
return str_replace(
'<a',
sprintf('<a target="%s"%s', $target, $relValue ? sprintf(' rel="%s"', $relValue) : $relValue),
$linkText
);
if (is_string($target) && $target !== '') {
return self::setAttribute('target', $target, $linkText);
}
return $linkText;
},
$processedContent
) ?: '';
);
return $processedContent;
}


/**
* Set or add value to the a attribute
*
* @param string $attribute The attribute, ('target' or 'rel')
* @param string $value The value of the attribute to add
* @param string $content The content to parse
* @return string
*/
private static function setAttribute(string $attribute, string $value, string $content): string
{
// The attribute is already set
if (\preg_match_all('~\s+' . $attribute . '="(.*?)~i', $content, $matches)) {
// If the attribute is target or the value is already set, leave the attribute as it is
if ($attribute === 'target' || \preg_match('~' . $attribute . '=".*?' . $value . '.*?"~i', $content)) {
return $content;
}
// Add the attribute to the list
return \preg_replace('/' . $attribute . '="(.*?)"/', sprintf('%s="$1 %s"', $attribute, $value), $content);
}

// Add the missing attribute with the value
return \str_replace('<a', sprintf('<a %s="%s"', $attribute, $value), $content);
}
}

0 comments on commit 94320b7

Please sign in to comment.