Bug #92301
Updated by Tobias Ulber about 4 years ago
Let's assume we have a CKEditor plugin/widget that generates the following code <pre><code class="html"> <div> <time datetime="2001-05-15T22:00">Tuesday, 15. May 2001, 10:00 Uhr</time> </div> </code></pre> 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 <pre><code class="html"> <div> <p><time datetime="2001-05-15T22:00">Tuesday, 15. May 2001, 10:00 Uhr</time></p> </div> </code></pre> After saving the record again, the code is also wrong in the database. -I 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: problem. 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. <pre><code class="php"> <?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> </p>', $value); // Double any trailing spacing paragraph so that it does not get removed by divideIntoLines() $value = preg_replace('/<p> <\/p>$/', '<p> </p><p> </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 if (trim($parts[$k]) === '') { $parts[$k] = ' '; } 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 into regular however this could also be reversed via the exitHTMLparser // This was previously an option to disable called "dontConvAmpInNBSP_rte" $parts[$k] = str_replace('&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); } } </code></pre> Notice that I have overrode the default setDivTags method with my changes. Don't forget to add this line to your ext_localconf.php <pre><code class="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; </code></pre> This is how you can use the custom transformation in the RTE.tsconfig RTE.config.tt_content.bodytext.proc.HTMLparser_db.overruleMode = tx_sitepackage_transformation,detectbrokenlinks,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 https://github.com/kosimas/TYPO3.CMS/commit/10c8ca4f9ad6fa6f51da09faf12079bca8ed0640