[BUGFIX] RTE: Support anchors without `href` 24/60324/7
authorThorben Nissen <thorben.nissen@kapp-hamburg.de>
Sun, 24 Mar 2019 16:06:07 +0000 (17:06 +0100)
committerBenni Mack <benni@typo3.org>
Sat, 9 Nov 2019 13:39:26 +0000 (14:39 +0100)
Removes enforcing of href attribute when storing RTE text into the
database. Removes adding of absolute scheme on a tags without href
attribute, when loading text from the database. Changes
ContentObjectRenderer::typolink to render a tag without href anyway, if
id or name attribute is present.
Adds unit tests.

Resolves: #87992
Releases: master, 9.5
Change-Id: I4dcd33e6f13dc6a1f364c96b425aa2f241653ae9
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/60324
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Susanne Moog <look@susi.dev>
Tested-by: Guido Schmechel <guido.schmechel@brandung.de>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Susanne Moog <look@susi.dev>
Reviewed-by: Guido Schmechel <guido.schmechel@brandung.de>
Reviewed-by: Benni Mack <benni@typo3.org>
typo3/sysext/core/Classes/Html/RteHtmlParser.php
typo3/sysext/core/Tests/Unit/Html/RteHtmlParserTest.php
typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php

index 22c09fd..0ee8b66 100644 (file)
@@ -338,6 +338,11 @@ class RteHtmlParser extends HtmlParser implements LoggerAwareInterface
         foreach ($blockSplit as $k => $v) {
             if ($k % 2) {
                 list($tagAttributes) = $this->get_tag_attributes($this->getFirstTag($v), true);
+
+                // Anchors would not have an href attribute
+                if (!isset($tagAttributes['href'])) {
+                    continue;
+                }
                 $linkService = GeneralUtility::makeInstance(LinkService::class);
                 $linkInformation = $linkService->resolve($tagAttributes['href'] ?? '');
 
index cab4f5f..8164325 100644 (file)
@@ -631,4 +631,82 @@ class RteHtmlParserTest extends UnitTestCase
         $thisConfig = ['proc.' => $this->procOptions];
         self::assertEquals($expectedResult, $subject->RTE_transform($subject->RTE_transform($content, [], 'db', $thisConfig), [], 'rte', $thisConfig));
     }
+
+    /**
+     * Data provider for anchorCorrectlyTransformedOnWayToDatabase
+     */
+    public static function anchorCorrectlyTransformedOnWayToDatabaseProvider()
+    {
+        return [
+            [
+                '<p><a name="some_anchor"></a></p>' . CRLF . '<h3>Some headline here</h3>',
+                '<p><a name="some_anchor"></a></p>' . CRLF . '<h3>Some headline here</h3>'
+            ],
+            [
+                '<p><a id="some_anchor"></a></p>' . CRLF . '<h3>Some headline here</h3>',
+                '<p><a id="some_anchor"></a></p>' . CRLF . '<h3>Some headline here</h3>'
+            ],
+            [
+                '<p><a name="some_anchor" id="some_anchor"></a></p>' . CRLF . '<h3>Some headline here</h3>',
+                '<p><a name="some_anchor" id="some_anchor"></a></p>' . CRLF . '<h3>Some headline here</h3>'
+            ],
+            [
+                '<p><a id="some_anchor">Some text inside the anchor</a></p>',
+                '<p><a id="some_anchor">Some text inside the anchor</a></p>'
+            ]
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider anchorCorrectlyTransformedOnWayToDatabaseProvider
+     * @param $content
+     * @param $expectedResult
+     */
+    public function anchorCorrectlyTransformedOnWayToDatabase($content, $expectedResult)
+    {
+        $eventDispatcher = $this->createMock(EventDispatcherInterface::class);
+        $subject = new RteHtmlParser($eventDispatcher);
+        $thisConfig = ['proc.' => $this->procOptions];
+        self::assertEquals($expectedResult, $subject->RTE_transform($content, [], 'db', $thisConfig));
+    }
+
+    /**
+     * Data provider for anchorCorrectlyTransformedOnWayToDatabaseAndBackToRTE
+     */
+    public static function anchorCorrectlyTransformedOnWayToDatabaseAndBackToRTEProvider()
+    {
+        return [
+            [
+                '<p><a name="some_anchor"></a></p>' . CRLF . '<h3>Some headline here</h3>',
+                '<p><a name="some_anchor"></a></p>' . CRLF . '<h3>Some headline here</h3>'
+            ],
+            [
+                '<p><a id="some_anchor"></a></p>' . CRLF . '<h3>Some headline here</h3>',
+                '<p><a id="some_anchor"></a></p>' . CRLF . '<h3>Some headline here</h3>'
+            ],
+            [
+                '<p><a name="some_anchor" id="some_anchor"></a></p>' . CRLF . '<h3>Some headline here</h3>',
+                '<p><a name="some_anchor" id="some_anchor"></a></p>' . CRLF . '<h3>Some headline here</h3>'
+            ],
+            [
+                '<p><a id="some_anchor">Some text inside the anchor</a></p>',
+                '<p><a id="some_anchor">Some text inside the anchor</a></p>'
+            ]
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider anchorCorrectlyTransformedOnWayToDatabaseAndBackToRTEProvider
+     * @param $content
+     * @param $expectedResult
+     */
+    public function anchorCorrectlyTransformedOnWayToDatabaseAndBackToRTE($content, $expectedResult)
+    {
+        $eventDispatcher = $this->createMock(EventDispatcherInterface::class);
+        $subject = new RteHtmlParser($eventDispatcher);
+        $thisConfig = ['proc.' => $this->procOptions];
+        self::assertEquals($expectedResult, $subject->RTE_transform($subject->RTE_transform($content, [], 'db', $thisConfig), [], 'rte', $thisConfig));
+    }
 }
index b0906cf..72fb858 100644 (file)
@@ -5026,7 +5026,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
         $title = $resolvedLinkParameters['title'];
 
         if (!$linkParameter) {
-            return $linkText;
+            return $this->resolveAnchorLink($linkText, $conf);
         }
 
         // Detecting kind of link and resolve all necessary parameters
@@ -7150,4 +7150,25 @@ class ContentObjectRenderer implements LoggerAwareInterface
     {
         return $this->typoScriptFrontendController ?: $GLOBALS['TSFE'];
     }
+
+    /**
+     * Support anchors without href value
+     * Changes ContentObjectRenderer::typolink to render a tag without href,
+     * if id or name attribute is present.
+     *
+     * @param string $linkText
+     * @param array $conf Typolink configuration decoded as array
+     * @return string Full a-Tag or just the linktext if id or name are not set.
+     */
+    protected function resolveAnchorLink(string $linkText, array $conf): string
+    {
+        $anchorTag = '<a ' . $this->getATagParams($conf) . '>';
+        $aTagParams = GeneralUtility::get_tag_attributes($anchorTag);
+        // If it looks like a anchor tag, render it anyway
+        if (isset($aTagParams['id']) || isset($aTagParams['name'])) {
+            return $anchorTag . $linkText . '</a>';
+        }
+        // Otherwise just return the link text
+        return $linkText;
+    }
 }