diff --git a/CHANGELOG.md b/CHANGELOG.md index e057cf9..2f70137 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [4.0.1] - unreleased + +### Fixed +- Pre-filter CMS content before it is passed to Summernote editor + +### Added +- `HtmlTagRemover` class + ## [4.0.0] - 2024-03-12 ### Changed diff --git a/composer.json b/composer.json index 403fb60..c6eda20 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "require-dev": { "phpstan/phpstan": "^1.8.11", "squizlabs/php_codesniffer": "3.*", - "oxid-esales/oxideshop-ce": "dev-b-7.1.x", + "oxid-esales/oxideshop-ce": "dev-b-7.1.x-add_content_html_filter-OXDEV-7182", "phpunit/phpunit": "^10.4", "codeception/codeception": "^5.0", "codeception/module-asserts": "^3.0", diff --git a/src/Service/EditorRenderer.php b/src/Service/EditorRenderer.php index a0c5f1c..cd6fe1a 100644 --- a/src/Service/EditorRenderer.php +++ b/src/Service/EditorRenderer.php @@ -9,6 +9,7 @@ namespace OxidEsales\WysiwygModule\Service; +use OxidEsales\EshopCommunity\Internal\Framework\Templating\HtmlFilter\HtmlFilter; use OxidEsales\EshopCommunity\Internal\Framework\Templating\TemplateRendererInterface; class EditorRenderer implements EditorRendererInterface @@ -30,7 +31,7 @@ public function render( 'iEditorWidth' => $this->prepareSize($width), 'iEditorHeight' => $this->prepareSize($height), 'sEditorField' => $fieldName, - 'sEditorValue' => $objectValue, + 'sEditorValue' => $this->filterContent($objectValue), 'langabbr' => $this->settingsService->getInterfaceLanguageAbbreviation(), 'blTextEditorDisabled' => $isEditorDisabled, 'oViewConf' => $this->settingsService->getActiveViewConfig(), @@ -52,4 +53,10 @@ private function checkIfOnlyDigitsInValue(string $sizeValue): bool { return (bool)preg_match("/^\d+$/i", $sizeValue); } + + private function filterContent(string $content): string + { + $filter = new HtmlFilter(new HtmlTagRemover()); + return $filter->filter($content); + } } diff --git a/src/Service/HtmlTagRemover.php b/src/Service/HtmlTagRemover.php new file mode 100644 index 0000000..cca840c --- /dev/null +++ b/src/Service/HtmlTagRemover.php @@ -0,0 +1,25 @@ +parentNode; + while ($node->hasChildNodes()) { + $parent->insertBefore($node->lastChild, $node->nextSibling); + } + $parent->removeChild($node); + } +} diff --git a/tests/Codeception/Acceptance/TextareaCheckCest.php b/tests/Codeception/Acceptance/TextareaCheckCest.php index b8227ff..332fbff 100644 --- a/tests/Codeception/Acceptance/TextareaCheckCest.php +++ b/tests/Codeception/Acceptance/TextareaCheckCest.php @@ -25,4 +25,35 @@ public function productDescriptionTextAreaModified(AcceptanceTester $I): void $I->seeElementInDOM("#ddoew #editor_oxarticles__oxlongdesc"); } + + public function contentIsFiltered(AcceptanceTester $I): void + { + $loadId = 'test_content'; + $template = "
par 1
par 2
"; + + $I->haveInDatabase('oxcontents', [ + 'OXID' => md5($loadId), + 'OXLOADID' => $loadId, + 'OXCONTENT' => $template, + 'OXCONTENT_1' => $template, + 'OXCONTENT_2' => $template, + 'OXCONTENT_3' => $template, + ]); + + $adminPanel = $I->loginAdmin(); + $adminPanel->openCMSPages(); + + $I->selectListFrame(); + $I->fillField("//input[@name='where[oxcontents][oxloadid]']", $loadId); + $I->submitForm('#search', []); + + $I->selectListFrame(); + $I->click($loadId); + + $I->selectEditFrame(); + $I->waitForDocumentReadyState(); + + $isVarDefined = $I->executeJS("return typeof filterTest !== 'undefined'"); + $I->assertFalse($isVarDefined); + } } diff --git a/tests/Unit/Service/EditorRendererTest.php b/tests/Unit/Service/EditorRendererTest.php index da31605..bd687c2 100644 --- a/tests/Unit/Service/EditorRendererTest.php +++ b/tests/Unit/Service/EditorRendererTest.php @@ -13,6 +13,7 @@ use OxidEsales\EshopCommunity\Internal\Framework\Templating\TemplateRendererInterface; use OxidEsales\WysiwygModule\Service\EditorRenderer; use OxidEsales\WysiwygModule\Service\SettingsInterface; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class EditorRendererTest extends TestCase @@ -197,7 +198,51 @@ public function testRenderCalledWithActiveViewConfig(): void $sut->render('any', 'any', 'any', 'any'); } - public function getSut( + #[DataProvider('filterTemplateProvider')] + public function testFilterContent(string $template, string $expectedTemplate): void + { + $templateRendererSpy = $this->createMock(TemplateRendererInterface::class); + $templateRendererSpy + ->expects($this->once()) + ->method('renderTemplate') + ->with( + '@ddoewysiwyg/ddoewysiwyg', + $this->callback(function ($context) use ($expectedTemplate) { + return $expectedTemplate == $context['sEditorValue']; + }) + ); + + $sut = $this->getSut($templateRendererSpy); + $sut->render('any', 'any', $template, 'any'); + } + + public static function filterTemplateProvider(): array + { + return [ + [ + 'template' => 'plain template', + 'expectedTemplate' => 'plain template', + ], + [ + 'template' => 'par 1
par 2
', + 'expectedTemplate' => 'par 1
//js1par 2
', + ], + [ + 'template' => '', + 'expectedTemplate' => '//js1//js2', + ], + [ + 'template' => 'par 1
par 2
', + 'expectedTemplate' => 'par 1
par 2
', + ], + ]; + } + + private function getSut( TemplateRendererInterface $templateRenderer = null, SettingsInterface $settingsService = null, ): EditorRenderer { diff --git a/tests/Unit/Service/HtmlTagRemoverTest.php b/tests/Unit/Service/HtmlTagRemoverTest.php new file mode 100644 index 0000000..83d3339 --- /dev/null +++ b/tests/Unit/Service/HtmlTagRemoverTest.php @@ -0,0 +1,54 @@ +loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); + $node = $doc->getElementsByTagName($node)->item(0); + + $remover = new HtmlTagRemover(); + $remover->remove($node); + + $this->assertEquals($expectedHtml, rtrim($doc->saveHTML())); + } + + public static function htmlProvider(): array + { + return [ + [ + 'node' => 'span', + 'html' => '