<?php
namespace Newland\NeosCommon\LinkHandler;

/*
 * This file is part of the "neos-common" package.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 */

use Neos\Flow\Annotations as Flow;
use Neos\Neos\Domain\Exception;
use Neos\ContentRepository\Domain\Model\NodeInterface;
use Newland\NeosCommon\LinkHandler\LinkingService;

/**
 * A Fusion Object that converts link references in the format "<type>://<UUID>" to proper URIs
 *
 * Right now node://<UUID> and asset://<UUID> are supported URI schemes.
 * Additionally record://<recordType>:<recordId> will be converted to a link to the detail view of that record.
 *
 * Usage::
 *
 *   someTextProperty.@process.1 = Neos.Neos:ConvertUris
 *
 * The optional property ``forceConversion`` can be used to have the links converted even when not
 * rendering the live workspace. This is used for links that are not inline editable (for
 * example links on images)::
 *
 *   someTextProperty.@process.1 = Neos.Neos:ConvertUris {
 *     forceConversion = true
 *   }
 *
 * The optional property ``externalLinkTarget`` can be modified to disable or change the target attribute of the
 * link tag for links to external targets::
 *
 *   prototype(Neos.Neos:ConvertUris) {
 *     externalLinkTarget = '_blank'
 *     resourceLinkTarget = '_blank'
 *   }
 *
 * The optional property ``absolute`` can be used to convert node uris to absolute links::
 *
 *   someTextProperty.@process.1 = Neos.Neos:ConvertUris {
 *     absolute = true
 *   }
 */
class ConvertUrisImplementation extends \Neos\Neos\Fusion\ConvertUrisImplementation
{
    /**
     * @Flow\Inject()
     * @var LinkingService
     */
    protected $linkingService;

    /**
     * Convert URIs matching a supported scheme with generated URIs
     *
     * If the workspace of the current node context is not live, no replacement will be done unless forceConversion is
     * set. This is needed to show the editable links with metadata in the content module.
     *
     * @return string
     * @throws Exception
     */
    public function evaluate()
    {
        $text = $this->fusionValue('value');

        if ($text === '' || $text === null) {
            return '';
        }

        if (!is_string($text)) {
            throw new Exception(
                sprintf('Only strings can be processed by this Fusion object, given: "%s".', gettype($text)),
                1382624080
            );
        }

        $node = $this->fusionValue('node');

        if (!$node instanceof NodeInterface) {
            throw new Exception(
                sprintf('The current node must be an instance of NodeInterface, given: "%s".', gettype($text)),
                1382624087
            );
        }

        if ($node->getContext()->getWorkspace()->getName() !== 'live' && !($this->fusionValue('forceConversion'))) {
            return $text;
        }

        $unresolvedUris = [];
        $linkingService = $this->linkingService;
        $controllerContext = $this->runtime->getControllerContext();

        $absolute = $this->fusionValue('absolute');

        $processedContent = (string) preg_replace_callback(
            LinkingService::PATTERN_SUPPORTED_URIS,
            function (array $matches) use ($node, $linkingService, $controllerContext, &$unresolvedUris, $absolute) {
                switch ($matches[1]) {
                    case 'node':
                        $resolvedUri = $linkingService->resolveNodeUri(
                            $matches[0],
                            $node,
                            $controllerContext,
                            $absolute
                        );
                        $this->runtime->addCacheTag('node', $matches[2]);
                        break;
                    case 'asset':
                        $resolvedUri = $linkingService->resolveAssetUri($matches[0]);
                        $this->runtime->addCacheTag('asset', $matches[2]);
                        break;
                    case 'record':
                        $resolvedUri = $linkingService->resolveRecordUri(
                            $matches[0],
                            $node,
                            $controllerContext,
                            $absolute
                        );
                        $this->runtime->addCacheTag('record', $matches[2]);
                        break;
                    default:
                        $resolvedUri = null;
                }

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

                return $resolvedUri;
            },
            $this->linkingService->nodeUriWorkaround($text)
        );

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

        $processedContent = $this->replaceLinkTargets($processedContent);

        return $processedContent;
    }
}
