tags. Because
tags are wrapped currently in a special handling, * they have a special place for configuration via 'proc.keepPDIVattribs' */ protected array $allowedAttributesForParagraphTags = [ 'class', 'align', 'id', 'title', 'dir', 'lang', 'xml:lang', 'itemscope', 'itemtype', 'itemprop', ]; /** * Any tags that are allowed outside of
sections - usually similar to the block elements * plus some special tags like
tags if (isset($this->procOptions['allowAttributes.'])) { $this->allowedAttributesForParagraphTags = $this->procOptions['allowAttributes.']; } // Override tags which are allowed outside of
tags if (isset($this->procOptions['allowTagsOutside'])) { if (!isset($this->procOptions['allowTagsOutside.'])) { $this->allowedTagsOutsideOfParagraphs = GeneralUtility::trimExplode(',', strtolower($this->procOptions['allowTagsOutside']), true); } else { $this->allowedTagsOutsideOfParagraphs = (array)$this->procOptions['allowTagsOutside.']; } } } /** * Main entry point for transforming RTE content in the database so the Rich Text Editor can deal with * e.g. links. */ public function transformTextForRichTextEditor(string $value, array $processingConfiguration): string { $initialValue = $value; $this->setProcessingConfiguration($processingConfiguration); $modes = $this->resolveAppliedTransformationModes('rte'); $beforeTransformTextForRichTextEditorEvent = new BeforeTransformTextForRichTextEditorEvent( $value, $initialValue, $processingConfiguration ); $this->eventDispatcher->dispatch($beforeTransformTextForRichTextEditorEvent); $value = $beforeTransformTextForRichTextEditorEvent->getHtmlContent(); $value = $this->streamlineLineBreaksForProcessing($value); // If an entry HTML cleaner was configured, pass the content through the HTMLcleaner $value = $this->runHtmlParserIfConfigured($value, 'entryHTMLparser_rte'); // Traverse modes foreach ($modes as $cmd) { switch ($cmd) { case 'detectbrokenlinks': $value = $this->markBrokenLinks($value); break; case 'css_transform': $value = $this->TS_transform_rte($value); break; default: // Do nothing } } // If an exit HTML cleaner was configured, pass the content through the HTMLcleaner $value = $this->runHtmlParserIfConfigured($value, 'exitHTMLparser_rte'); // Final clean up of linebreaks $value = $this->streamlineLineBreaksAfterProcessing($value); $afterTransformTextForRichTextEditorEvent = new AfterTransformTextForRichTextEditorEvent( $value, $initialValue, $processingConfiguration ); $this->eventDispatcher->dispatch($afterTransformTextForRichTextEditorEvent); return $afterTransformTextForRichTextEditorEvent->getHtmlContent(); } /** * Called to process HTML content before it is stored in the database. */ public function transformTextForPersistence(string $value, array $processingConfiguration): string { $initialValue = $value; $this->setProcessingConfiguration($processingConfiguration); $modes = $this->resolveAppliedTransformationModes('db'); $beforeTransformTextForPersistenceEvent = new BeforeTransformTextForPersistenceEvent( $value, $initialValue, $processingConfiguration ); $this->eventDispatcher->dispatch($beforeTransformTextForPersistenceEvent); $value = $beforeTransformTextForPersistenceEvent->getHtmlContent(); $value = $this->streamlineLineBreaksForProcessing($value); // If an entry HTML cleaner was configured, pass the content through the HTMLcleaner $value = $this->runHtmlParserIfConfigured($value, 'entryHTMLparser_db'); // Traverse modes foreach ($modes as $cmd) { switch ($cmd) { case 'detectbrokenlinks': $value = $this->removeBrokenLinkMarkers($value); break; case 'ts_links': $value = $this->TS_links_db($value); break; case 'css_transform': // Transform empty paragraphs into spacing paragraphs $value = str_replace('
', '', $value); // Double any trailing spacing paragraph so that it does not get removed by divideIntoLines() $value = preg_replace('/
<\/p>$/', '
', $value) ?? $value; $value = $this->TS_transform_db($value); break; default: // Do nothing } } // process markup with HTML Sanitizer $value = $this->htmlSanitize($value, $this->procOptions['HTMLparser_db.'] ?? []); // If an exit HTML cleaner was configured, pass the content through the HTMLcleaner $value = $this->runHtmlParserIfConfigured($value, 'exitHTMLparser_db'); // Final clean up of linebreaks $value = $this->streamlineLineBreaksAfterProcessing($value); $afterTransformTextForPersistenceEvent = new AfterTransformTextForPersistenceEvent( $value, $initialValue, $processingConfiguration ); $this->eventDispatcher->dispatch($afterTransformTextForPersistenceEvent); return $afterTransformTextForPersistenceEvent->getHtmlContent(); } /** * Ensures what transformation modes should be executed, and that they are only executed once. * * @return array the resolved transformation modes */ protected function resolveAppliedTransformationModes(string $direction): array { // Setting modes / transformations to be called if ((string)($this->procOptions['overruleMode'] ?? '') !== '') { $modes = GeneralUtility::trimExplode(',', $this->procOptions['overruleMode']); } else { $modes = [$this->procOptions['mode']]; } $modeList = implode(',', $modes); // Replace the shortcut "default" with all custom modes $modeList = str_replace('default', 'detectbrokenlinks,css_transform,ts_links', $modeList); // Make list unique $modes = array_unique(GeneralUtility::trimExplode(',', $modeList, true)); // Reverse order if direction is "rte" if ($direction === 'rte') { $modes = array_reverse($modes); } return $modes; } /** * Runs the HTML parser if it is configured * Getting additional HTML cleaner configuration. These are applied either before or after the main transformation * is done and thus totally independent processing options you can set up. * * This is only possible via TSconfig (procOptions) currently. * * @param string $configurationDirective used to look up in the procOptions if enabled, and then fetch the * @return string the processed content */ protected function runHtmlParserIfConfigured(string $content, string $configurationDirective): string { if (!empty($this->procOptions[$configurationDirective])) { [$keepTags, $keepNonMatchedTags, $hscMode, $additionalConfiguration] = $this->HTMLparserConfig($this->procOptions[$configurationDirective . '.']); $content = $this->HTMLcleaner($content, $keepTags, $keepNonMatchedTags, $hscMode, $additionalConfiguration); } return $content; } /************************************ * * Specific RTE TRANSFORMATION functions * *************************************/ /** * Transformation handler: 'ts_links' / direction: "db" * Processing anchor tags, and resolves them correctly again via the LinkService syntax * * Splits content into tag blocks and processes each tag, and allows hooks to actually render * the result. * * @param string $value Content input * @return string Content output */ protected function TS_links_db(string $value): string { $blockSplit = $this->splitIntoBlock('A', $value); foreach ($blockSplit as $k => $v) { if ($k % 2) { [$tagAttributes] = $this->get_tag_attributes($this->getFirstTag($v), true); // Anchors would not have a href attribute if (!isset($tagAttributes['href'])) { continue; } // Store the link as tag as default by TYPO3, with the link service syntax try { $linkInformation = $this->linkService->resolve($tagAttributes['href']); $tagAttributes['href'] = $this->linkService->asString($linkInformation); } catch (UnknownLinkHandlerException $e) { $tagAttributes['href'] = $linkInformation['href'] ?? $tagAttributes['href']; } $blockSplit[$k] = '' . $this->TS_links_db($this->removeFirstAndLastTag($blockSplit[$k])) . ''; } } return implode('', $blockSplit); } /** * Transformation handler: 'css_transform' / direction: "db" * Cleaning (->db) for standard content elements (ts) * * @param string $value Content input * @return string Content output * @see TS_transform_rte() */ protected function TS_transform_db(string $value): string { // Safety... so forever loops are avoided (they should not occur, but an error would potentially do this...) $this->TS_transform_db_safecounter--; if ($this->TS_transform_db_safecounter < 0) { return $value; } // Split the content from RTE by the occurrence of these blocks: $blockSplit = $this->splitIntoBlock($this->blockElementList, $value); // Avoid superfluous linebreaks by transform_db after ending headListTag while (count($blockSplit) > 0 && trim(end($blockSplit)) === '') { array_pop($blockSplit); } // Traverse the blocks foreach ($blockSplit as $k => $v) { if ($k % 2) { // Inside block: // Init: $tag = $this->getFirstTag($v); $tagName = strtolower($this->getFirstTagName($v)); // Process based on the tag: switch ($tagName) { case 'blockquote': case 'dd': case 'div': case 'header': case 'section': case 'footer': case 'nav': case 'article': case 'aside': $blockSplit[$k] = $tag . $this->TS_transform_db($this->removeFirstAndLastTag($blockSplit[$k])) . '' . $tagName . '>'; break; case 'pre': break; default: // usually