[FEATURE] Add LinkService and "t3://" linking syntax 94/47094/23
authorBenni Mack <benni@typo3.org>
Sat, 5 Mar 2016 12:46:01 +0000 (13:46 +0100)
committerAndreas Fernandez <typo3@scripting-base.de>
Wed, 10 Aug 2016 07:09:42 +0000 (09:09 +0200)
A new and unified syntax to link to pages, files, folders
within a TYPO3 instance is added. All links are prefixed
with "t3://".

This patch adds a LinkService to convert from the "old"
typolink syntax to the new syntax, and also allows to add
the new syntax to typolink syntax.

The further steps are
* Don't replace the <a> tags with <links-tags inside the RTE transformations anymore
* Make the frontend lib.parseFunc_RTE deal with <a> tags directly
* Add a migration wizard for existing content

Resolves: #74365
Releases: master
Change-Id: I95a68c5770f4e4c5d9f8b3973817f1a270353217
Reviewed-on: https://review.typo3.org/47094
Reviewed-by: Frank Naegler <frank.naegler@typo3.org>
Tested-by: Frank Naegler <frank.naegler@typo3.org>
Tested-by: Bamboo TYPO3com <info@typo3.com>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
Reviewed-by: Andreas Fernandez <typo3@scripting-base.de>
Tested-by: Andreas Fernandez <typo3@scripting-base.de>
33 files changed:
typo3/sysext/backend/Classes/Controller/LinkBrowserController.php
typo3/sysext/backend/Classes/Tree/View/ElementBrowserPageTreeView.php
typo3/sysext/backend/Resources/Public/JavaScript/FormEngineLinkBrowserAdapter.js
typo3/sysext/core/Classes/LinkHandling/EmailLinkHandler.php [new file with mode: 0644]
typo3/sysext/core/Classes/LinkHandling/FileLinkHandler.php [new file with mode: 0644]
typo3/sysext/core/Classes/LinkHandling/FolderLinkHandler.php [new file with mode: 0644]
typo3/sysext/core/Classes/LinkHandling/LegacyLinkNotationConverter.php [new file with mode: 0644]
typo3/sysext/core/Classes/LinkHandling/LinkHandlingInterface.php [new file with mode: 0644]
typo3/sysext/core/Classes/LinkHandling/LinkService.php [new file with mode: 0644]
typo3/sysext/core/Classes/LinkHandling/PageLinkHandler.php [new file with mode: 0644]
typo3/sysext/core/Classes/LinkHandling/UrlLinkHandler.php [new file with mode: 0644]
typo3/sysext/core/Configuration/DefaultConfiguration.php
typo3/sysext/core/Documentation/Changelog/master/Feature-74365-LinkServiceForUnifiedReferencingSyntax.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/LinkHandling/EmailLinkHandlerTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/LinkHandling/FileLinkHandlerTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/LinkHandling/FolderLinkHandlerTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/LinkHandling/LegacyLinkNotationConverterTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/LinkHandling/LinkServiceTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/LinkHandling/PageLinkHandlerTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/LinkHandling/UrlLinkHandlerTest.php [new file with mode: 0644]
typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php
typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php
typo3/sysext/recordlist/Classes/LinkHandler/FileLinkHandler.php
typo3/sysext/recordlist/Classes/LinkHandler/FolderLinkHandler.php
typo3/sysext/recordlist/Classes/LinkHandler/MailLinkHandler.php
typo3/sysext/recordlist/Classes/LinkHandler/PageLinkHandler.php
typo3/sysext/recordlist/Classes/LinkHandler/UrlLinkHandler.php
typo3/sysext/recordlist/Resources/Private/Templates/LinkBrowser/File.html
typo3/sysext/recordlist/Resources/Private/Templates/LinkBrowser/Folder.html
typo3/sysext/recordlist/Resources/Private/Templates/LinkBrowser/Page.html
typo3/sysext/recordlist/Resources/Public/JavaScript/FileLinkHandler.js
typo3/sysext/recordlist/Resources/Public/JavaScript/PageLinkHandler.js
typo3/sysext/rtehtmlarea/Classes/Controller/BrowseLinksController.php

index f2de493..7c34e91 100644 (file)
@@ -17,6 +17,7 @@ namespace TYPO3\CMS\Backend\Controller;
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\LinkHandling\LinkService;
 use TYPO3\CMS\Core\Page\PageRenderer;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Frontend\Service\TypoLinkCodecService;
@@ -39,6 +40,14 @@ class LinkBrowserController extends AbstractLinkBrowserController
         $currentLinkParts['params'] = $currentLinkParts['additionalParams'];
         unset($currentLinkParts['additionalParams']);
 
+        if (!empty($currentLinkParts['url'])) {
+            $linkService = GeneralUtility::makeInstance(LinkService::class);
+            $data = $linkService->resolve($currentLinkParts['url']);
+            $currentLinkParts['type'] = $data['type'];
+            unset($data['type']);
+            $currentLinkParts['url'] = $data;
+        }
+
         $this->currentLinkParts = $currentLinkParts;
 
         parent::initCurrentUrl();
index a3ea73c..9e4fd1c 100644 (file)
@@ -14,6 +14,7 @@ namespace TYPO3\CMS\Backend\Tree\View;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\LinkHandling\LinkService;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Frontend\Page\PageRepository;
 use TYPO3\CMS\Recordlist\Tree\View\LinkParameterProviderInterface;
@@ -75,7 +76,8 @@ class ElementBrowserPageTreeView extends BrowseTreeView
     public function wrapTitle($title, $v, $ext_pArrPages = false)
     {
         if ($this->ext_isLinkable($v['doktype'], $v['uid'])) {
-            return '<span class="list-tree-title"><a href="#" class="t3js-pageLink" data-id="' . (int)$v['uid'] . '">' . $title . '</a></span>';
+            $url = GeneralUtility::makeInstance(LinkService::class)->asString(['type' => LinkService::TYPE_PAGE, 'pageuid' => (int)$v['uid']]);
+            return '<span class="list-tree-title"><a href="' . htmlspecialchars($url) . '" class="t3js-pageLink">' . $title . '</a></span>';
         } else {
             return '<span class="list-tree-title text-muted">' . $title . '</span>';
         }
index 440d240..7a52778 100644 (file)
@@ -50,22 +50,6 @@ define(['jquery', 'TYPO3/CMS/Recordlist/LinkBrowser'], function($, LinkBrowser)
                var field = FormEngineLinkBrowserAdapter.checkReference();
                if (field) {
                        var attributeValues = LinkBrowser.getLinkAttributeValues();
-
-                       // remove page: prefix from page links
-                       if (input.indexOf('page:') === 0) {
-                               input = input.substr(5);
-                       }
-
-                       // remove the mailto: prefix from mail links
-                       if (input.indexOf('mailto:') === 0) {
-                               input = input.substr(7);
-                       }
-
-                       // remove http:// schema for external links
-                       if (attributeValues['data-htmlarea-external'] && input.substr(0, 7) === "http://") {
-                               input = input.substr(7);
-                       }
-
                        // encode link on server
                        attributeValues.url = input;
 
diff --git a/typo3/sysext/core/Classes/LinkHandling/EmailLinkHandler.php b/typo3/sysext/core/Classes/LinkHandling/EmailLinkHandler.php
new file mode 100644 (file)
index 0000000..2346607
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\LinkHandling;
+
+/*
+ * 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!
+ */
+
+/**
+ * Resolves emails
+ */
+class EmailLinkHandler implements LinkHandlingInterface
+{
+
+    /**
+     * Returns the link to an email as a string
+     *
+     * @param array $parameters
+     * @return string
+     */
+    public function asString(array $parameters): string
+    {
+        return 'mailto:' . $parameters['email'];
+    }
+
+    /**
+     * Returns the email address without the "mailto:" prefix
+     * in the 'email' property of the array.
+     *
+     * @param array $data
+     * @return array
+     */
+    public function resolveHandlerData(array $data): array
+    {
+        return ['email' => substr($data['email'], 7)];
+    }
+}
diff --git a/typo3/sysext/core/Classes/LinkHandling/FileLinkHandler.php b/typo3/sysext/core/Classes/LinkHandling/FileLinkHandler.php
new file mode 100644 (file)
index 0000000..165094c
--- /dev/null
@@ -0,0 +1,92 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\LinkHandling;
+
+/*
+ * 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!
+ */
+use TYPO3\CMS\Core\Resource\ResourceFactory;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Interface for classes which are transforming a tag link hrefs for folders, in order to
+ * use FAL to store them in database, which means that files can be moved in the fileadmin
+ * without breaking file links in the frontend/backend
+ */
+class FileLinkHandler implements LinkHandlingInterface
+{
+
+    /**
+     * The Base URN
+     * @var string
+     */
+    protected $baseUrn = 't3://file';
+
+    /**
+     * The resource factory object to resolve file objects
+     * @var ResourceFactory
+     */
+    protected $resourceFactory;
+
+    /**
+     * Returns the link to a file as a string
+     *
+     * @param array $parameters
+     * @return string
+     */
+    public function asString(array $parameters): string
+    {
+        if ($parameters['file'] === null) {
+            return '';
+        }
+        $uid = $parameters['file']->getUid();
+        // I am not sure about this use case. Maybe if the file was not indexed and saved to DB (migration from old systems)
+        if ($uid > 0) {
+            $urn = '?uid=' . $uid;
+        } else {
+            $identifier = $parameters['file']->getIdentifier();
+            $urn = '?identifier=' . urlencode($identifier);
+        }
+
+        return $this->baseUrn . $urn;
+    }
+
+    /**
+     * Get a file object inside the array data from the string
+     *
+     * @param array $data with the "file" property containing a File object
+     * @return array
+     * @throws \TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException
+     */
+    public function resolveHandlerData(array $data): array
+    {
+        if (isset($data['uid'])) {
+            $fileId = $data['uid'];
+        } else {
+            $fileId = $data['identifier'];
+        }
+        return ['file' => $this->getResourceFactory()->getFileObject($fileId)];
+    }
+
+    /**
+     * Initializes the resource factory (only once)
+     *
+     * @return ResourceFactory
+     */
+    protected function getResourceFactory(): ResourceFactory
+    {
+        if (!$this->resourceFactory) {
+            $this->resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
+        }
+        return $this->resourceFactory;
+    }
+}
diff --git a/typo3/sysext/core/Classes/LinkHandling/FolderLinkHandler.php b/typo3/sysext/core/Classes/LinkHandling/FolderLinkHandler.php
new file mode 100644 (file)
index 0000000..dd76075
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\LinkHandling;
+
+/*
+ * 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!
+ */
+use TYPO3\CMS\Core\Resource\ResourceFactory;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Interface for classes which are transforming a tag link hrefs for folders, in order to
+ * use FAL to store them in database, which means that folders can be moved in the fileadmin
+ * without breaking folder links in the frontend/backend
+ */
+class FolderLinkHandler implements LinkHandlingInterface
+{
+
+    /**
+     * The Base URN
+     * @var string
+     */
+    protected $baseUrn = 't3://folder';
+
+    /**
+     * The resource factory to resolve
+     * @var \TYPO3\CMS\Core\Resource\ResourceFactory
+     */
+    protected $resourceFactory;
+
+    /**
+     * Returns a link notation to a folder
+     *
+     * @param array $parameters
+     *
+     * @return string
+     */
+    public function asString(array $parameters): string
+    {
+        // the magic with prepending slash if it is missing will not work on windows
+        return $this->baseUrn . '?storage=' . $parameters['folder']->getStorage()->getUid() .
+        '&identifier=' . urlencode('/' . ltrim($parameters['folder']->getIdentifier(), '/'));
+    }
+
+    /**
+     * Get a folder object inside the array data from the string
+     *
+     * @param array $data with the "folder" property containing a Folder object
+     *
+     * @return array
+     */
+    public function resolveHandlerData(array $data): array
+    {
+        $combinedIdentifier = ($data['storage'] ?? '0') . ':' . $data['identifier'];
+        return ['folder' => $this->getResourceFactory()->getFolderObjectFromCombinedIdentifier($combinedIdentifier)];
+    }
+
+    /**
+     * Initializes the resource factory (only once)
+     *
+     * @return ResourceFactory
+     */
+    protected function getResourceFactory(): ResourceFactory
+    {
+        if (!$this->resourceFactory) {
+            $this->resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
+        }
+        return $this->resourceFactory;
+    }
+}
diff --git a/typo3/sysext/core/Classes/LinkHandling/LegacyLinkNotationConverter.php b/typo3/sysext/core/Classes/LinkHandling/LegacyLinkNotationConverter.php
new file mode 100644 (file)
index 0000000..cf5c150
--- /dev/null
@@ -0,0 +1,218 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\LinkHandling;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
+use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Core\Resource\Folder;
+use TYPO3\CMS\Core\Resource\ResourceFactory;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\MathUtility;
+
+/**
+ * Class to resolve and convert the "old" link information (email, external url, file, page etc)
+ * to a URL or new format for migration
+ *
+ * @internal
+ */
+class LegacyLinkNotationConverter
+{
+
+    /**
+     * @var ResourceFactory
+     */
+    protected $resourceFactory;
+
+    /**
+     * Part of the typolink construction functionality, called by typoLink()
+     * Used to resolve "legacy"-based typolinks.
+     *
+     * Tries to get the type of the link from the link parameter
+     * could be
+     *  - "mailto" an email address
+     *  - "url" external URL
+     *  - "file" a local file (checked AFTER getPublicUrl() is called)
+     *  - "page" a page (integer or alias)
+     *
+     * Does NOT check if the page exists or the file exists.
+     *
+     * @param string $linkParameter could be "fileadmin/myfile.jpg", "info@typo3.org", "13" or "http://www.typo3.org"
+     *
+     * @return array
+     */
+    public function resolve(string $linkParameter): array
+    {
+        $result = [];
+        // Parse URL scheme
+        $scheme = parse_url($linkParameter, PHP_URL_SCHEME);
+
+        // Resolve FAL-api "file:UID-of-sys_file-record" and "file:combined-identifier"
+        if (stripos($linkParameter, 'file:') === 0) {
+            $result = $this->getFileOrFolderObjectFromMixedIdentifier(substr($linkParameter, 5));
+        } elseif (GeneralUtility::validEmail($linkParameter)) {
+            $result['type'] = LinkService::TYPE_EMAIL;
+            $result['email'] = $linkParameter;
+        } elseif (strpos($linkParameter, ':') !== false) {
+            // Check for link-handler keyword
+            list($linkHandlerKeyword, $linkHandlerValue) = explode(':', $linkParameter, 2);
+            $result['type'] = strtolower(trim($linkHandlerKeyword));
+            $result['url'] = $linkHandlerValue;
+        } else {
+            // special handling without a scheme
+            $isLocalFile = 0;
+            $fileChar = (int)strpos($linkParameter, '/');
+            $urlChar = (int)strpos($linkParameter, '.');
+
+            $containsSlash = false;
+            if (!MathUtility::canBeInterpretedAsInteger($linkParameter)) {
+                // Detects if a file is found in site-root and if so it will be treated like a normal file.
+                list($rootFileDat) = explode('?', rawurldecode($linkParameter));
+                $containsSlash = strpos($rootFileDat, '/') !== false;
+                $pathInfo = pathinfo($rootFileDat);
+                $fileExtension = strtolower(($pathInfo['extension'] ?? ''));
+                if (!$containsSlash
+                    && trim($rootFileDat)
+                    && (
+                        @is_file(PATH_site . $rootFileDat)
+                        || $fileExtension === 'php'
+                        || $fileExtension === 'html'
+                        || $fileExtension === 'htm'
+                    )
+                ) {
+                    $isLocalFile = 1;
+                } elseif ($containsSlash) {
+                    // Adding this so realurl directories are linked right (non-existing).
+                    $isLocalFile = 2;
+                }
+            }
+
+            // url (external): If doubleSlash or if a '.' comes before a '/'.
+            if ($isLocalFile !== 1 && $urlChar && (!$containsSlash || $urlChar < $fileChar)) {
+                $result['type'] = LinkService::TYPE_URL;
+                if (!$scheme) {
+                    $result['url'] = 'http://' . $linkParameter;
+                } else {
+                    $result['url'] = $linkParameter;
+                }
+                // file (internal) or folder
+            } elseif ($containsSlash || $isLocalFile) {
+                $result = $this->getFileOrFolderObjectFromMixedIdentifier($linkParameter);
+            } else {
+                // Integer or alias (alias is without slashes or periods or commas, that is
+                // 'nospace,alphanum_x,lower,unique' according to definition in $GLOBALS['TCA']!)
+                $result = $this->resolvePageRelatedParameters($linkParameter);
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * Internal method to do some magic to get a page parts, additional params, fragment / section hash
+     *
+     * @param string $data the input variable, can be "mypage,23" with fragments, keys
+     *
+     * @return array the result array with the page type set
+     */
+    protected function resolvePageRelatedParameters(string $data): array
+    {
+        $result = ['type' => LinkService::TYPE_PAGE];
+        if (strpos($data, '#') !== false) {
+            list($data, $result['fragment']) = explode('#', $data, 2);
+        }
+        // check for additional parameters
+        if (strpos($data, '?') !== false) {
+            list($data, $result['parameters']) = explode('?', $data, 2);
+        } elseif (strpos($data, '&') !== false) {
+            list($data, $result['parameters']) = explode('&', $data, 2);
+        }
+        if (empty($data)) {
+            $result['pageuid'] = 'current';
+        } elseif ($data{0} === '#') {
+            $result['pageuid'] = 'current';
+            $result['fragment'] = substr($data, 1);
+        } elseif (strpos($data, ',') !== false) {
+            list($result['pageuid'], $result['pagetype']) = explode(',', $data, 2);
+        } elseif (strpos($data, '/') !== false) {
+            $data = explode('/', trim($data, '/'));
+            $result['pageuid'] = array_shift($data);
+            foreach ($data as $k => $item) {
+                if ($data[$k] % 2 === 0 && !empty($data[$k + 1])) {
+                    $result['page' . $data[$k]] = $data[$k + 1];
+                }
+            }
+        } else {
+            $result['pageuid'] = $data;
+        }
+
+        // expect an alias
+        if (!MathUtility::canBeInterpretedAsInteger($result['pageuid']) && $result['pageuid'] !== 'current') {
+            $result['pagealias'] = $result['pageuid'];
+            unset($result['pageuid']);
+        }
+
+        return $result;
+    }
+
+    /**
+     * Internal method that fetches a file or folder object based on the file or folder combined identifier
+     *
+     * @param string $mixedIdentifier can be something like "2" (file uid), "fileadmin/i/like.png" or "2:/myidentifier/"
+     *
+     * @return array the result with the type (file or folder) set
+     */
+    protected function getFileOrFolderObjectFromMixedIdentifier(string $mixedIdentifier): array
+    {
+        $result = [];
+        try {
+            $fileOrFolderObject = $this->getResourceFactory()->retrieveFileOrFolderObject($mixedIdentifier);
+            // Link to a folder or file
+            if ($fileOrFolderObject instanceof File) {
+                $result['type'] = LinkService::TYPE_FILE;
+                $result['file'] = $fileOrFolderObject;
+            } elseif ($fileOrFolderObject instanceof Folder) {
+                $result['type'] = LinkService::TYPE_FOLDER;
+                $result['folder'] = $fileOrFolderObject;
+            } else {
+                $result['type'] = LinkService::TYPE_UNKNOWN;
+                $result['file'] = $mixedIdentifier;
+            }
+        } catch (\RuntimeException $e) {
+            // Element wasn't found
+            $result['type'] = LinkService::TYPE_UNKNOWN;
+            $result['file'] = $mixedIdentifier;
+        } catch (ResourceDoesNotExistException $e) {
+            // Resource was not found
+            $result['type'] = LinkService::TYPE_UNKNOWN;
+            $result['file'] = $mixedIdentifier;
+        }
+
+        return $result;
+    }
+
+    /**
+     * Initializes the resource factory (only once)
+     *
+     * @return ResourceFactory
+     */
+    protected function getResourceFactory(): ResourceFactory
+    {
+        if (!$this->resourceFactory) {
+            $this->resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
+        }
+        return $this->resourceFactory;
+    }
+}
diff --git a/typo3/sysext/core/Classes/LinkHandling/LinkHandlingInterface.php b/typo3/sysext/core/Classes/LinkHandling/LinkHandlingInterface.php
new file mode 100644 (file)
index 0000000..786ecd0
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\LinkHandling;
+
+/*
+ * 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!
+ */
+
+/**
+ * Interface for classes which are transforming a tag link hrefs to records or resources
+ * basically any URLs that should not be saved directly in the database on as is basis
+ * since they might be moved, changed by admin working in backend
+ */
+interface LinkHandlingInterface
+{
+
+    /**
+     * Returns a string interpretation of the link href query from objects, something like
+     *
+     *  - t3://page?uid=23&my=value#cool
+     *  - https://www.typo3.org/
+     *  - t3://file?uid=13
+     *  - t3://folder?storage=2&identifier=/my/folder/
+     *  - mailto:mac@safe.com
+     *
+     * array of data -> string
+     *
+     * @param array $parameters
+     * @return string
+     */
+    public function asString(array $parameters): string;
+
+    /**
+     * Returns a array with data interpretation of the link href from parsed query parameters of urn
+     * representation.
+     *
+     * array of strings -> array of data
+     *
+     * @param array $data
+     * @return array
+     */
+    public function resolveHandlerData(array $data): array;
+}
diff --git a/typo3/sysext/core/Classes/LinkHandling/LinkService.php b/typo3/sysext/core/Classes/LinkHandling/LinkService.php
new file mode 100644 (file)
index 0000000..9b9444b
--- /dev/null
@@ -0,0 +1,148 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\LinkHandling;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Core\SingletonInterface;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Class LinkService, responsible to find what kind of resource (type) is used
+ * to link to (email, external url, file, page etc)
+ * with the possibility to get a system-wide understandable "urn" to identify
+ * what type it actually is, based on the scheme or prefix.
+ */
+class LinkService implements SingletonInterface
+{
+
+    const TYPE_PAGE = 'page';
+    const TYPE_URL = 'url';
+    const TYPE_EMAIL = 'email';
+    const TYPE_FILE = 'file';
+    const TYPE_FOLDER = 'folder';
+    const TYPE_UNKNOWN = 'unknown';
+
+    /**
+     * All registered LinkHandlers
+     *
+     * @var LinkHandlingInterface[]
+     */
+    protected $handlers;
+
+    /**
+     * LinkService constructor initializes the registered handlers.
+     */
+    public function __construct()
+    {
+        if (!empty($GLOBALS['TYPO3_CONF_VARS']['SYS']['linkHandler'])) {
+            foreach ($GLOBALS['TYPO3_CONF_VARS']['SYS']['linkHandler'] as $type => $handler) {
+                if (!is_object($this->handlers[$type])) {
+                    $this->handlers[$type] = GeneralUtility::makeInstance($handler);
+                }
+            }
+        }
+    }
+
+    /**
+     * Part of the typolink construction functionality, called by typoLink()
+     * Used to resolve "legacy"-based typolinks and URNs.
+     *
+     * Tries to get the type of the link from the link parameter
+     * could be
+     *  - "mailto" an email address
+     *  - "url" external URL
+     *  - "file" a local file (checked AFTER getPublicUrl() is called)
+     *  - "page" a page (integer or alias)
+     *
+     * Does NOT check if the page exists or the file exists.
+     *
+     * @param string $linkParameter could be "fileadmin/myfile.jpg", "info@typo3.org", "13" or "http://www.typo3.org"
+     * @return array
+     */
+    public function resolve(string $linkParameter): array
+    {
+        try {
+            // Check if the new syntax with "t3://" is used
+            return $this->resolveByStringRepresentation($linkParameter);
+        } catch (\InvalidArgumentException $e) {
+            $legacyLinkNotationConverter = GeneralUtility::makeInstance(LegacyLinkNotationConverter::class);
+            return $legacyLinkNotationConverter->resolve($linkParameter);
+        }
+    }
+
+    /**
+     * Returns a array with data interpretation of the link target, something like t3:blabla.
+     *
+     * @param string $urn
+     * @return array
+     */
+    public function resolveByStringRepresentation(string $urn): array
+    {
+        // linking to any t3:// syntax
+        if (stripos($urn, 't3://') === 0) {
+            // lets parse the urn
+            $urnParsed = parse_url($urn);
+            $type = $urnParsed['host'];
+            if (isset($urnParsed['query'])) {
+                parse_str($urnParsed['query'], $data);
+            } else {
+                $data = [];
+            }
+            $fragment = $urnParsed['fragment'] ?? null;
+
+            if (is_object($this->handlers[$type])) {
+                $result = $this->handlers[$type]->resolveHandlerData($data);
+                $result['type'] = $type;
+            } else {
+                throw new \InvalidArgumentException('LinkHandler for ' . $type . ' was not registered', 1460581769);
+            }
+            // this was historically named "section"
+            if ($fragment) {
+                $result['fragment'] = $fragment;
+            }
+        } elseif (stripos($urn, '://') && $this->handlers[self::TYPE_URL]) {
+            $result = $this->handlers[self::TYPE_URL]->resolveHandlerData(['url' => $urn]);
+            $result['type'] = self::TYPE_URL;
+        } elseif (stripos($urn, 'mailto:') === 0 && $this->handlers[self::TYPE_EMAIL]) {
+            $result = $this->handlers[self::TYPE_EMAIL]->resolveHandlerData(['email' => $urn]);
+            $result['type'] = self::TYPE_EMAIL;
+        } else {
+            throw new \InvalidArgumentException('No valid URN to resolve found', 1457177667);
+        }
+
+        return $result;
+    }
+
+    /**
+     * Returns a string interpretation of the link target, something like
+     *
+     *  - t3://page?uid=23&my=value#cool
+     *  - https://www.typo3.org/
+     *  - t3://file?uid=13
+     *  - t3://folder?storage=2&identifier=/my/folder/
+     *  - mailto:mac@safe.com
+     *
+     * @param array $parameters
+     * @return string
+     * @throws \InvalidArgumentException
+     */
+    public function asString(array $parameters): string
+    {
+        if (is_object($this->handlers[$parameters['type']])) {
+            return $this->handlers[$parameters['type']]->asString($parameters);
+        }
+        throw new \InvalidArgumentException('No valid handlers found for type: ' . $parameters['type'], 1460629247);
+    }
+}
diff --git a/typo3/sysext/core/Classes/LinkHandling/PageLinkHandler.php b/typo3/sysext/core/Classes/LinkHandling/PageLinkHandler.php
new file mode 100644 (file)
index 0000000..9c626cc
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\LinkHandling;
+
+/*
+ * 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!
+ */
+
+/**
+ * Resolves links to pages and the parameters given
+ */
+class PageLinkHandler implements LinkHandlingInterface
+{
+
+    /**
+     * The Base URN for this link handling to act on
+     * @var string
+     */
+    protected $baseUrn = 't3://page';
+
+    /**
+     * Returns all valid parameters for linking to a TYPO3 page as a string
+     *
+     * @param array $parameters
+     * @return string
+     */
+    public function asString(array $parameters): string
+    {
+        $urn = $this->baseUrn;
+        if (isset($parameters['pagealias']) && $parameters['pagealias'] !== 'current') {
+            $urn .= '?alias=' . $parameters['pagealias'];
+        } else {
+            $urn .= '?uid=' . $parameters['pageuid'];
+        }
+        $urn = rtrim($urn, ':');
+        if (!empty($parameters['pagetype'])) {
+            $urn .= '&type=' . $parameters['pagetype'];
+        }
+        if (!empty($parameters['parameters'])) {
+            $urn .= '&' . ltrim($parameters['parameters'], '?&');
+        }
+        if (!empty($parameters['fragment'])) {
+            $urn .= '#' . $parameters['fragment'];
+        }
+
+        return $urn;
+    }
+
+    /**
+     * Returns all relevant information built in the link to a page (see asString())
+     *
+     * @param array $data
+     * @return array
+     */
+    public function resolveHandlerData(array $data): array
+    {
+        $result = [];
+        if (isset($data['uid'])) {
+            $result['pageuid'] = $data['uid'];
+            unset($data['uid']);
+        }
+        if (isset($data['alias'])) {
+            $result['pagealias'] = $data['alias'];
+            unset($data['alias']);
+        }
+        if (isset($data['type'])) {
+            $result['pagetype'] = $data['type'];
+            unset($data['type']);
+        }
+        if (!empty($data)) {
+            $result['parameters'] = http_build_query($data);
+        }
+        if (empty($result)) {
+            $result['pageuid'] = 'current';
+        }
+
+        return $result;
+    }
+}
diff --git a/typo3/sysext/core/Classes/LinkHandling/UrlLinkHandler.php b/typo3/sysext/core/Classes/LinkHandling/UrlLinkHandler.php
new file mode 100644 (file)
index 0000000..8a3ee7b
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+namespace TYPO3\CMS\Core\LinkHandling;
+
+/*
+ * 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!
+ */
+
+/**
+ * Resolves URLs (simple, no magic needed)
+ */
+class UrlLinkHandler implements LinkHandlingInterface
+{
+
+    /**
+     * Returns the URL as given
+     *
+     * @param array $parameters
+     * @return mixed
+     */
+    public function asString(array $parameters): string
+    {
+        return $this->addHttpSchemeAsFallback($parameters['url']);
+    }
+
+    /**
+     * Returns the URL as is
+     *
+     * @param array $data (needs 'url') inside
+     * @return array
+     */
+    public function resolveHandlerData(array $data): array
+    {
+        return ['url' => $this->addHttpSchemeAsFallback($data['url'])];
+    }
+
+    /**
+     * Ensures that a scheme is always added, if www.typo3.org was added previously
+     *
+     * @param string $url the URL
+     * @return string
+     */
+    protected function addHttpSchemeAsFallback(string $url): string
+    {
+        if (!empty($url)) {
+            $urlParts = parse_url($url);
+            if (empty($urlParts['scheme'])) {
+                $url = 'http://' . $url;
+            }
+        }
+        return $url;
+    }
+}
index ca89633..5c9c33d 100644 (file)
@@ -280,6 +280,13 @@ return array(
         'fluid' => array(
             'interceptors' => array(),
         ),
+        'linkHandler' => array( // Array: Available link types, class which implement the LinkHandling interface
+            'page'   => \TYPO3\CMS\Core\LinkHandling\PageLinkHandler::class,
+            'file'   => \TYPO3\CMS\Core\LinkHandling\FileLinkHandler::class,
+            'folder' => \TYPO3\CMS\Core\LinkHandling\FolderLinkHandler::class,
+            'url'    => \TYPO3\CMS\Core\LinkHandling\UrlLinkHandler::class,
+            'email'  => \TYPO3\CMS\Core\LinkHandling\EmailLinkHandler::class,
+        ),
         'livesearch' => array(),    // Array: keywords used for commands to search for specific tables
         'isInitialInstallationInProgress' => false,        // Boolean: If TRUE, the installation is 'in progress'. This value is handled within the install tool step installer internally.
         'isInitialDatabaseImportDone' => true,        // Boolean: If TRUE, the database import is finished. This value is handled within the install tool step installer internally.
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-74365-LinkServiceForUnifiedReferencingSyntax.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-74365-LinkServiceForUnifiedReferencingSyntax.rst
new file mode 100644 (file)
index 0000000..026a62a
--- /dev/null
@@ -0,0 +1,128 @@
+================================================================
+Feature: #74365 - Add Linkservice for unified referencing syntax
+================================================================
+
+Description
+===========
+
+Resources within TYPO3 have been referenced using multiple, different forms of syntax
+in the past.
+
+TYPO3 now supports a modern and future-proof way of referencing resources using an
+extensible and expressive syntax which is easy to understand.
+
+In order to understand the syntax, we will guide you through using a simple page
+link.
+
+``t3://page?uid=13&campaignCode=ABC123``
+
+The syntax consists of three main parts, separated by colon characters:
+
+Syntax Namespace (t3://)
+   The namespace is set to ``t3://`` to ensure the ``LinkService`` should be called to
+   parse the URN.
+   This value is fixed.
+
+Resource handler key (page)
+   The resource handler key is a list of available handlers that TYPO3 can work
+   with. At the time of writing these handlers are:
+
+   * page
+   * file
+   * folder
+
+   More keys can be added via $TYPO3_CONF_VARS['SYS']['linkHandler'] in an associative
+   array where the key is the handler key and the value is a class implementing
+   the LinkHandlerInterface.
+
+Resource parameters(?uid=13&campaignCode=ABC123)
+   These are the specific identification parameters that are used by any handler.
+   Note that these may carry additional parameters in order to configure the
+   behavior of any handler.
+
+Handler syntax
+==============
+
+page
+----
+
+The page identifier is a compound string based on several optional settings.
+
+uid
+   **int**:
+   The **uid** of a page record.
+
+   ``t3://page?uid=13``
+alias
+   **string**:
+   The **alias** of a page record (as an alternative to the UID).
+
+   ``t3://page?alias=myfunkyalias``
+type
+   **int** *(optional, prefixed with comma)*:
+
+   ``t3://page?uid=13&type=3`` will reference page 13 in type 3.
+parameters
+   **string** *(optional, prefixed with &)*:
+
+   ``t3://page?uid=1313&my=param&will=get&added=here``
+fragment
+   **string** *(optional, prefixed with #)*:
+
+   ``t3://page?alias=myfunkyalias#c123``
+
+   ``t3://page?uid=13&type=3#c123``
+
+   ``t3://page?uid=13&type3?my=param&will=get&added=here#c123``
+
+file
+----
+
+uid
+   **int**: The UID of a file within the FAL database table `sys_file`.
+   `t3://file?uid=13`
+
+identifier
+   **int**: The identifier of a file when not indexed in FAL.
+   `t3://file?identifier=myfile.jpg`
+
+folder
+------
+
+identifier
+   **string**: The identifier of a given folder.
+   `t3://folder?identifier=fileadmin`
+
+storage
+   **string**: The FAL storage to the given folder (optional).
+   `t3://folder?storage=1&identifier=myfolder`
+
+
+Examples:
+=========
+
+Linking to a page in RTE
+------------------------
+
+The old way of linking to a page in the RTE resulted in the following code in the
+database:
+``<link 13?campaignCode=ABC123 _blank class="linkMe" #c1234>Text</link>``
+
+The new way would be the following code in the database:
+``<a href="t3://page?uid=13&campaignCode=ABC123#c1234" target="_blank" class="linkMe">Text</a>``
+
+As you can see, the syntax is more in line with known markup, thus removing the
+demand of data processing from or to the RTE component.
+
+Referencing an image in RTE
+---------------------------
+
+``<img src="t3://file?uid=134&renderAs=png" width="200" height="200">``
+
+In this example we illustrate a **fictional** usecase of identifier configuration.
+
+Impact
+======
+
+Currently the impact is rather low, since a fallback mechanism will still be able to
+work with the old syntax.
diff --git a/typo3/sysext/core/Tests/Unit/LinkHandling/EmailLinkHandlerTest.php b/typo3/sysext/core/Tests/Unit/LinkHandling/EmailLinkHandlerTest.php
new file mode 100644 (file)
index 0000000..6bbba4b
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Unit\LinkHandling;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Core\LinkHandling\EmailLinkHandler;
+
+class EmailLinkHandlerTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
+{
+
+    /**
+     * Data to resolve strings to arrays and vice versa, external, mail, page
+     *
+     * @return array
+     */
+    public function resolveParametersForNonFilesDataProvider()
+    {
+        return [
+            'email with protocol' => [
+                [
+                    'email' => 'mailto:one@love.com'
+                ],
+                [
+                    'email' => 'one@love.com'
+                ],
+                'mailto:one@love.com'
+            ],
+            'email with protocol 2' => [
+                [
+                    'email' => 'mailto:info@typo3.org'
+                ],
+                [
+                    'email' => 'info@typo3.org'
+                ],
+                'mailto:info@typo3.org'
+            ],
+        ];
+    }
+
+    /**
+     * @test
+     *
+     * @param string $input
+     * @param array  $expected
+     * @param string $finalString
+     *
+     * @dataProvider resolveParametersForNonFilesDataProvider
+     */
+    public function resolveReturnsSplitParameters($input, $expected, $finalString)
+    {
+        $subject = new EmailLinkHandler();
+        $this->assertEquals($expected, $subject->resolveHandlerData($input));
+    }
+
+    /**
+     * @test
+     *
+     * @param string $input
+     * @param array  $parameters
+     * @param string $expected
+     *
+     * @dataProvider resolveParametersForNonFilesDataProvider
+     */
+    public function splitParametersToUnifiedIdentifier($input, $parameters, $expected)
+    {
+        $subject = new EmailLinkHandler();
+        $this->assertEquals($expected, $subject->asString($parameters));
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/LinkHandling/FileLinkHandlerTest.php b/typo3/sysext/core/Tests/Unit/LinkHandling/FileLinkHandlerTest.php
new file mode 100644 (file)
index 0000000..250ab60
--- /dev/null
@@ -0,0 +1,123 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Unit\LinkHandling;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Core\LinkHandling\FileLinkHandler;
+use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Core\Resource\ResourceFactory;
+use TYPO3\CMS\Core\Resource\ResourceStorage;
+use TYPO3\CMS\Core\Utility\MathUtility;
+
+class FileLinkHandlerTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
+{
+
+    /**
+     * testing folders
+     */
+
+    /**
+     * Data provider for pointing to files
+     * t3:file:1:myfolder/myidentifier.jpg
+     * t3:folder:1:myfolder
+     *
+     * @return array
+     */
+    public function resolveParametersForFilesDataProvider()
+    {
+        return [
+            'file without FAL - cool style' => [
+                [
+                    'identifier' => 'fileadmin/deep/down.jpg'
+                ],
+                [
+                    'file' => 'fileadmin/deep/down.jpg'
+                ],
+                't3://file?identifier=fileadmin%2Fdeep%2Fdown.jpg'
+            ],
+            'file with FAL uid - cool style' => [
+                [
+                    'uid' => 23
+                ],
+                [
+                    'file' => 23
+                ],
+                't3://file?uid=23'
+            ],
+        ];
+    }
+
+    /**
+     * Helpful to know in which if() clause the stuff gets in
+     *
+     * @test
+     *
+     * @param string $input
+     * @param array  $expected
+     * @param string $finalString
+     *
+     * @dataProvider resolveParametersForFilesDataProvider
+     */
+    public function resolveFileReferencesToSplitParameters($input, $expected, $finalString)
+    {
+        /** @var ResourceStorage|\PHPUnit_Framework_MockObject_MockObject $storageMock */
+        $storage = $this->getMockBuilder(ResourceStorage::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        /** @var ResourceFactory|\PHPUnit_Framework_MockObject_MockObject $storageMock */
+        $factory = $this->getMockBuilder(ResourceFactory::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        // fake methods to return proper objects
+        $fileObject = new File(['identifier' => $expected['file']], $storage);
+        $factory->expects($this->any())->method('getFileObject')->with($expected['file'])->willReturn($fileObject);
+        $expected['file'] = $fileObject;
+
+        /** @var FileLinkHandler|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface $subject */
+        $subject = $this->getAccessibleMock(FileLinkHandler::class, ['dummy']);
+        $subject->_set('resourceFactory', $factory);
+        $this->assertEquals($expected, $subject->resolveHandlerData($input));
+    }
+
+    /**
+     * Helpful to know in which if() clause the stuff gets in
+     *
+     * @test
+     *
+     * @param string $input
+     * @param array  $parameters
+     * @param string $expected
+     *
+     * @dataProvider resolveParametersForFilesDataProvider
+     */
+    public function splitParametersToUnifiedIdentifierForFiles($input, $parameters, $expected)
+    {
+        $fileObject = $this->getMockBuilder(File::class)
+            ->setMethods(['getUid', 'getIdentifier'])
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $uid = 0;
+        if (MathUtility::canBeInterpretedAsInteger($parameters['file'])) {
+            $uid = $parameters['file'];
+        }
+        $fileObject->expects($this->once())->method('getUid')->willReturn($uid);
+        $fileObject->expects($this->any())->method('getIdentifier')->willReturn($parameters['file']);
+        $parameters['file'] = $fileObject;
+
+        $subject = new FileLinkHandler();
+        $this->assertEquals($expected, $subject->asString($parameters));
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/LinkHandling/FolderLinkHandlerTest.php b/typo3/sysext/core/Tests/Unit/LinkHandling/FolderLinkHandlerTest.php
new file mode 100644 (file)
index 0000000..fb93550
--- /dev/null
@@ -0,0 +1,127 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Unit\LinkHandling;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Core\LinkHandling\FolderLinkHandler;
+use TYPO3\CMS\Core\Resource\Folder;
+use TYPO3\CMS\Core\Resource\ResourceFactory;
+use TYPO3\CMS\Core\Resource\ResourceStorage;
+
+class FolderLinkHandlerTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
+{
+
+    /**
+     * testing folders
+     */
+
+    /**
+     * Data provider for pointing to files
+     * t3:file:1:myfolder/myidentifier.jpg
+     * t3:folder:1:myfolder
+     *
+     * @return array
+     */
+    public function resolveParametersForFilesDataProvider()
+    {
+        return [
+            'folder without FAL - cool style' => [
+                [
+                    'storage' => 0,
+                    'identifier' => '/fileadmin/myimages/'
+                ],
+                [
+                    'folder' => '0:/fileadmin/myimages/'
+                ],
+                't3://folder?storage=0&identifier=%2Ffileadmin%2Fmyimages%2F'
+            ],
+            'folder with combined identifier and file prefix (FAL) - cool style' => [
+                [
+                    'storage' => 2,
+                    'identifier' => '/myimages/'
+                ],
+                [
+                    'folder' => '2:/myimages/'
+                ],
+                't3://folder?storage=2&identifier=%2Fmyimages%2F'
+            ],
+        ];
+    }
+
+    /**
+     * Helpful to know in which if() clause the stuff gets in
+     *
+     * @test
+     *
+     * @param string $input
+     * @param array  $expected
+     * @param string $finalString
+     *
+     * @dataProvider resolveParametersForFilesDataProvider
+     */
+    public function resolveFileReferencesToSplitParameters($input, $expected, $finalString)
+    {
+        $storage = $this->getMockBuilder(ResourceStorage::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $factory = $this->getMockBuilder(ResourceFactory::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        // fake methods to return proper objects
+        $folderObject = new Folder($storage, $expected['folder'], $expected['folder']);
+        $factory->expects($this->once())->method('getFolderObjectFromCombinedIdentifier')->with($expected['folder'])
+            ->willReturn($folderObject);
+        $expected['folder'] = $folderObject;
+
+        /** @var FolderLinkHandler|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface $subject */
+        $subject = $this->getAccessibleMock(FolderLinkHandler::class, ['dummy']);
+        $subject->_set('resourceFactory', $factory);
+        $this->assertEquals($expected, $subject->resolveHandlerData($input));
+    }
+
+    /**
+     * Helpful to know in which if() clause the stuff gets in
+     *
+     * @test
+     *
+     * @param string $input
+     * @param array  $parameters
+     * @param string $expected
+     *
+     * @dataProvider resolveParametersForFilesDataProvider
+     */
+    public function splitParametersToUnifiedIdentifierForFiles($input, $parameters, $expected)
+    {
+        $folderObject = $this->getMockBuilder(Folder::class)
+            ->setMethods(['getCombinedIdentifier', 'getStorage', 'getIdentifier'])
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $folderObject->expects($this->any())->method('getCombinedIdentifier')->willReturn($parameters['folder']);
+        $folderData = explode(':', $parameters['folder']);
+        /** @var ResourceStorage|\PHPUnit_Framework_MockObject_MockObject $storageMock */
+        $storage = $this->getMockBuilder(ResourceStorage::class)
+            ->disableOriginalConstructor()
+            ->getMock(array('getUid'));
+        $storage->method('getUid')->willReturn($folderData[0]);
+        $folderObject->expects($this->any())->method('getStorage')->willReturn($storage);
+        $folderObject->expects($this->any())->method('getIdentifier')->willReturn($folderData[1]);
+        $parameters['folder'] = $folderObject;
+
+        $subject = new FolderLinkHandler();
+        $this->assertEquals($expected, $subject->asString($parameters));
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/LinkHandling/LegacyLinkNotationConverterTest.php b/typo3/sysext/core/Tests/Unit/LinkHandling/LegacyLinkNotationConverterTest.php
new file mode 100644 (file)
index 0000000..9431b6c
--- /dev/null
@@ -0,0 +1,281 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Unit\LinkHandling;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Core\LinkHandling\LegacyLinkNotationConverter;
+use TYPO3\CMS\Core\LinkHandling\LinkService;
+use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Core\Resource\Folder;
+use TYPO3\CMS\Core\Resource\ResourceFactory;
+use TYPO3\CMS\Core\Resource\ResourceStorage;
+use TYPO3\CMS\Core\Utility\MathUtility;
+
+class LegacyLinkNotationConverterTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
+{
+    /**
+     * Data to resolve strings to arrays and vice versa, external, mail, page
+     *
+     * @return array
+     */
+    public function resolveParametersForNonFilesDataProvider()
+    {
+        return [
+            'simple page - old style' => [
+                // original input value
+                '13',
+                // splitted values
+                [
+                    'type' => LinkService::TYPE_PAGE,
+                    'pageuid' => 13
+                ],
+                // final unified URN
+                't3://page?uid=13'
+            ],
+            'page with type - old style' => [
+                '13,31',
+                [
+                    'type' => LinkService::TYPE_PAGE,
+                    'pageuid' => 13,
+                    'pagetype' => 31
+                ],
+                't3://page?uid=13&type=31'
+            ],
+            'page with type and fragment - old style' => [
+                '13,31#uncool',
+                [
+                    'type' => LinkService::TYPE_PAGE,
+                    'pageuid' => '13',
+                    'pagetype' => '31',
+                    'fragment' => 'uncool'
+                ],
+                't3://page?uid=13&type=31#uncool'
+            ],
+            'page with type and parameters and fragment - old style' => [
+                '13,31?unbel=ievable#uncool',
+                [
+                    'type' => LinkService::TYPE_PAGE,
+                    'pageuid' => '13',
+                    'pagetype' => '31',
+                    'parameters' => 'unbel=ievable',
+                    'fragment' => 'uncool'
+                ],
+                't3://page?uid=13&type=31&unbel=ievable#uncool'
+            ],
+            'page with alias - old style' => [
+                'alias13',
+                [
+                    'type' => LinkService::TYPE_PAGE,
+                    'pagealias' => 'alias13'
+                ],
+                't3://page?alias=alias13'
+            ]
+        ];
+    }
+
+    /**
+     * @test
+     *
+     * @param string $input
+     * @param array  $expected
+     * @param string $finalString
+     *
+     * @dataProvider resolveParametersForNonFilesDataProvider
+     */
+    public function resolveReturnsSplitParameters($input, $expected, $finalString)
+    {
+        $subject = new LegacyLinkNotationConverter();
+        $this->assertEquals($expected, $subject->resolve($input));
+    }
+
+    /**
+     * @test
+     *
+     * @param string $input
+     * @param array  $parameters
+     * @param string $expected
+     *
+     * @dataProvider resolveParametersForNonFilesDataProvider
+     */
+    public function splitParametersToUnifiedIdentifier($input, $parameters, $expected)
+    {
+        $subject = new LinkService();
+        $this->assertEquals($expected, $subject->asString($parameters));
+    }
+
+    /**
+     * testing files and folders
+     */
+
+    /**
+     * Data provider for pointing to files
+     * t3:file:15
+     * t3:file:fileadmin/deep/down.jpg
+     * t3:file:1:myfolder/myidentifier.jpg
+     * t3:folder:1:myfolder
+     *
+     * @return array
+     */
+    public function resolveParametersForFilesDataProvider()
+    {
+        return [
+            'file without FAL - VERY old style' => [
+                'fileadmin/on/steroids.png',
+                [
+                    'type' => LinkService::TYPE_FILE,
+                    'file' => 'fileadmin/on/steroids.png'
+                ],
+                't3://file?identifier=fileadmin%2Fon%2Fsteroids.png'
+            ],
+            'file without FAL with file prefix - VERY old style' => [
+                'file:fileadmin/on/steroids.png',
+                [
+                    'type' => LinkService::TYPE_FILE,
+                    'file' => 'fileadmin/on/steroids.png'
+                ],
+                't3://file?identifier=fileadmin%2Fon%2Fsteroids.png'
+            ],
+            'file with FAL uid - old style' => [
+                'file:23',
+                [
+                    'type' => LinkService::TYPE_FILE,
+                    'file' => 23
+                ],
+                't3://file?uid=23'
+            ],
+            'folder without FAL - VERY old style' => [
+                'fileadmin/myimages/',
+                [
+                    'type' => LinkService::TYPE_FOLDER,
+                    'folder' => 'fileadmin/myimages/'
+                ],
+                't3://folder?storage=0&identifier=%2Ffileadmin%2Fmyimages%2F'
+            ],
+            'folder with combined identifier and file prefix (FAL) - old style' => [
+                'file:2:/myimages/',
+                [
+                    'type' => LinkService::TYPE_FOLDER,
+                    'folder' => '2:/myimages/'
+                ],
+                't3://folder?storage=2&identifier=%2Fmyimages%2F'
+            ],
+        ];
+    }
+
+    /**
+     * Helpful to know in which if() clause the stuff gets in
+     *
+     * @test
+     *
+     * @param string $input
+     * @param array  $expected
+     * @param string $finalString
+     *
+     * @dataProvider resolveParametersForFilesDataProvider
+     */
+    public function resolveFileReferencesToSplitParameters($input, $expected, $finalString)
+    {
+        $storage = $this->getMockBuilder(ResourceStorage::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $factory = $this->getMockBuilder(ResourceFactory::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        // fake methods to return proper objects
+        if ($expected['type'] === LinkService::TYPE_FILE) {
+            $fileObject = new File(['identifier' => $expected['file']], $storage);
+            $factory->expects($this->any())->method('getFileObjectFromCombinedIdentifier')->with($expected['file'])
+                ->willReturn($fileObject);
+            $factory->expects($this->any())->method('retrieveFileOrFolderObject')->with($expected['file'])
+                ->willReturn($fileObject);
+            $factory->expects($this->any())->method('getFileObject')->with($expected['file'])->willReturn($fileObject);
+            $expected['file'] = $fileObject;
+        }
+        // fake methods to return proper objects
+        if ($expected['type'] === LinkService::TYPE_FOLDER) {
+            if (substr($expected['folder'], 0, 5) === 'file:') {
+                $expected['folder'] = substr($expected['folder'], 5);
+            }
+            $folderObject = new Folder($storage, $expected['folder'], $expected['folder']);
+            $factory->expects($this->any())->method('retrieveFileOrFolderObject')->with($expected['folder'])
+                ->willReturn($folderObject);
+            $factory->expects($this->any())->method('getFolderObjectFromCombinedIdentifier')->with($expected['folder'])
+                ->willReturn($folderObject);
+            $expected['folder'] = $folderObject;
+        }
+
+        /** @var LegacyLinkNotationConverter|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface $subject */
+        $subject = $this->getAccessibleMock(LegacyLinkNotationConverter::class, ['dummy']);
+        $subject->_set('resourceFactory', $factory);
+        $this->assertEquals($expected, $subject->resolve($input));
+    }
+
+    /**
+     * Helpful to know in which if() clause the stuff gets in
+     *
+     * @test
+     *
+     * @param string $input
+     * @param array  $parameters
+     * @param string $expected
+     *
+     * @dataProvider resolveParametersForFilesDataProvider
+     */
+    public function splitParametersToUnifiedIdentifierForFiles($input, $parameters, $expected)
+    {
+        // fake methods to return proper objects
+        if ($parameters['type'] === LinkService::TYPE_FILE) {
+            $fileObject = $this->getMockBuilder(File::class)
+                ->setMethods(['getUid', 'getIdentifier'])
+                ->disableOriginalConstructor()
+                ->getMock();
+            $uid = 0;
+            if (MathUtility::canBeInterpretedAsInteger($parameters['file'])) {
+                $uid = $parameters['file'];
+            }
+            $fileObject->expects($this->once())->method('getUid')->willReturn($uid);
+            $fileObject->expects($this->any())->method('getIdentifier')->willReturn($parameters['file']);
+            $parameters['file'] = $fileObject;
+        }
+        // fake methods to return proper objects
+        if ($parameters['type'] === LinkService::TYPE_FOLDER) {
+            if (substr($parameters['folder'], 0, 5) === 'file:') {
+                $parameters['folder'] = substr($parameters['folder'], 5);
+            }
+            // fake "0" storage
+            if (!MathUtility::canBeInterpretedAsInteger($parameters['folder']{0})) {
+                $parameters['folder'] = '0:' . $parameters['folder'];
+            }
+            $folderObject = $this->getMockBuilder(Folder::class)
+                ->setMethods(['getCombinedIdentifier', 'getStorage', 'getIdentifier'])
+                ->disableOriginalConstructor()
+                ->getMock();
+            $folderObject->expects($this->any())->method('getCombinedIdentifier')->willReturn($parameters['folder']);
+            $folderData = explode(':', $parameters['folder']);
+            /** @var ResourceStorage|\PHPUnit_Framework_MockObject_MockObject $storageMock */
+            $storage = $this->getMockBuilder(ResourceStorage::class)
+                ->disableOriginalConstructor()
+                ->getMock(array('getUid'));
+            $storage->method('getUid')->willReturn($folderData[0]);
+            $folderObject->expects($this->any())->method('getStorage')->willReturn($storage);
+            $folderObject->expects($this->any())->method('getIdentifier')->willReturn($folderData[1]);
+            $parameters['folder'] = $folderObject;
+        }
+
+        $subject = new LinkService();
+        $this->assertEquals($expected, $subject->asString($parameters));
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/LinkHandling/LinkServiceTest.php b/typo3/sysext/core/Tests/Unit/LinkHandling/LinkServiceTest.php
new file mode 100644 (file)
index 0000000..96acde0
--- /dev/null
@@ -0,0 +1,242 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Unit\LinkHandling;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Core\LinkHandling\LinkService;
+
+class LinkServiceTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
+{
+    /**
+     * Data to resolve strings to arrays and vice versa, external, mail, page
+     *
+     * @return array
+     */
+    public function resolveParametersForNonFilesDataProvider()
+    {
+        return [
+            'simple page - old style' => [
+                // original input value
+                '13',
+                // splitted values
+                [
+                    'type' => LinkService::TYPE_PAGE,
+                    'pageuid' => 13
+                ],
+                // final unified URN
+                't3://page?uid=13'
+            ],
+            'page with type - old style' => [
+                '13,31',
+                [
+                    'type' => LinkService::TYPE_PAGE,
+                    'pageuid' => 13,
+                    'pagetype' => 31
+                ],
+                't3://page?uid=13&type=31'
+            ],
+            'page with type and fragment - old style' => [
+                '13,31#uncool',
+                [
+                    'type' => LinkService::TYPE_PAGE,
+                    'pageuid' => '13',
+                    'pagetype' => '31',
+                    'fragment' => 'uncool'
+                ],
+                't3://page?uid=13&type=31#uncool'
+            ],
+            'page with type and parameters and fragment - old style' => [
+                '13,31?unbel=ievable#uncool',
+                [
+                    'type' => LinkService::TYPE_PAGE,
+                    'pageuid' => '13',
+                    'pagetype' => '31',
+                    'parameters' => 'unbel=ievable',
+                    'fragment' => 'uncool'
+                ],
+                't3://page?uid=13&type=31&unbel=ievable#uncool'
+            ],
+            'page with alias - old style' => [
+                'alias13',
+                [
+                    'type' => LinkService::TYPE_PAGE,
+                    'pagealias' => 'alias13'
+                ],
+                't3://page?alias=alias13'
+            ],
+            'http URL' => [
+                'http://www.have.you/ever?did=this',
+                [
+                    'type' => LinkService::TYPE_URL,
+                    'url' => 'http://www.have.you/ever?did=this'
+                ],
+                'http://www.have.you/ever?did=this'
+            ],
+            'http URL without scheme' => [
+                'www.have.you/ever?did=this',
+                [
+                    'type' => LinkService::TYPE_URL,
+                    'url' => 'http://www.have.you/ever?did=this'
+                ],
+                'http://www.have.you/ever?did=this'
+            ],
+            'https URL' => [
+                'https://www.have.you/ever?did=this',
+                [
+                    'type' => LinkService::TYPE_URL,
+                    'url' => 'https://www.have.you/ever?did=this'
+                ],
+                'https://www.have.you/ever?did=this'
+            ],
+            'https URL with port' => [
+                'https://www.have.you:8088/ever?did=this',
+                [
+                    'type' => LinkService::TYPE_URL,
+                    'url' => 'https://www.have.you:8088/ever?did=this'
+                ],
+                'https://www.have.you:8088/ever?did=this'
+            ],
+            'ftp URL' => [
+                'ftp://www.have.you/ever?did=this',
+                [
+                    'type' => LinkService::TYPE_URL,
+                    'url' => 'ftp://www.have.you/ever?did=this'
+                ],
+                'ftp://www.have.you/ever?did=this'
+            ],
+            'afp URL' => [
+                'afp://www.have.you/ever?did=this',
+                [
+                    'type' => LinkService::TYPE_URL,
+                    'url' => 'afp://www.have.you/ever?did=this'
+                ],
+                'afp://www.have.you/ever?did=this'
+            ],
+            'sftp URL' => [
+                'sftp://nice:andsecret@www.have.you:23/ever?did=this',
+                [
+                    'type' => LinkService::TYPE_URL,
+                    'url' => 'sftp://nice:andsecret@www.have.you:23/ever?did=this'
+                ],
+                'sftp://nice:andsecret@www.have.you:23/ever?did=this'
+            ],
+            'email with protocol' => [
+                'mailto:one@love.com',
+                [
+                    'type' => LinkService::TYPE_EMAIL,
+                    'email' => 'one@love.com'
+                ],
+                'mailto:one@love.com'
+            ],
+            'email without protocol' => [
+                'one@love.com',
+                [
+                    'type' => LinkService::TYPE_EMAIL,
+                    'email' => 'one@love.com'
+                ],
+                'mailto:one@love.com'
+            ],
+            'current page - cool style' => [
+                't3://page?uid=current',
+                [
+                    'type' => LinkService::TYPE_PAGE,
+                    'pageuid' => 'current'
+                ],
+                't3://page?uid=current'
+            ],
+            'current empty page - cool style' => [
+                't3://page',
+                [
+                    'type' => LinkService::TYPE_PAGE,
+                    'pageuid' => 'current'
+                ],
+                't3://page?uid=current'
+            ],
+            'simple page - cool style' => [
+                't3://page?uid=13',
+                [
+                    'type' => LinkService::TYPE_PAGE,
+                    'pageuid' => 13
+                ],
+                't3://page?uid=13'
+            ],
+            'page with alias - cool style' => [
+                't3://page?alias=alias13',
+                [
+                    'type' => LinkService::TYPE_PAGE,
+                    'pagealias' => 'alias13'
+                ],
+                't3://page?alias=alias13'
+            ],
+            'page with alias and type - cool style' => [
+                't3://page?alias=alias13&type=31',
+                [
+                    'type' => LinkService::TYPE_PAGE,
+                    'pagealias' => 'alias13',
+                    'pagetype' => '31'
+                ],
+                't3://page?alias=alias13&type=31'
+            ],
+            'page with alias and parameters - cool style' => [
+                't3://page?alias=alias13&my=additional&parameter=that&are=nice',
+                [
+                    'type' => LinkService::TYPE_PAGE,
+                    'pagealias' => 'alias13',
+                    'parameters' => 'my=additional&parameter=that&are=nice'
+                ],
+                't3://page?alias=alias13&my=additional&parameter=that&are=nice',
+            ],
+            'page with alias and parameters and fragment - cool style' => [
+                't3://page?alias=alias13&my=additional&parameter=that&are=nice#again',
+                [
+                    'type' => LinkService::TYPE_PAGE,
+                    'pagealias' => 'alias13',
+                    'parameters' => 'my=additional&parameter=that&are=nice',
+                    'fragment' => 'again'
+                ],
+                't3://page?alias=alias13&my=additional&parameter=that&are=nice#again',
+            ]
+        ];
+    }
+
+    /**
+     * @test
+     *
+     * @param string $input
+     * @param array  $expected
+     * @param string $finalString
+     *
+     * @dataProvider resolveParametersForNonFilesDataProvider
+     */
+    public function resolveReturnsSplitParameters($input, $expected, $finalString)
+    {
+        $subject = new LinkService();
+        $this->assertEquals($expected, $subject->resolve($input));
+    }
+
+    /**
+     * @test
+     *
+     * @param string $input
+     * @param array  $parameters
+     * @param string $expected
+     *
+     * @dataProvider resolveParametersForNonFilesDataProvider
+     */
+    public function splitParametersToUnifiedIdentifier($input, $parameters, $expected)
+    {
+        $subject = new LinkService();
+        $this->assertEquals($expected, $subject->asString($parameters));
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/LinkHandling/PageLinkHandlerTest.php b/typo3/sysext/core/Tests/Unit/LinkHandling/PageLinkHandlerTest.php
new file mode 100644 (file)
index 0000000..5a17abc
--- /dev/null
@@ -0,0 +1,140 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Unit\LinkHandling;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Core\LinkHandling\PageLinkHandler;
+
+class PageLinkHandlerTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
+{
+
+    /**
+     * Data to resolve strings to arrays and vice versa, external, mail, page
+     *
+     * @return array
+     */
+    public function resolveParametersForNonFilesDataProvider()
+    {
+        return [
+            'current page - cool style' => [
+                [
+                    'uid' => 'current'
+                ],
+                [
+                    'pageuid' => 'current'
+                ],
+                't3://page?uid=current'
+            ],
+            'current empty page - cool style' => [
+                [
+
+                ],
+                [
+                    'pageuid' => 'current'
+                ],
+                't3://page?uid=current'
+            ],
+            'simple page - cool style' => [
+                [
+                    'uid' => 13
+                ],
+                [
+                    'pageuid' => 13
+                ],
+                't3://page?uid=13'
+            ],
+            'page with alias - cool style' => [
+                [
+                    'alias' => 'alias13'
+                ],
+                [
+                    'pagealias' => 'alias13'
+                ],
+                't3://page?alias=alias13'
+            ],
+            'page with alias and type - cool style' => [
+                [
+                    'alias' => 'alias13',
+                    'type' => 31
+                ],
+                [
+                    'pagealias' => 'alias13',
+                    'pagetype' => '31'
+                ],
+                't3://page?alias=alias13&type=31'
+            ],
+            'page with alias and parameters - cool style' => [
+                [
+                    'alias' => 'alias13',
+                    'my' => 'additional',
+                    'parameter' => 'that',
+                    'are' => 'nice'
+                ],
+                [
+                    'pagealias' => 'alias13',
+                    'parameters' => 'my=additional&parameter=that&are=nice'
+                ],
+                't3://page?alias=alias13&my=additional&parameter=that&are=nice',
+            ],
+            'page with alias and parameters and fragment - cool style' => [
+                [
+                    'alias' => 'alias13',
+                    'my' => 'additional',
+                    'parameter' => 'that',
+                    'are' => 'nice'
+                ],
+                [
+                    'pagealias' => 'alias13',
+                    'parameters' => 'my=additional&parameter=that&are=nice',
+                    'fragment' => 'again'
+                ],
+                't3://page?alias=alias13&my=additional&parameter=that&are=nice#again',
+            ]
+        ];
+    }
+
+    /**
+     * @test
+     *
+     * @param string $input
+     * @param array  $expected
+     * @param string $finalString
+     *
+     * @dataProvider resolveParametersForNonFilesDataProvider
+     */
+    public function resolveReturnsSplitParameters($input, $expected, $finalString)
+    {
+        $subject = new PageLinkHandler();
+        // fragment it is processed outside handler data
+        if (isset($expected['fragment'])) {
+            unset($expected['fragment']);
+        }
+        $this->assertEquals($expected, $subject->resolveHandlerData($input));
+    }
+
+    /**
+     * @test
+     *
+     * @param string $input
+     * @param array  $parameters
+     * @param string $expected
+     *
+     * @dataProvider resolveParametersForNonFilesDataProvider
+     */
+    public function splitParametersToUnifiedIdentifier($input, $parameters, $expected)
+    {
+        $subject = new PageLinkHandler();
+        $this->assertEquals($expected, $subject->asString($parameters));
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/LinkHandling/UrlLinkHandlerTest.php b/typo3/sysext/core/Tests/Unit/LinkHandling/UrlLinkHandlerTest.php
new file mode 100644 (file)
index 0000000..5f6d67c
--- /dev/null
@@ -0,0 +1,125 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Unit\LinkHandling;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Core\LinkHandling\UrlLinkHandler;
+
+class UrlLinkHandlerTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
+{
+
+    /**
+     * Data to resolve strings to arrays and vice versa, external, mail, page
+     *
+     * @return array
+     */
+    public function resolveParametersForNonFilesDataProvider()
+    {
+        return [
+            'URL without a scheme' => [
+                [
+                    'url' => 'www.have.you/ever?did=this'
+                ],
+                [
+                    'url' => 'http://www.have.you/ever?did=this'
+                ],
+                'http://www.have.you/ever?did=this'
+            ],
+            'http URL' => [
+                [
+                    'url' => 'http://www.have.you/ever?did=this'
+                ],
+                [
+                    'url' => 'http://www.have.you/ever?did=this'
+                ],
+                'http://www.have.you/ever?did=this'
+            ],
+            'https URL' => [
+                [
+                    'url' => 'https://www.have.you/ever?did=this'
+                ],
+                [
+                    'url' => 'https://www.have.you/ever?did=this'
+                ],
+                'https://www.have.you/ever?did=this'
+            ],
+            'https URL with port' => [
+                [
+                    'url' => 'https://www.have.you:8088/ever?did=this'
+                ],
+                [
+                    'url' => 'https://www.have.you:8088/ever?did=this'
+                ],
+                'https://www.have.you:8088/ever?did=this'
+            ],
+            'ftp URL' => [
+                [
+                    'url' => 'ftp://www.have.you/ever?did=this'
+                ],
+                [
+                    'url' => 'ftp://www.have.you/ever?did=this'
+                ],
+                'ftp://www.have.you/ever?did=this'
+            ],
+            'afp URL' => [
+                [
+                    'url' => 'afp://www.have.you/ever?did=this'
+                ],
+                [
+                    'url' => 'afp://www.have.you/ever?did=this'
+                ],
+                'afp://www.have.you/ever?did=this'
+            ],
+            'sftp URL' => [
+                [
+                    'url' => 'sftp://nice:andsecret@www.have.you:23/ever?did=this'
+                ],
+                [
+                    'url' => 'sftp://nice:andsecret@www.have.you:23/ever?did=this'
+                ],
+                'sftp://nice:andsecret@www.have.you:23/ever?did=this'
+            ]
+        ];
+    }
+
+    /**
+     * @test
+     *
+     * @param string $input
+     * @param array  $expected
+     * @param string $finalString
+     *
+     * @dataProvider resolveParametersForNonFilesDataProvider
+     */
+    public function resolveReturnsSplitParameters($input, $expected, $finalString)
+    {
+        $subject = new UrlLinkHandler();
+        $this->assertEquals($expected, $subject->resolveHandlerData($input));
+    }
+
+    /**
+     * @test
+     *
+     * @param string $input
+     * @param array  $parameters
+     * @param string $expected
+     *
+     * @dataProvider resolveParametersForNonFilesDataProvider
+     */
+    public function splitParametersToUnifiedIdentifier($input, $parameters, $expected)
+    {
+        $subject = new UrlLinkHandler();
+        $this->assertEquals($expected, $subject->asString($parameters));
+    }
+}
index 2d9f18f..2408e83 100644 (file)
@@ -19,6 +19,7 @@ use TYPO3\CMS\Core\Charset\CharsetConverter;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\FrontendEditing\FrontendEditingController;
 use TYPO3\CMS\Core\Html\HtmlParser;
+use TYPO3\CMS\Core\LinkHandling\LinkService;
 use TYPO3\CMS\Core\Log\LogManager;
 use TYPO3\CMS\Core\Mail\MailMessage;
 use TYPO3\CMS\Core\Resource\Exception;
@@ -5739,64 +5740,6 @@ class ContentObjectRenderer
     }
 
     /**
-     * part of the typolink construction functionality, called by typoLink()
-     *
-     * tries to get the type of the link from the link parameter
-     * could be
-     *  - "mailto" an email address
-     *  - "url" external URL
-     *  - "file" a local file (checked AFTER getPublicUrl() is called)
-     *  - "page" a page (integer or alias)
-     *
-     * @param string $linkParameter could be "fileadmin/myfile.jpg" or "info@typo3.org" or "13" or "http://www.typo3.org"
-     * @return string the keyword
-     * @see typoLink()
-     */
-    protected function detectLinkTypeFromLinkParameter($linkParameter)
-    {
-        // Parse URL:
-        $scheme = parse_url($linkParameter, PHP_URL_SCHEME);
-        // Detecting kind of link:
-        // If it's a mail address:
-        if (strpos($linkParameter, '@') > 0 && (!$scheme || $scheme === 'mailto')) {
-            return 'mailto';
-        }
-
-        $isLocalFile = 0;
-        $fileChar = intval(strpos($linkParameter, '/'));
-        $urlChar = intval(strpos($linkParameter, '.'));
-
-        $containsSlash = false;
-        // Firsts, test if $linkParameter is numeric and page with such id exists. If yes, do not attempt to link to file
-        if (!MathUtility::canBeInterpretedAsInteger($linkParameter) || empty($this->getTypoScriptFrontendController()->sys_page->getPage_noCheck($linkParameter))) {
-            // Detects if a file is found in site-root and if so it will be treated like a normal file.
-            list($rootFileDat) = explode('?', rawurldecode($linkParameter));
-            $containsSlash = strpos($rootFileDat, '/') !== false;
-            $rFD_fI = pathinfo($rootFileDat);
-            $fileExtension = strtolower($rFD_fI['extension']);
-            if (!$containsSlash && trim($rootFileDat) && (@is_file(PATH_site . $rootFileDat) || $fileExtension === 'php' || $fileExtension === 'html' || $fileExtension === 'htm')) {
-                $isLocalFile = 1;
-            } elseif ($containsSlash) {
-                // Adding this so realurl directories are linked right (non-existing).
-                $isLocalFile = 2;
-            }
-        }
-
-        // url (external): If doubleSlash or if a '.' comes before a '/'.
-        if ($scheme || $isLocalFile !== 1 && $urlChar && (!$containsSlash || $urlChar < $fileChar)) {
-            return 'url';
-
-        // file (internal)
-        } elseif ($containsSlash || $isLocalFile) {
-            return 'file';
-        }
-
-        // Integer or alias (alias is without slashes or periods or commas, that is
-        // 'nospace,alphanum_x,lower,unique' according to definition in $GLOBALS['TCA']!)
-        return 'page';
-    }
-
-    /**
      * Implements the "typolink" property of stdWrap (and others)
      * Basically the input string, $linktext, is (typically) wrapped in a <a>-tag linking to some page, email address, file or URL based on a parameter defined by the configuration array $conf.
      * This function is best used from internal functions as is. There are some API functions defined after this function which is more suited for general usage in external applications.
@@ -5831,7 +5774,7 @@ class ContentObjectRenderer
         $linkParameter = $resolvedLinkParameters['href'];
         $target = $resolvedLinkParameters['target'];
         $linkClass = $resolvedLinkParameters['class'];
-        $forceTitle = $resolvedLinkParameters['title'];
+        $title = $resolvedLinkParameters['title'];
 
         if (!$linkParameter) {
             return $linkText;
@@ -5857,25 +5800,19 @@ class ContentObjectRenderer
             $target = '';
         }
 
-        // Title tag
-        $title = $conf['title'];
-        if ($conf['title.']) {
-            $title = $this->stdWrap($title, $conf['title.']);
-        }
-
-        $theTypeP = '';
-        // Detecting kind of link
-        $linkType = $this->detectLinkTypeFromLinkParameter($linkParameter);
-        switch ($linkType) {
+        // Detecting kind of link and resolve all necessary parameters
+        /** @var LinkService $linkService */
+        $linkService = GeneralUtility::makeInstance(LinkService::class);
+        $linkDetails = $linkService->resolve($linkParameter);
+        switch ($linkDetails['type']) {
             // If it's a mail address
-            case 'mailto':
-                $linkParameter = preg_replace('/^mailto:/i', '', $linkParameter);
-                list($this->lastTypoLinkUrl, $linkText) = $this->getMailTo($linkParameter, $linkText);
+            case LinkService::TYPE_EMAIL:
+                list($this->lastTypoLinkUrl, $linkText) = $this->getMailTo($linkDetails['email'], $linkText);
                 $finalTagParts['url'] = $this->lastTypoLinkUrl;
             break;
 
-            // url (external): If doubleSlash or if a '.' comes before a '/'.
-            case 'url':
+            // URL (external)
+            case LinkService::TYPE_URL:
                 if (empty($target)) {
                     if (isset($conf['extTarget'])) {
                         $target = $conf['extTarget'];
@@ -5886,35 +5823,28 @@ class ContentObjectRenderer
                         $target = $this->stdWrap($target, $conf['extTarget.']);
                     }
                 }
-                $linkText = $this->parseFallbackLinkTextIfLinkTextIsEmpty($linkText, $linkParameter);
-                // Parse URL:
-                $urlParts = parse_url($linkParameter);
-                if (!$urlParts['scheme']) {
-                    $scheme = 'http://';
-                } else {
-                    $scheme = '';
-                }
-
-                $this->lastTypoLinkUrl = $this->processUrl(UrlProcessorInterface::CONTEXT_EXTERNAL, $scheme . $linkParameter, $conf);
+                $linkText = $this->parseFallbackLinkTextIfLinkTextIsEmpty($linkText, $linkDetails['url']);
 
+                $this->lastTypoLinkUrl = $this->processUrl(UrlProcessorInterface::CONTEXT_EXTERNAL, $linkDetails['url'], $conf);
                 $this->lastTypoLinkTarget = $target;
                 $finalTagParts['url'] = $this->lastTypoLinkUrl;
                 $finalTagParts['targetParams'] = $target ? ' target="' . htmlspecialchars($target) . '"' : '';
-                $finalTagParts['aTagParams'] .= $this->extLinkATagParams($finalTagParts['url'], $linkType);
+                $finalTagParts['aTagParams'] .= $this->extLinkATagParams($finalTagParts['url'], LinkService::TYPE_URL);
             break;
 
-            // file (internal)
-            case 'file':
-
-                $splitLinkParam = explode('?', $linkParameter);
-
+            // File (internal)
+            case LinkService::TYPE_FILE:
+            case LinkService::TYPE_FOLDER:
+                $fileOrFolderObject = $linkDetails['file'] ? $linkDetails['file'] : $linkDetails['folder'];
                 // check if the file exists or if a / is contained (same check as in detectLinkType)
-                if (file_exists(rawurldecode($splitLinkParam[0])) || strpos($linkParameter, '/') !== false) {
+                if ($fileOrFolderObject instanceof FileInterface || $fileOrFolderObject instanceof Folder) {
+                    $linkLocation = $fileOrFolderObject->getPublicUrl();
                     // Setting title if blank value to link
-                    $linkText = $this->parseFallbackLinkTextIfLinkTextIsEmpty($linkText, rawurldecode($linkParameter));
-                    $fileUri = (!StringUtility::beginsWith($linkParameter, '/') ? $GLOBALS['TSFE']->absRefPrefix : '') . $linkParameter;
-                    $this->lastTypoLinkUrl = $this->processUrl(UrlProcessorInterface::CONTEXT_FILE, $fileUri, $conf);
+                    $linkText = $this->parseFallbackLinkTextIfLinkTextIsEmpty($linkText, rawurldecode($linkLocation));
+                    $linkLocation = (!StringUtility::beginsWith($linkLocation, '/') ? $tsfe->absRefPrefix : '') . $linkLocation;
+                    $this->lastTypoLinkUrl = $this->processUrl(UrlProcessorInterface::CONTEXT_FILE, $linkLocation, $conf);
                     $this->lastTypoLinkUrl = $this->forceAbsoluteUrl($this->lastTypoLinkUrl, $conf);
+
                     if (empty($target)) {
                         $target = isset($conf['fileTarget']) ? $conf['fileTarget'] : $tsfe->fileTarget;
                         if ($conf['fileTarget.']) {
@@ -5924,50 +5854,40 @@ class ContentObjectRenderer
                     $this->lastTypoLinkTarget = $target;
                     $finalTagParts['url'] = $this->lastTypoLinkUrl;
                     $finalTagParts['targetParams'] = $target ? ' target="' . htmlspecialchars($target) . '"' : '';
-                    $finalTagParts['aTagParams'] .= $this->extLinkATagParams($finalTagParts['url'], $linkType);
+                    $finalTagParts['aTagParams'] .= $this->extLinkATagParams($finalTagParts['url'], LinkService::TYPE_FILE);
                 } else {
-                    $this->getTimeTracker()->setTSlogMessage('typolink(): File "' . $splitLinkParam[0] . '" did not exist, so "' . $linkText . '" was not linked.', 1);
+                    $this->getTimeTracker()->setTSlogMessage('typolink(): File "' . $linkParameter . '" did not exist, so "' . $linkText . '" was not linked.', 1);
                     return $linkText;
                 }
             break;
 
-            // Integer or alias (alias is without slashes or periods or commas, that is
-            // 'nospace,alphanum_x,lower,unique' according to definition in $GLOBALS['TCA']!)
-            case 'page':
+            // Link to a page
+            case LinkService::TYPE_PAGE:
                 $enableLinksAcrossDomains = $tsfe->config['config']['typolinkEnableLinksAcrossDomains'];
-
                 if ($conf['no_cache.']) {
                     $conf['no_cache'] = $this->stdWrap($conf['no_cache'], $conf['no_cache.']);
                 }
-                // Splitting the parameter by ',' and if the array counts more than 1 element it's an id/type/parameters triplet
-                $pairParts = GeneralUtility::trimExplode(',', $linkParameter, true);
-                $linkParameter = $pairParts[0];
-                $link_params_parts = explode('#', $linkParameter);
-                // Link-data del
-                $linkParameter = trim($link_params_parts[0]);
-                // If no id or alias is given
-                if ($linkParameter === '') {
-                    $linkParameter = $tsfe->id;
+                // Checking if the id-parameter is an alias.
+                if (!empty($linkDetails['pagealias'])) {
+                    $linkDetails['pageuid'] = $tsfe->sys_page->getPageIdFromAlias($linkDetails['pagealias']);
+                } elseif (empty($linkDetails['pageuid']) || $linkDetails['pageuid'] === 'current') {
+                    // If no id or alias is given
+                    $linkDetails['pageuid'] = $tsfe->id;
                 }
-
                 $sectionMark = trim(isset($conf['section.']) ? $this->stdWrap($conf['section'], $conf['section.']) : $conf['section']);
+                if ($sectionMark === '' && isset($linkDetails['fragment'])) {
+                    $sectionMark = $linkDetails['fragment'];
+                }
                 if ($sectionMark !== '') {
                     $sectionMark = '#' . (MathUtility::canBeInterpretedAsInteger($sectionMark) ? 'c' : '') . $sectionMark;
                 }
+                // Overruling 'type'
+                $pageType = $linkDetails['pagetype'] ?? 0;
 
-                if ($link_params_parts[1] && $sectionMark === '') {
-                    $sectionMark = trim($link_params_parts[1]);
-                    $sectionMark = '#' . (MathUtility::canBeInterpretedAsInteger($sectionMark) ? 'c' : '') . $sectionMark;
-                }
-                if (count($pairParts) > 1) {
-                    // Overruling 'type'
-                    $theTypeP = isset($pairParts[1]) ? $pairParts[1] : 0;
-                    $conf['additionalParams'] .= isset($pairParts[2]) ? $pairParts[2] : '';
-                }
-                // Checking if the id-parameter is an alias.
-                if (!MathUtility::canBeInterpretedAsInteger($linkParameter)) {
-                    $linkParameter = $tsfe->sys_page->getPageIdFromAlias($linkParameter);
+                if (isset($linkDetails['parameters'])) {
+                    $conf['additionalParams'] .= '&' . ltrim($linkDetails['parameters'], '&');
                 }
+
                 // Link to page even if access is missing?
                 if (isset($conf['linkAccessRestrictedPages'])) {
                     $disableGroupAccessCheck = (bool)$conf['linkAccessRestrictedPages'];
@@ -5975,7 +5895,7 @@ class ContentObjectRenderer
                     $disableGroupAccessCheck = (bool)$tsfe->config['config']['typolinkLinkAccessRestrictedPages'];
                 }
                 // Looking up the page record to verify its existence:
-                $page = $tsfe->sys_page->getPage($linkParameter, $disableGroupAccessCheck);
+                $page = $tsfe->sys_page->getPage($linkDetails['pageuid'], $disableGroupAccessCheck);
                 if (!empty($page)) {
                     // MointPoints, look for closest MPvar:
                     $MPvarAcc = [];
@@ -6103,7 +6023,7 @@ class ContentObjectRenderer
                                 $target = $this->stdWrap($target, $conf['target.']);
                             }
                         }
-                        $LD = $tsfe->tmpl->linkData($page, $target, $conf['no_cache'], '', '', $addQueryParams, $theTypeP, $targetDomain);
+                        $LD = $tsfe->tmpl->linkData($page, $target, $conf['no_cache'], '', '', $addQueryParams, $pageType, $targetDomain);
                         if ($targetDomain !== '') {
                             // We will add domain only if URL does not have it already.
                             if ($enableLinksAcrossDomains && $targetDomain !== $currentDomain) {
@@ -6121,7 +6041,7 @@ class ContentObjectRenderer
                                 $LD['totalURL'] = $absoluteUrlScheme . '://' . $targetDomain . ($LD['totalURL'][0] === '/' ? '' : '/') . $LD['totalURL'];
                             }
                         }
-                        $this->lastTypoLinkUrl = $this->URLqMark($LD['totalURL'], '') . $sectionMark;
+                        $this->lastTypoLinkUrl = $LD['totalURL'] . $sectionMark;
                     }
                     $this->lastTypoLinkTarget = $LD['target'];
                     // If sectionMark is set, there is no baseURL AND the current page is the page the link is to, check if there are any additional parameters or addQueryString parameters and if not, drop the url.
@@ -6165,7 +6085,7 @@ class ContentObjectRenderer
                             ],
                             $tsfe->config['config']['typolinkLinkAccessRestrictedPages_addParams']
                         );
-                        $this->lastTypoLinkUrl = $this->getTypoLink_URL($thePage['uid'] . ($theTypeP ? ',' . $theTypeP : ''), $addParams, $target);
+                        $this->lastTypoLinkUrl = $this->getTypoLink_URL($thePage['uid'] . ($pageType ? ',' . $pageType : ''), $addParams, $target);
                         $this->lastTypoLinkUrl = $this->forceAbsoluteUrl($this->lastTypoLinkUrl, $conf);
                         $this->lastTypoLinkLD['totalUrl'] = $this->lastTypoLinkUrl;
                         $LD = $this->lastTypoLinkLD;
@@ -6178,13 +6098,57 @@ class ContentObjectRenderer
                     return $linkText;
                 }
             break;
+
+            // Legacy files or something else
+            case LinkService::TYPE_UNKNOWN:
+                if ($linkDetails['file']) {
+                    $linkLocation = $linkDetails['file'];
+                    // Setting title if blank value to link
+                    $linkText = $this->parseFallbackLinkTextIfLinkTextIsEmpty($linkText, rawurldecode($linkLocation));
+                    $linkLocation = (!StringUtility::beginsWith($linkLocation, '/') ? $tsfe->absRefPrefix : '') . $linkLocation;
+                    $this->lastTypoLinkUrl = $this->processUrl(UrlProcessorInterface::CONTEXT_FILE, $linkLocation, $conf);
+                    $this->lastTypoLinkUrl = $this->forceAbsoluteUrl($this->lastTypoLinkUrl, $conf);
+                    if (empty($target)) {
+                        $target = isset($conf['fileTarget']) ? $conf['fileTarget'] : $tsfe->fileTarget;
+                        if ($conf['fileTarget.']) {
+                            $target = $this->stdWrap($target, $conf['fileTarget.']);
+                        }
+                    }
+                    $this->lastTypoLinkTarget = $target;
+                    $finalTagParts['url'] = $this->lastTypoLinkUrl;
+                    $finalTagParts['targetParams'] = $target ? ' target="' . $target . '"' : '';
+                    $finalTagParts['aTagParams'] .= $this->extLinkATagParams($finalTagParts['url'], LinkService::TYPE_FILE);
+                } elseif ($linkDetails['url']) {
+                    if (empty($target)) {
+                        if (isset($conf['extTarget'])) {
+                            $target = $conf['extTarget'];
+                        } elseif ($tsfe->dtdAllowsFrames) {
+                            $target = $tsfe->extTarget;
+                        }
+                        if ($conf['extTarget.']) {
+                            $target = $this->stdWrap($target, $conf['extTarget.']);
+                        }
+                    }
+                    $linkText = $this->parseFallbackLinkTextIfLinkTextIsEmpty($linkText, $linkDetails['url']);
+
+                    $this->lastTypoLinkUrl = $this->processUrl(UrlProcessorInterface::CONTEXT_EXTERNAL, $linkDetails['url'], $conf);
+                    $this->lastTypoLinkTarget = $target;
+                    $finalTagParts['url'] = $this->lastTypoLinkUrl;
+                    $finalTagParts['targetParams'] = $target ? ' target="' . $target . '"' : '';
+                    $finalTagParts['aTagParams'] .= $this->extLinkATagParams($finalTagParts['url'], LinkService::TYPE_URL);
+                }
+            break;
         }
 
-        $finalTagParts['TYPE'] = $linkType;
+        $finalTagParts['TYPE'] = $linkDetails['type'];
         $this->lastTypoLinkLD = $LD;
 
-        if ($forceTitle) {
-            $title = $forceTitle;
+        // Title tag
+        if (empty($title)) {
+            $title = $conf['title'];
+            if ($conf['title.']) {
+                $title = $this->stdWrap($title, $conf['title.']);
+            }
         }
 
         if ($JSwindowParams) {
@@ -6204,7 +6168,7 @@ class ContentObjectRenderer
                 . $finalTagParts['aTagParams']
                 . '>';
         } else {
-            if ($tsfe->spamProtectEmailAddresses === 'ascii' && $linkType === 'mailto') {
+            if ($tsfe->spamProtectEmailAddresses === 'ascii' && $linkDetails['type'] === LinkService::TYPE_EMAIL) {
                 $finalAnchorTag = '<a href="' . $finalTagParts['url'] . '"';
             } else {
                 $finalAnchorTag = '<a href="' . htmlspecialchars($finalTagParts['url']) . '"';
index 8222836..c7e39d4 100644 (file)
@@ -2708,65 +2708,6 @@ class ContentObjectRendererTest extends UnitTestCase
     /**
      * @return array
      */
-    public function detectLinkTypeFromLinkParameterDataProvider()
-    {
-        return [
-            'Domain only' => [
-                'example.com',
-                'url'
-            ],
-            'URL without a file' => [
-                'http://example.com',
-                'url'
-            ],
-            'URL with schema and a file' => [
-                'http://example.com/index.php',
-                'url'
-            ],
-            'URL with a file but without a schema' => [
-                'example.com/index.php',
-                'url'
-            ],
-            'file' => [
-                '/index.php',
-                'file'
-            ],
-        ];
-    }
-
-    /**
-     * @test
-     * @param string $linkParameter
-     * @param string $expectedResult
-     * @dataProvider detectLinkTypeFromLinkParameterDataProvider
-     */
-    public function detectLinkTypeFromLinkParameter($linkParameter, $expectedResult)
-    {
-        /** @var TemplateService|\PHPUnit_Framework_MockObject_MockObject $templateServiceObjectMock */
-        $templateServiceObjectMock = $this->getMockBuilder(TemplateService::class)
-            ->setMethods(array('dummy'))
-            ->getMock();
-        $templateServiceObjectMock->setup = array(
-            'lib.' => array(
-                'parseFunc.' => $this->getLibParseFunc(),
-            ),
-        );
-        /** @var TypoScriptFrontendController|\PHPUnit_Framework_MockObject_MockObject $typoScriptFrontendControllerMockObject */
-        $typoScriptFrontendControllerMockObject = $this->createMock(TypoScriptFrontendController::class);
-        $typoScriptFrontendControllerMockObject->config = array(
-            'config' => array(),
-            'mainScript' => 'index.php',
-        );
-        $typoScriptFrontendControllerMockObject->tmpl = $templateServiceObjectMock;
-        $GLOBALS['TSFE'] = $typoScriptFrontendControllerMockObject;
-        $this->subject->_set('typoScriptFrontendController', $typoScriptFrontendControllerMockObject);
-
-        $this->assertEquals($expectedResult, $this->subject->_call('detectLinkTypeFromLinkParameter', $linkParameter));
-    }
-
-    /**
-     * @return array
-     */
     public function typolinkReturnsCorrectLinksForEmailsAndUrlsDataProvider()
     {
         return array(
index daf127a..40098d1 100644 (file)
@@ -17,8 +17,8 @@ namespace TYPO3\CMS\Recordlist\LinkHandler;
 use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Backend\Tree\View\ElementBrowserFolderTreeView;
 use TYPO3\CMS\Core\Imaging\Icon;
+use TYPO3\CMS\Core\LinkHandling\LinkService;
 use TYPO3\CMS\Core\Page\PageRenderer;
-use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
 use TYPO3\CMS\Core\Resource\File;
 use TYPO3\CMS\Core\Resource\FileInterface;
 use TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter;
@@ -26,7 +26,6 @@ use TYPO3\CMS\Core\Resource\Folder;
 use TYPO3\CMS\Core\Resource\ResourceFactory;
 use TYPO3\CMS\Core\Resource\ResourceInterface;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Utility\StringUtility;
 use TYPO3\CMS\Recordlist\Tree\View\LinkParameterProviderInterface;
 use TYPO3\CMS\Recordlist\View\FolderUtilityRenderer;
 
@@ -71,21 +70,9 @@ class FileLinkHandler extends AbstractLinkHandler implements LinkHandlerInterfac
         if (!$linkParts['url']) {
             return false;
         }
-        $url = rawurldecode($linkParts['url']);
-
-        if (StringUtility::beginsWith($url, 'file:') && !StringUtility::beginsWith($url, 'file://')) {
-            $rel = substr($url, 5);
-            try {
-                // resolve FAL-api "file:UID-of-sys_file-record" and "file:combined-identifier"
-                $fileOrFolderObject = ResourceFactory::getInstance()->retrieveFileOrFolderObject($rel);
-                if (is_a($fileOrFolderObject, $this->expectedClass)) {
-                    $this->linkParts = $linkParts;
-                    $this->linkParts['url'] = $rel;
-                    $this->linkParts['name'] = $fileOrFolderObject->getName();
-                    return true;
-                }
-            } catch (FileDoesNotExistException $e) {
-            }
+        if (isset($linkParts['url'][$this->mode]) && $linkParts['url'][$this->mode] instanceof $this->expectedClass) {
+            $this->linkParts = $linkParts;
+            return true;
         }
         return false;
     }
@@ -97,7 +84,7 @@ class FileLinkHandler extends AbstractLinkHandler implements LinkHandlerInterfac
      */
     public function formatCurrentUrl()
     {
-        return $this->linkParts['name'];
+        return $this->linkParts['url'][$this->mode]->getName();
     }
 
     /**
@@ -111,16 +98,22 @@ class FileLinkHandler extends AbstractLinkHandler implements LinkHandlerInterfac
     {
         GeneralUtility::makeInstance(PageRenderer::class)->loadRequireJsModule('TYPO3/CMS/Recordlist/FileLinkHandler');
 
-        $this->expandFolder = isset($request->getQueryParams()['expandFolder']) ? $request->getQueryParams()['expandFolder'] : null;
-        if (!empty($this->linkParts) && !isset($this->expandFolder)) {
-            $this->expandFolder = $this->linkParts['url'];
-        }
-
         /** @var ElementBrowserFolderTreeView $folderTree */
         $folderTree = GeneralUtility::makeInstance(ElementBrowserFolderTreeView::class);
         $folderTree->setLinkParameterProvider($this);
         $this->view->assign('tree', $folderTree->getBrowsableTree());
 
+        $this->expandFolder = isset($request->getQueryParams()['expandFolder']) ? $request->getQueryParams()['expandFolder'] : null;
+        if (!empty($this->linkParts) && !isset($this->expandFolder)) {
+            $this->expandFolder = $this->linkParts['url'][$this->mode];
+            if ($this->expandFolder instanceof Folder) {
+                if ($this->mode === 'file') {
+                    $this->expandFolder = $this->expandFolder->getParentFolder();
+                }
+                $this->expandFolder = $this->expandFolder->getCombinedIdentifier();
+            }
+        }
+
         // Create upload/create folder forms, if a path is given
         $selectedFolder = $this->getSelectedFolder($this->expandFolder);
 
@@ -161,7 +154,11 @@ class FileLinkHandler extends AbstractLinkHandler implements LinkHandlerInterfac
         $folderIcon = $this->iconFactory->getIconForResource($folder, Icon::SIZE_SMALL)->render();
         $this->view->assign('selectedFolderIcon', $folderIcon);
         $this->view->assign('selectedFolderTitle', GeneralUtility::fixed_lgd_cs($folder->getIdentifier(), (int)$this->getBackendUser()->uc['titleLen']));
-        $this->view->assign('currentIdentifier', !empty($this->linkParts) ? $this->linkParts['url'] : '');
+        if ($this->mode === 'file') {
+            $this->view->assign('currentIdentifier', !empty($this->linkParts) ? $this->linkParts['url']['file']->getUid() : '');
+        } else {
+            $this->view->assign('currentIdentifier', !empty($this->linkParts) ? $this->linkParts['url']['folder']->getCombinedIdentifier() : '');
+        }
 
         // Get files from the folder:
         $fileObjects = $this->getFolderContent($folder, $extensionList);
@@ -178,7 +175,7 @@ class FileLinkHandler extends AbstractLinkHandler implements LinkHandlerInterfac
      * @param Folder $folder
      * @param string $extensionList
      *
-     * @return FileInterface[]
+     * @return FileInterface[]|Folder[]
      */
     protected function getFolderContent(Folder $folder, $extensionList)
     {
@@ -215,6 +212,7 @@ class FileLinkHandler extends AbstractLinkHandler implements LinkHandlerInterfac
             'uid'  => $fileOrFolderObject->getUid(),
             'size' => $size,
             'name' => $fileOrFolderObject->getName(),
+            'url'  => GeneralUtility::makeInstance(LinkService::class)->asString(['type' => LinkService::TYPE_FILE, 'file' => $fileOrFolderObject]),
             'title' => GeneralUtility::fixed_lgd_cs($fileOrFolderObject->getName(), (int)$this->getBackendUser()->uc['titleLen'])
         ];
     }
@@ -225,7 +223,7 @@ class FileLinkHandler extends AbstractLinkHandler implements LinkHandlerInterfac
     public function getBodyTagAttributes()
     {
         return [
-            'data-current-link' => empty($this->linkParts) ? '' : 'file:' . $this->linkParts['url']
+            'data-current-link' => GeneralUtility::makeInstance(LinkService::class)->asString(['type' => LinkService::TYPE_FILE, 'file' => $this->linkParts['url']['file']])
         ];
     }
 
@@ -237,7 +235,7 @@ class FileLinkHandler extends AbstractLinkHandler implements LinkHandlerInterfac
     public function getUrlParameters(array $values)
     {
         $parameters = [
-            'expandFolder' => isset($values['identifier']) ? $values['identifier'] : (string)$this->expandFolder
+            'expandFolder' => $values['identifier'] ?? $this->expandFolder
         ];
         return array_merge($this->linkBrowser->getUrlParameters($values), $parameters);
     }
index 10f65d6..fd042b5 100644 (file)
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\Recordlist\LinkHandler;
  */
 
 use TYPO3\CMS\Core\Imaging\Icon;
+use TYPO3\CMS\Core\LinkHandling\LinkService;
 use TYPO3\CMS\Core\Resource\FileInterface;
 use TYPO3\CMS\Core\Resource\Folder;
 use TYPO3\CMS\Core\Resource\InaccessibleFolder;
@@ -39,7 +40,7 @@ class FolderLinkHandler extends FileLinkHandler
     /**
      * @param Folder $folder
      * @param string $extensionList
-     * @return FileInterface[]
+     * @return FileInterface[]|Folder[]
      */
     protected function getFolderContent(Folder $folder, $extensionList)
     {
@@ -47,6 +48,20 @@ class FolderLinkHandler extends FileLinkHandler
     }
 
     /**
+     * @return string[] Array of body-tag attributes
+     */
+    public function getBodyTagAttributes()
+    {
+        if ($this->linkParts['url']['folder'] instanceof $this->expectedClass) {
+            return [
+                'data-current-link' => GeneralUtility::makeInstance(LinkService::class)->asString(['type' => LinkService::TYPE_FOLDER, 'folder' => $this->linkParts['url']['folder']])
+            ];
+        } else {
+            return [];
+        }
+    }
+
+    /**
      * Renders a single item displayed in the current folder
      *
      * @param ResourceInterface $fileOrFolderObject
@@ -67,6 +82,7 @@ class FolderLinkHandler extends FileLinkHandler
             'icon' => $this->iconFactory->getIcon('apps-filetree-folder-default', Icon::SIZE_SMALL, $overlay)->render(),
             'identifier' => $fileOrFolderObject->getCombinedIdentifier(),
             'name' => $fileOrFolderObject->getName(),
+            'url'  => GeneralUtility::makeInstance(LinkService::class)->asString(['type' => LinkService::TYPE_FOLDER, 'folder' => $fileOrFolderObject]),
             'title' => GeneralUtility::fixed_lgd_cs($fileOrFolderObject->getName(), (int)$this->getBackendUser()->uc['titleLen'])
         ];
     }
index 6ad7704..cef793c 100644 (file)
@@ -63,7 +63,7 @@ class MailLinkHandler extends AbstractLinkHandler implements LinkHandlerInterfac
      */
     public function canHandleLink(array $linkParts)
     {
-        if ($linkParts['url'] && strpos($linkParts['url'], '@')) {
+        if (isset($linkParts['url']['email'])) {
             $this->linkParts = $linkParts;
             return true;
         }
@@ -77,7 +77,7 @@ class MailLinkHandler extends AbstractLinkHandler implements LinkHandlerInterfac
      */
     public function formatCurrentUrl()
     {
-        return $this->linkParts['url'];
+        return $this->linkParts['url']['email'];
     }
 
     /**
@@ -91,7 +91,7 @@ class MailLinkHandler extends AbstractLinkHandler implements LinkHandlerInterfac
     {
         GeneralUtility::makeInstance(PageRenderer::class)->loadRequireJsModule('TYPO3/CMS/Recordlist/MailLinkHandler');
 
-        $this->view->assign('email', !empty($this->linkParts) ? $this->linkParts['url'] : '');
+        $this->view->assign('email', !empty($this->linkParts) ? $this->linkParts['url']['email'] : '');
         return $this->view->render('Mail');
     }
 
index 3903162..455e8f2 100644 (file)
@@ -21,6 +21,7 @@ use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Imaging\Icon;
+use TYPO3\CMS\Core\LinkHandling\LinkService;
 use TYPO3\CMS\Core\Page\PageRenderer;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
@@ -58,31 +59,26 @@ class PageLinkHandler extends AbstractLinkHandler implements LinkHandlerInterfac
             return false;
         }
 
-        $id = $linkParts['url'];
-        $parts = explode('#', $id);
-        if (count($parts) > 1) {
-            $id = $parts[0];
-            $anchor = $parts[1];
-        } else {
-            $anchor = '';
-        }
+        $data = $linkParts['url'];
         // Checking if the id-parameter is an alias.
-        if (!MathUtility::canBeInterpretedAsInteger($id)) {
-            $records = BackendUtility::getRecordsByField('pages', 'alias', $id);
+        if (isset($data['pagealias'])) {
+            $records = BackendUtility::getRecordsByField('pages', 'alias', $data['pagealias']);
             if (empty($records)) {
                 return false;
             }
-            $id = (int)$records[0]['uid'];
+            $data['pageuid'] = (int)$records[0]['uid'];
         }
-        $pageRow = BackendUtility::getRecordWSOL('pages', $id);
-        if (!$pageRow) {
+        // Check if the page still exists
+        if ((int)$data['pageuid'] > 0) {
+            $pageRow = BackendUtility::getRecordWSOL('pages', $data['pageuid']);
+            if (!$pageRow) {
+                return false;
+            }
+        } elseif ($data['pageuid'] !== 'current') {
             return false;
         }
 
         $this->linkParts = $linkParts;
-        $this->linkParts['pageid'] = $id;
-        $this->linkParts['anchor'] = $anchor;
-
         return true;
     }
 
@@ -96,12 +92,12 @@ class PageLinkHandler extends AbstractLinkHandler implements LinkHandlerInterfac
         $lang = $this->getLanguageService();
         $titleLen = (int)$this->getBackendUser()->uc['titleLen'];
 
-        $id = $this->linkParts['pageid'];
+        $id = $this->linkParts['url']['pageuid'];
         $pageRow = BackendUtility::getRecordWSOL('pages', $id);
 
         return htmlspecialchars($lang->getLL('page'))
             . ' \'' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($pageRow['title'], $titleLen)) . '\''
-            . ' (ID:' . $id . ($this->linkParts['anchor'] ? ', #' . $this->linkParts['anchor'] : '') . ')';
+            . ' (ID: ' . $id . ($this->linkParts['url']['fragment'] ? ', #' . $this->linkParts['url']['fragment'] : '') . ')';
     }
 
     /**
@@ -143,9 +139,9 @@ class PageLinkHandler extends AbstractLinkHandler implements LinkHandlerInterfac
     protected function getRecordsOnExpandedPage($pageId)
     {
         // If there is an anchor value (content element reference) in the element reference, then force an ID to expand:
-        if (!$pageId && isset($this->linkParts['anchor'])) {
+        if (!$pageId && isset($this->linkParts['url']['fragment'])) {
             // Set to the current link page id.
-            $pageId = $this->linkParts['pageid'];
+            $pageId = $this->linkParts['url']['pageuid'];
         }
         // Draw the record list IF there is a page id to expand:
         if ($pageId && MathUtility::canBeInterpretedAsInteger($pageId) && $this->getBackendUser()->isInWebMount($pageId)) {
@@ -179,7 +175,8 @@ class PageLinkHandler extends AbstractLinkHandler implements LinkHandlerInterfac
 
             // Enrich list of records
             foreach ($contentElements as &$contentElement) {
-                $contentElement['isSelected'] = !empty($this->linkParts) && (int)$this->linkParts['anchor'] === (int)$contentElement['uid'];
+                $contentElement['url'] = GeneralUtility::makeInstance(LinkService::class)->asString(['type' => LinkService::TYPE_PAGE, 'pageuid' => (int)$pageId, 'fragment' => $contentElement['uid']]);
+                $contentElement['isSelected'] = !empty($this->linkParts) && (int)$this->linkParts['url']['fragment'] === (int)$contentElement['uid'];
                 $contentElement['icon'] = $this->iconFactory->getIconForRecord('tt_content', $contentElement, Icon::SIZE_SMALL)->render();
                 $contentElement['title'] = BackendUtility::getRecordTitle('tt_content', $contentElement, true);
             }
@@ -234,11 +231,15 @@ class PageLinkHandler extends AbstractLinkHandler implements LinkHandlerInterfac
      */
     public function getBodyTagAttributes()
     {
-        if (empty($this->linkParts)) {
+        if (count($this->linkParts) === 0 || empty($this->linkParts['url']['pageuid'])) {
             return [];
         }
         return [
-            'data-current-link' => $this->linkParts['pageid'] . ($this->linkParts['anchor'] !== '' ? '#' . $this->linkParts['anchor'] : '')
+            'data-current-link' => GeneralUtility::makeInstance(LinkService::class)->asString([
+                'type' => LinkService::TYPE_PAGE,
+                'pageuid' => (int)$this->linkParts['url']['pageuid'],
+                'fragment' => $this->linkParts['url']['fragment']
+            ])
         ];
     }
 
@@ -262,7 +263,7 @@ class PageLinkHandler extends AbstractLinkHandler implements LinkHandlerInterfac
      */
     public function isCurrentlySelectedItem(array $values)
     {
-        return !empty($this->linkParts) && (int)$this->linkParts['pageid'] === (int)$values['pid'];
+        return !empty($this->linkParts) && (int)$this->linkParts['url']['pageuid'] === (int)$values['pid'];
     }
 
     /**
index c1eabb9..a6021fd 100644 (file)
@@ -61,8 +61,8 @@ class UrlLinkHandler extends AbstractLinkHandler implements LinkHandlerInterface
         if (!$linkParts['url']) {
             return false;
         }
-        if (strpos($linkParts['url'], '://') === false) {
-            $linkParts['url'] = 'http://' . $linkParts['url'];
+        if (isset($linkParts['url']['url'])) {
+            $linkParts['url'] = $linkParts['url']['url'];
         }
         $this->linkParts = $linkParts;
 
index 0a998d1..599b93a 100644 (file)
@@ -19,7 +19,7 @@
                                        <f:for each="{itemsInSelectedFolder}" as="file">
                                                <li{f:if(condition: '{file.uid} == {currentIdentifier}', then: ' class="active"')}>
                                                         <span class="list-tree-group">
-                                                                       <a href="#" class="t3js-fileLink list-tree-group" title="{file.name}" data-file="file:{file.uid}">
+                                                                       <a href="{file.url}" class="t3js-fileLink list-tree-group" title="{file.name}">
                                                                                <span class="list-tree-icon">
                                                                                        <span title="{file.name} ({file.size})">{file.icon -> f:format.raw()}</span>
                                                                                </span>
index 4e6314b..1bcc75b 100644 (file)
@@ -15,7 +15,7 @@
                                        <f:for each="{itemsInSelectedFolder}" as="folder">
                                                <li{f:if(condition: '{folder.identifier} == {currentIdentifier}', then: ' class="active"')}>
                                                         <span class="list-tree-group">
-                                                                       <a href="#" class="t3js-fileLink list-tree-group" title="{folder.name}" data-file="file:{folder.identifier}">
+                                                                       <a href="{folder.url}" class="t3js-fileLink list-tree-group" title="{folder.name}">
                                                                                <span class="list-tree-icon">
                                                                                        <span title="{folder.name}">{folder.icon -> f:format.raw()}</span>
                                                                                </span>
index f9e5886..58da4a9 100644 (file)
@@ -25,7 +25,7 @@
                                                                <span class="list-tree-group">
                                                                        <span class="list-tree-icon">{content.icon -> f:format.raw()}</span>
                                                                        <span class="list-tree-title">
-                                                                               <a href="#" class="t3js-pageLink" data-id="{activePage.uid}" data-anchor="#{content.uid}">
+                                                                               <a href="{content.url}" class="t3js-pageLink">
                                                                                        {content.title}
                                                                                </a>
                                                                        </span>
index 847c2a8..ae444a7 100644 (file)
@@ -33,8 +33,7 @@ define(['jquery', 'TYPO3/CMS/Recordlist/LinkBrowser', 'TYPO3/CMS/Backend/LegacyT
         */
        FileLinkHandler.linkFile = function(event) {
                event.preventDefault();
-
-               LinkBrowser.finalizeFunction($(this).data('file'));
+               LinkBrowser.finalizeFunction($(this).attr('href'));
        };
 
        /**
@@ -43,13 +42,11 @@ define(['jquery', 'TYPO3/CMS/Recordlist/LinkBrowser', 'TYPO3/CMS/Backend/LegacyT
         */
        FileLinkHandler.linkCurrent = function(event) {
                event.preventDefault();
-
                LinkBrowser.finalizeFunction(FileLinkHandler.currentLink);
        };
 
        $(function() {
                FileLinkHandler.currentLink = $('body').data('currentLink');
-
                $('a.t3js-fileLink').on('click', FileLinkHandler.linkFile);
                $('input.t3js-linkCurrent').on('click', FileLinkHandler.linkCurrent);
        });
index 572a56a..6e07cb0 100644 (file)
@@ -33,11 +33,7 @@ define(['jquery', 'TYPO3/CMS/Recordlist/LinkBrowser'], function($, LinkBrowser)
         */
        PageLinkHandler.linkPage = function(event) {
                event.preventDefault();
-
-               var id = $(this).data('id');
-               var anchor = $(this).data('anchor');
-
-               LinkBrowser.finalizeFunction('page:' + id + (anchor ? anchor : ''));
+               LinkBrowser.finalizeFunction($(this).attr('href'));
        };
 
        /**
@@ -52,7 +48,7 @@ define(['jquery', 'TYPO3/CMS/Recordlist/LinkBrowser'], function($, LinkBrowser)
                        return;
                }
 
-               LinkBrowser.finalizeFunction('page:' + value);
+               LinkBrowser.finalizeFunction(value);
        };
 
        /**
@@ -61,8 +57,7 @@ define(['jquery', 'TYPO3/CMS/Recordlist/LinkBrowser'], function($, LinkBrowser)
         */
        PageLinkHandler.linkCurrent = function(event) {
                event.preventDefault();
-
-               LinkBrowser.finalizeFunction('page:' + PageLinkHandler.currentLink);
+               LinkBrowser.finalizeFunction(PageLinkHandler.currentLink);
        };
 
        $(function() {
index 073aa2e..016aa33 100644 (file)
@@ -16,6 +16,7 @@ namespace TYPO3\CMS\Rtehtmlarea\Controller;
 
 use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\LinkHandling\LinkService;
 use TYPO3\CMS\Core\Page\PageRenderer;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Lang\LanguageService;
@@ -215,6 +216,14 @@ class BrowseLinksController extends AbstractLinkBrowserController
             return;
         }
 
+        if (!empty($this->currentLinkParts['url'])) {
+            $linkService = GeneralUtility::makeInstance(LinkService::class);
+            $data = $linkService->resolve($this->currentLinkParts['url']);
+            $this->currentLinkParts['type'] = $data['type'];
+            unset($data['type']);
+            $this->currentLinkParts['url'] = $data;
+        }
+
         if (!empty($this->currentLinkParts['class'])) {
             // remove required classes
             $currentClasses = GeneralUtility::trimExplode(' ', $this->currentLinkParts['class'], true);
@@ -222,33 +231,6 @@ class BrowseLinksController extends AbstractLinkBrowserController
                 $this->currentLinkParts['class'] = end($currentClasses);
             }
         }
-
-        if (empty($this->currentLinkParts['data-htmlarea-external']) && strpos($this->currentLinkParts['url'], 'mailto:') === false) {
-            // strip siteUrl prefix except for external and mail links
-            $paramsPosition = strpos($this->currentLinkParts['url'], '?');
-            if ($paramsPosition !== false) {
-                $this->currentLinkParts['url'] = substr($this->currentLinkParts['url'], $paramsPosition + 1);
-            }
-            // special treatment for page links, remove the id= part
-            $idPosition = strpos($this->currentLinkParts['url'], 'id=');
-            if ($idPosition !== false) {
-                $this->currentLinkParts['url'] = substr($this->currentLinkParts['url'], $idPosition + 3);
-            }
-
-            // in RTE the additional params are encoded directly at the end of the href part
-            // we need to split this again into dedicated fields
-            $additionalParamsPosition = strpos($this->currentLinkParts['url'], '?');
-            if ($additionalParamsPosition === false) {
-                $additionalParamsPosition = strpos($this->currentLinkParts['url'], '&');
-            }
-            if ($additionalParamsPosition !== false) {
-                $this->currentLinkParts['params'] = substr($this->currentLinkParts['url'], $additionalParamsPosition);
-                $this->currentLinkParts['url'] = substr($this->currentLinkParts['url'], 0, $additionalParamsPosition);
-                // in case the first sign was an ? override it with &
-                $this->currentLinkParts['params'][0] = '&';
-            }
-        }
-
         parent::initCurrentUrl();
     }