Project

General

Profile

Actions

Bug #92301

closed

Elements getting wrapped with p tags, even if allowed outside paragraph

Added by Tobias Ulber over 3 years ago. Updated over 1 year ago.

Status:
Closed
Priority:
Should have
Assignee:
-
Category:
RTE (rtehtmlarea + ckeditor)
Target version:
-
Start date:
2020-09-13
Due date:
% Done:

100%

Estimated time:
TYPO3 Version:
10
PHP Version:
Tags:
Complexity:
Is Regression:
Sprint Focus:

Description

Let's assume we have a CKEditor plugin/widget that generates the following code

<div>
<time datetime="2001-05-15T22:00">Tuesday, 15. May 2001, 10:00 Uhr</time>
</div>

After saving, this code gets correctly inserted into the database. But after that,
Typo3 loads the following code inside the RTE, even if the time tag is allowed outside of a paragraph via tsconfig

<div>
<p><time datetime="2001-05-15T22:00">Tuesday, 15. May 2001, 10:00 Uhr</time></p>
</div>

After saving the record again, the code is also wrong in the database.

I managed to fix this issue after hours of php debugging.
The problem is located in the RteHtmlParser(typo3/sysext/core/Classes/Html/RteHtmlParser.php).
The allowedTagsOutsideOfParagraphs property of the RteHtmlParser class, is not getting queried before the call of the setDivTags method inside the TS_transform_rte method.
I created a Typo3 fork, where I fixed the problem.

Edit:
I created a better patch.

For anyone who stumbles across this issue, I managed to create a workaround.
I've just created a custom transformation class that inherits from the RteHtmlParser class.

<?php

namespace Vendor\Sitepackage\Service;

use Psr\EventDispatcher\EventDispatcherInterface;
use TYPO3\CMS\Core\Html\RteHtmlParser;

class RteTransformation extends RteHtmlParser
{
    /**
     * NOTE: must be public as it is accessed by \TYPO3\CMS\Core\Html\RteHtmlParser without API
     *
     * @var \TYPO3\CMS\Core\Html\RteHtmlParser
     */
    public $pObj;

    /**
     * NOTE: must be public as it is accessed by \TYPO3\CMS\Core\Html\RteHtmlParser without API
     *
     * @var string
     */
    public $transformationKey = 'tx_sitepackage_transformation';

    /**
     * @var array
     */
    protected $configuration;

    public function __construct(EventDispatcherInterface $eventDispatcher = null)
    {
        $this->eventDispatcher = $eventDispatcher;
    }

    protected function loadConfiguration()
    {
        $this->configuration = $this->pObj->procOptions['usertrans.'][$this->transformationKey . '.'];
        $this->setProcessingConfiguration($this->pObj->procOptions);
    }

    /**
     * Transforms RTE content prior to database storage
     *
     * @param string $value RTE HTML to clean for database storage
     * @return string
     */
    public function transform_db($value)
    {
        $this->loadConfiguration();
        // Transform empty paragraphs into spacing paragraphs
        $value = str_replace('<p></p>', '<p>&nbsp;</p>', $value);
        // Double any trailing spacing paragraph so that it does not get removed by divideIntoLines()
        $value = preg_replace('/<p>&nbsp;<\/p>$/', '<p>&nbsp;</p><p>&nbsp;</p>', $value);
        return $this->TS_transform_db($value);
    }

    /**
     * Transforms database content for RTE display
     *
     * @param string $value Database content to transform into RTE-ready HTML
     * @return string
     */
    public function transform_rte($value)
    {
        $this->loadConfiguration();
        return $this->TS_transform_rte($value);
    }

    /**
     * OVERRIDES DEFAULT METHOD IN RteHtmlParser.php!!!
     * Converts all lines into <p></p>-sections (unless the line has a p - tag already)
     * For processing of content going FROM database TO RTE.
     *
     * @param string $value Value to convert
     * @return string Processed value.
     * @see divideIntoLines()
     */
    protected function setDivTags($value)
    {
        // First, setting configuration for the HTMLcleaner function. This will process each line between the <div>/<p> section on their way to the RTE
        $keepTags = $this->getKeepTags('rte');
        // Divide the content into lines
        $parts = explode(LF, $value);
        foreach ($parts as $k => $v) {
            // Processing of line content:
            // If the line is blank, set it to &nbsp;
            if (trim($parts[$k]) === '') {
                $parts[$k] = '&nbsp;';
            } else {
                // Clean the line content, keeping unknown tags (as they can be removed in the entryHTMLparser)
                $parts[$k] = $this->HTMLcleaner($parts[$k], $keepTags, 'protect');
                // convert double-encoded &nbsp; into regular &nbsp; however this could also be reversed via the exitHTMLparser
                // This was previously an option to disable called "dontConvAmpInNBSP_rte" 
                $parts[$k] = str_replace('&amp;nbsp;', '&nbsp;', $parts[$k]);
            }
            $partFTN = strtolower($this->getFirstTagName($parts[$k] ?? ''));
            // Filter out the hr
            $allowedTagsOutsideOfParagraphs = array_filter($this->allowedTagsOutsideOfParagraphs, function ($tag) {
                return $tag != 'hr';
            });
            // Wrapping the line in <p> tags if not already wrapped and does not contain an hr tag and is not allowed outside of Paragraphs
            if (!preg_match('/<(hr)(\\s[^>\\/]*)?[[:space:]]*\\/?>/i', $parts[$k]) && !in_array($partFTN, $allowedTagsOutsideOfParagraphs)) {
                $testStr = strtolower(trim($parts[$k]));
                if (strpos($testStr, '<div') !== 0 || substr($testStr, -6) !== '</div>') {
                    if (strpos($testStr, '<p') !== 0 || substr($testStr, -4) !== '</p>') {
                        // Only set p-tags if there is not already div or p tags:
                        $parts[$k] = '<p>' . $parts[$k] . '</p>';
                    }
                }
            }
        }
        // Implode result:
        return implode(LF, $parts);
    }
}


Notice that I have overrode the default setDivTags method with my changes.

Don't forget to add this line to your ext_localconf.php

/***************
 * Register Custom Transformation
 */
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_parsehtml_proc.php']['transformation']['tx_sitepackage_transformation'] = Vendor\Sitepackage\Service\RteTransformation::class;

This is how you can use the custom transformation in the RTE.tsconfig
config.tt_content.bodytext.proc.overruleMode = detectbrokenlinks,tx_sitepackage_transformation,ts_links

Have a look at https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/Rte/Transformations/CustomApi.html if you need more informations on how to use custom transformations.
Here are a few more infomations on how to use custom transformations https://docs.typo3.org/m/typo3/reference-coreapi/7.6/en-us/_sources/Rte/Transformations/CustomApi/Index.rst.txt?refid=transformations-custom&line=25


Related issues 1 (0 open1 closed)

Related to TYPO3 Core - Bug #85038: allowTagsOutside not working in rte_ckeditorClosed2018-05-17

Actions
Actions

Also available in: Atom PDF