From 7439f22773a2a85e0b43fc98259f33b9a9ca08eb Mon Sep 17 00:00:00 2001 From: Glomberg Date: Wed, 29 Apr 2026 10:55:52 +0300 Subject: [PATCH 01/17] Version: 6.78.99-dev. --- cleantalk.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cleantalk.php b/cleantalk.php index cf448745e..00543a409 100644 --- a/cleantalk.php +++ b/cleantalk.php @@ -4,7 +4,7 @@ Plugin Name: Anti-Spam by CleanTalk Plugin URI: https://cleantalk.org Description: Max power, all-in-one, no Captcha, premium anti-spam plugin. No comment spam, no registration spam, no contact spam, protects any WordPress forms. - Version: 6.78 + Version: 6.78.99-dev Author: CleanTalk - Anti-Spam Protection Author URI: https://cleantalk.org Text Domain: cleantalk-spam-protect From f82c996318e012590260836e5a71555bba323478 Mon Sep 17 00:00:00 2001 From: Glomberg Date: Wed, 29 Apr 2026 10:57:03 +0300 Subject: [PATCH 02/17] Version: 6.78.99-fix. --- cleantalk.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cleantalk.php b/cleantalk.php index cf448745e..1dbff17eb 100644 --- a/cleantalk.php +++ b/cleantalk.php @@ -4,7 +4,7 @@ Plugin Name: Anti-Spam by CleanTalk Plugin URI: https://cleantalk.org Description: Max power, all-in-one, no Captcha, premium anti-spam plugin. No comment spam, no registration spam, no contact spam, protects any WordPress forms. - Version: 6.78 + Version: 6.78.99-fix Author: CleanTalk - Anti-Spam Protection Author URI: https://cleantalk.org Text Domain: cleantalk-spam-protect From e21b6d58ff7dadc98a4d338a1b4761cef8dbf57f Mon Sep 17 00:00:00 2001 From: Glomberg Date: Wed, 29 Apr 2026 14:39:40 +0300 Subject: [PATCH 03/17] Fix. Code. Github action fixed. --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d52b4d44f..b97c509de 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -33,6 +33,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ env.PHP_VERSION }} + coverage: xdebug - name: Run MySQL server run: sudo systemctl start mysql From f70df83fd002c275c8e1ad457c4b1b2e303549d5 Mon Sep 17 00:00:00 2001 From: Viktor Date: Wed, 29 Apr 2026 18:23:39 +0300 Subject: [PATCH 04/17] Fix. Code. Unit tests for #760 added. (#779) (#792) * Upd. Settings. `Bot Detector` setting - bot-detector setting removed. (#760) * Upd. Settings. `Bot Detector` setting - added constant. * Upd. Settings. `Bot Detector` setting - setting to data migration added. * Upd. Settings. `Bot Detector` setting - wrapper for bot-detector state checking added. * Upd. Settings. `Bot Detector` setting - bot-detector state checking replaced by wrapper. * Upd. Settings. `Bot Detector` setting - bot-detector setting removed. * Upd. Settings. `Bot Detector` setting - actual state displayed in the `Summary`. * Upd. Settings. `Bot Detector` setting - bot-detector state checking replaced by wrapper #2. * Upd. Code. JS re-minified. * Fix. Settings. Bot-Detector summary description fixed. * Fix. Code. Unit tests fixed: `TestFluentForms`, `TestNinjaForms`. * Fix. Code. Unit test added: `TestCleantalkSettings`. * Fix. Code. Unit test added: `TestSummaryAndSupportRenderer`. * Fix. Code. JS re-minified. * Fix. Code. Unit tests for #760 added. * Fix. CI/CD. GitHub action for testing added. * Fix. Code. Unit tests fixed. * Fix. Code. Unit tests for #760 added #2. * Fix. Code. Unit test for `apbct__is_bot_detector_enabled` fixed. * Fix. Code. Unit test for `apbct__is_bot_detector_enabled` fixed #2. * Fix. Code. Github action fixed. * Fix. Code. Unit test for `apbct__is_bot_detector_enabled` fixed #3. * Fix. Code. Unit test `TestCleantalkSettings` fixed. * Fix. Code. Unit test `TestCleantalkSettings` fixed #2. * Fix. Code. Unit test `TestCleantalkPublic` added. * Fix. Code. Unit test `TestCleantalkPublic` updated. * Fix. Code. Unit test `TestCleantalkPublic` updated #2. * Fix. Code. Unit tests `TestCtPublicFunctionsLocalize` and `TestCtPublicLocalize` added. --- .../TestCtPublicFunctionsLocalize.php | 33 ++++++ .../ApbctWP/Localize/TestCtPublicLocalize.php | 33 ++++++ tests/Inc/TestCleantalkCommon.php | 110 ++++++++++++++++++ tests/Inc/TestCleantalkPublic.php | 80 +++++++++++++ tests/Inc/TestCleantalkSettings.php | 11 ++ tests/Inc/TestCleantalkUpdater.php | 37 ++++++ 6 files changed, 304 insertions(+) create mode 100644 tests/ApbctWP/Localize/TestCtPublicFunctionsLocalize.php create mode 100644 tests/ApbctWP/Localize/TestCtPublicLocalize.php create mode 100644 tests/Inc/TestCleantalkCommon.php create mode 100644 tests/Inc/TestCleantalkPublic.php create mode 100644 tests/Inc/TestCleantalkUpdater.php diff --git a/tests/ApbctWP/Localize/TestCtPublicFunctionsLocalize.php b/tests/ApbctWP/Localize/TestCtPublicFunctionsLocalize.php new file mode 100644 index 000000000..dae16de11 --- /dev/null +++ b/tests/ApbctWP/Localize/TestCtPublicFunctionsLocalize.php @@ -0,0 +1,33 @@ +assertArrayHasKey('bot_detector_enabled', $localize_data); + $this->assertArrayNotHasKey('data__bot_detector_enabled', $localize_data); + } +} \ No newline at end of file diff --git a/tests/ApbctWP/Localize/TestCtPublicLocalize.php b/tests/ApbctWP/Localize/TestCtPublicLocalize.php new file mode 100644 index 000000000..ffa1ca500 --- /dev/null +++ b/tests/ApbctWP/Localize/TestCtPublicLocalize.php @@ -0,0 +1,33 @@ +assertArrayHasKey('bot_detector_enabled', $localize_data); + $this->assertArrayNotHasKey('data__bot_detector_enabled', $localize_data); + } +} \ No newline at end of file diff --git a/tests/Inc/TestCleantalkCommon.php b/tests/Inc/TestCleantalkCommon.php new file mode 100644 index 000000000..35e856693 --- /dev/null +++ b/tests/Inc/TestCleantalkCommon.php @@ -0,0 +1,110 @@ +assertTrue($bot_detector_state); + } + + public function testApbctIsBotDetectorDisabledByData() + { + // Arrange + global $apbct; + $apbct->data['bot_detector_enabled'] = 0; + + // Act + $bot_detector_state = apbct__is_bot_detector_enabled(); + + // Assert + $this->assertFalse($bot_detector_state); + } + + public function testApbctIsBotDetectorEnabledByDataTrue() + { + // Arrange + global $apbct; + $apbct->data['bot_detector_enabled'] = 1; + + // Act + $bot_detector_state = apbct__is_bot_detector_enabled(); + + // Assert + $this->assertTrue($bot_detector_state); + } + + public function testApbctIsBotDetectorEnabledByConstant() + { + // Arrange + global $apbct; + $apbct_constant_mock = $this->getMockBuilder(ApbctConstant::class) + ->onlyMethods(['isDefined', 'getValue']) + ->disableOriginalConstructor() + ->getMock(); + + $apbct_constant_mock->method('isDefined') + ->willReturn(true); + + $apbct_constant_mock->method('getValue') + ->willReturn(true); + $apbct->service_constants = new ServiceConstants(); + $apbct->service_constants->bot_detector_enabled = $apbct_constant_mock; + + // Act + $bot_detector_state = apbct__is_bot_detector_enabled(); + + // Assert + $this->assertTrue($bot_detector_state); + } + + public function testApbctIsBotDetectorDisabledByConstant() + { + // Arrange + global $apbct; + $apbct_constant_mock = $this->getMockBuilder(ApbctConstant::class) + ->onlyMethods(['isDefined', 'getValue']) + ->disableOriginalConstructor() + ->getMock(); + + $apbct_constant_mock->method('isDefined') + ->willReturn(true); + + $apbct_constant_mock->method('getValue') + ->willReturn(false); + $apbct->service_constants = new ServiceConstants(); + $apbct->service_constants->bot_detector_enabled = $apbct_constant_mock; + + // Act + $bot_detector_state = apbct__is_bot_detector_enabled(); + + // Assert + $this->assertFalse($bot_detector_state); + } +} diff --git a/tests/Inc/TestCleantalkPublic.php b/tests/Inc/TestCleantalkPublic.php new file mode 100644 index 000000000..9d073b2cd --- /dev/null +++ b/tests/Inc/TestCleantalkPublic.php @@ -0,0 +1,80 @@ +data['bot_detector_enabled'] = 0; + $apbct->settings['data__pixel'] = '3'; + + // Act + apbct_init(); + + // Assert + $this->assertNotNull($apbct->pixel_url); + } + + public function testApbctInitPixelUrlNotLoad() + { + // Arrange + global $apbct; + $apbct->data['bot_detector_enabled'] = 1; + $apbct->settings['data__pixel'] = '3'; + + // Act + apbct_init(); + + // Assert + $this->assertNull($apbct->pixel_url); + } + + public function testApbctHookWpFooterShowPixel() + { + // Arrange + global $apbct; + $apbct->data['bot_detector_enabled'] = 0; + $apbct->settings['data__pixel'] = '3'; + + // Act + ob_start(); + apbct_hook__wp_footer(); + $output = ob_get_clean(); + + // Assert + $this->assertStringContainsString('img alt="Cleantalk Pixel" title="Cleantalk Pixel" id="apbct_pixel" style="display: none;"', $output); + } + + public function testApbctEnqueueAndLocalizePublicScripts() + { + // Arrange + global $apbct; + $apbct->data['bot_detector_enabled'] = 1; + + // Act + apbct_enqueue_and_localize_public_scripts(); + + // Assert + $this->assertTrue(wp_script_is('ct_bot_detector')); + } +} \ No newline at end of file diff --git a/tests/Inc/TestCleantalkSettings.php b/tests/Inc/TestCleantalkSettings.php index c0e04e939..cd552d9f6 100644 --- a/tests/Inc/TestCleantalkSettings.php +++ b/tests/Inc/TestCleantalkSettings.php @@ -1,5 +1,7 @@ data['bot_detector_enabled'] = 1; } + protected function tearDown(): void + { + global $apbct; + unset($apbct); + parent::tearDown(); + } + public function testApbctSettingsSetFieldsBotDetector() { $fields = apbct_settings__set_fields(); diff --git a/tests/Inc/TestCleantalkUpdater.php b/tests/Inc/TestCleantalkUpdater.php new file mode 100644 index 000000000..ec3abe2f3 --- /dev/null +++ b/tests/Inc/TestCleantalkUpdater.php @@ -0,0 +1,37 @@ +settings['data__bot_detector_enabled'] = 1; + + // Act + apbct_update_to_6_76_0(); + $apbct_rebuilt = new State('cleantalk', array('settings', 'data', 'errors', 'remote_calls', 'stats', 'fw_stats')); + + // Assert + $this->assertEquals('1', $apbct_rebuilt->data['bot_detector_enabled']); + } +} From 53685fc21f7d59861c25609b105f9f094dc798ed Mon Sep 17 00:00:00 2001 From: AntonV1211 Date: Mon, 4 May 2026 12:59:07 +0700 Subject: [PATCH 05/17] Mod. Settings. Moving the AC option --- inc/cleantalk-settings.php | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/inc/cleantalk-settings.php b/inc/cleantalk-settings.php index 8e8d344ea..4905098ca 100644 --- a/inc/cleantalk-settings.php +++ b/inc/cleantalk-settings.php @@ -189,6 +189,23 @@ function apbct_settings__set_fields() ), 'long_description' => true, ), + 'sfw__anti_crawler' => array( + 'type' => 'checkbox', + 'title' => 'Anti-Crawler' . $additional_ac_title, // Do not to localize this phrase + 'class' => 'apbct_settings-field_wrapper', + 'parent' => 'sfw__enabled', + 'description' => + __( + 'Plugin shows SpamFireWall stop page for any bot, except allowed bots (Google, Yahoo and etc).', + 'cleantalk-spam-protect' + ) + . '
' + . __( + 'Anti-Crawler includes blocking bots by the User-Agent. Use Personal lists in the Dashboard to filter specific User-Agents.', + 'cleantalk-spam-protect' + ), + 'long_description' => true, + ), 'data__email_decoder__status' => array( 'type' => 'custom_html', 'title' => __('Encode contact data', 'cleantalk-spam-protect'), @@ -869,23 +886,6 @@ function apbct_settings__set_fields() 'title' => __('Custom logo on SpamFireWall blocking pages', 'cleantalk-spam-protect'), 'parent' => 'sfw__enabled', ), - 'sfw__anti_crawler' => array( - 'type' => 'checkbox', - 'title' => 'Anti-Crawler' . $additional_ac_title, // Do not to localize this phrase - 'class' => 'apbct_settings-field_wrapper', - 'parent' => 'sfw__enabled', - 'description' => - __( - 'Plugin shows SpamFireWall stop page for any bot, except allowed bots (Google, Yahoo and etc).', - 'cleantalk-spam-protect' - ) - . '
' - . __( - 'Anti-Crawler includes blocking bots by the User-Agent. Use Personal lists in the Dashboard to filter specific User-Agents.', - 'cleantalk-spam-protect' - ), - 'long_description' => true, - ), 'sfw__anti_flood' => array( 'type' => 'checkbox', 'title' => 'Anti-Flood', // Do not to localize this phrase From 8b95e4fc4f71ad825c7daf09e9e12c480fa9632c Mon Sep 17 00:00:00 2001 From: alexandergull Date: Tue, 5 May 2026 16:15:33 +0500 Subject: [PATCH 06/17] Fix. Search forms. Add a sign of 's' GET param to a native search form signs. --- .../IntegrationsByClass/WPSearchForm.php | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/lib/Cleantalk/Antispam/IntegrationsByClass/WPSearchForm.php b/lib/Cleantalk/Antispam/IntegrationsByClass/WPSearchForm.php index 4faa697dc..03f302061 100644 --- a/lib/Cleantalk/Antispam/IntegrationsByClass/WPSearchForm.php +++ b/lib/Cleantalk/Antispam/IntegrationsByClass/WPSearchForm.php @@ -2,13 +2,6 @@ namespace Cleantalk\Antispam\IntegrationsByClass; -use Cleantalk\ApbctWP\Escape; -use Cleantalk\ApbctWP\Variables\Post; -use Cleantalk\ApbctWP\Variables\Server; -use Cleantalk\Common\TT; -use Cleantalk\ApbctWP\Sanitize; -use Cleantalk\ApbctWP\Variables\Cookie; -use Cleantalk\ApbctWP\State; use Cleantalk\ApbctWP\Honeypot; use DOMDocument; @@ -32,10 +25,10 @@ public function doAjaxWork() public function doPublicWork() { global $apbct; - if ( $apbct->settings['forms__search_test'] ) { + if ($apbct->settings['forms__search_test']) { add_filter('get_search_form', array($this, 'apbctFormSearchAddFields'), 999); } - if ( ! is_admin() && ! apbct_is_ajax() && ! apbct_is_customize_preview() ) { + if ($this->isNativeSearchFormRequest()) { // Default search add_filter('get_search_query', array($this, 'testSpam')); add_action('wp_head', array($this, 'addNoindex'), 1); @@ -168,4 +161,22 @@ public function addNoindex() echo '' . "\n"; echo '' . "\n"; } + + /** + * Process signs for the default search request. + * - not an admin page + * - not an ajax call + * - not a preview + * - has a 's' param in the GET array + * @return bool + */ + public function isNativeSearchFormRequest() + { + return ( + !is_admin() && + !apbct_is_ajax() && + !apbct_is_customize_preview() && + isset($_GET['s']) // https://app.doboard.com/1/task/47523#comment_305493 + ); + } } From cc18c631d3ed5f74c741aca011d53a062b65f740 Mon Sep 17 00:00:00 2001 From: alexandergull Date: Thu, 7 May 2026 16:40:48 +0500 Subject: [PATCH 07/17] Fix. Contacts Encoder. Shortcodes. Content sanitization improved. --- .../Shortcodes/EncodeContentSC.php | 96 ++++++++++++++++++- .../TestContactsEncoderShortCodeEncode.php | 70 ++++++++++++++ 2 files changed, 162 insertions(+), 4 deletions(-) diff --git a/lib/Cleantalk/ApbctWP/ContactsEncoder/Shortcodes/EncodeContentSC.php b/lib/Cleantalk/ApbctWP/ContactsEncoder/Shortcodes/EncodeContentSC.php index a77d7ff42..7780e623c 100644 --- a/lib/Cleantalk/ApbctWP/ContactsEncoder/Shortcodes/EncodeContentSC.php +++ b/lib/Cleantalk/ApbctWP/ContactsEncoder/Shortcodes/EncodeContentSC.php @@ -3,6 +3,7 @@ namespace Cleantalk\ApbctWP\ContactsEncoder\Shortcodes; use Cleantalk\ApbctWP\ContactsEncoder\ContactsEncoder; +use Cleantalk\ApbctWP\Escape; use Cleantalk\ApbctWP\Variables\Cookie; use Cleantalk\Common\ContactsEncoder\Dto\Params; use Cleantalk\Common\ContactsEncoder\Exclusions\ExclusionsService; @@ -102,24 +103,111 @@ public function changeContentBeforeEncoderModify($content) return $content; } + if ($this->isShortcodeInsideHtmlTag($content)) { + return $content; + } + // skip encoding if the content is already encoded with hook // Extract shortcode content to protect it from email encoding - $shortcode_exist_pattern = sprintf('/\[%s\](.*?)\[\/%s\]/s', $this->public_name, $this->public_name); + $shortcode_exist_pattern = sprintf('/(\[%s\])(.*?)(\[\/%s\])/s', $this->public_name, $this->public_name); $content = preg_replace_callback($shortcode_exist_pattern, function ($matches) { $placeholder = preg_replace('/EE\_\d+/', 'EE_' . (string)$this->shortcode_counter++, $this->exclusion_wrapper); if (is_null($placeholder)) { $placeholder = $this->exclusion_wrapper; } - if (isset($matches[0])) { - $this->shortcode_replacements[$placeholder] = $matches[0]; + if (isset($matches[1], $matches[2], $matches[3])) { + $prefix = $matches[1]; + $entity = $matches[2]; + $suffix = $matches[3]; + $entity = Escape::escKsesPost($entity); + $this->shortcode_replacements[$placeholder] = $prefix . $entity . $suffix; } return $placeholder; }, $content); - return $content; } + /** + * Checks whether any shortcode occurrence is located inside an HTML tag. + * + * This validation is used to prevent shortcode extraction from HTML + * attribute contexts such as: + * + * + * + * Processing shortcodes inside HTML tags may lead to malformed markup + * after WordPress content filters (e.g. wptexturize()) mutate surrounding + * content. Such mutations may potentially lead to attribute injection or + * mutation-XSS issues. + * + * The method scans all opening and closing shortcode tags and verifies + * whether their offsets are located between an unclosed "<" and ">" pair. + * + * @param string $content The content to validate. + * + * @return bool True if any shortcode boundary is detected inside an HTML tag, + * false otherwise. + */ + protected function isShortcodeInsideHtmlTag($content) + { + preg_match_all( + sprintf('/\[\/?%s\]/', preg_quote($this->public_name, '/')), + $content, + $matches, + PREG_OFFSET_CAPTURE + ); + + if (isset($matches[0])) { + foreach ($matches[0] as $match) { + $offset = $match[1] ?? null; + + if ($offset === null) { + continue; + } + + if ($this->isOffsetInsideHtmlTag($content, $offset)) { + return true; + } + } + } + + return false; + } + + + /** + * Determines whether a given character offset is located inside an HTML tag. + * + * The method performs a lightweight context check by locating the nearest + * "<" and ">" characters before the specified offset. + * + * If the last "<" appears after the last ">", the offset is considered + * to be inside an HTML tag or attribute context. + * + * Example: + * + * X'; + + $result = $this->shortcode->changeContentBeforeEncoderModify($content); + + // shortcode should NOT be replaced because it's inside HTML tag + $this->assertEquals($content, $result); + } + + public function testShortcodeOutsideHtmlIsProcessed() + { + $content = '[apbct_encode_data]Test content[/apbct_encode_data]'; + + $result = $this->shortcode->changeContentBeforeEncoderModify($content); + + $this->assertStringContainsString( + '%%APBCT_SHORT_CODE_INCLUDE_EE_0%%', + $result + ); + + $this->assertNotEquals($content, $result); + } + + public function testMultipleShortcodesAreHandled() + { + $content = + '[apbct_encode_data]A[/apbct_encode_data]' . + ' middle ' . + '[apbct_encode_data]B[/apbct_encode_data]'; + + $result = $this->shortcode->changeContentBeforeEncoderModify($content); + + $this->assertStringContainsString('%%APBCT_SHORT_CODE_INCLUDE_EE_0%%', $result); + $this->assertStringContainsString('%%APBCT_SHORT_CODE_INCLUDE_EE_1%%', $result); + } + + public function testHtmlAttributeBreakPayloadDoesNotExplode() + { + $content = 'Test'; + + $result = $this->shortcode->changeContentBeforeEncoderModify($content); + + // must remain stable, no corruption, no placeholder injection inside tag + $this->assertStringContainsString('assertStringContainsString('', $result); + } + + public function testOffsetDetectionInsideHtmlTag() + { + $content = 'X'; + + $pos = strpos($content, '[apbct_encode_data]'); + + $this->assertTrue( + $this->shortcode->isOffsetInsideHtmlTag($content, $pos) + ); + } + + public function testOffsetDetectionOutsideHtmlTag() + { + $content = '[apbct_encode_data]test[/apbct_encode_data]'; + + $pos = strpos($content, '[apbct_encode_data]'); + + $this->assertFalse( + $this->shortcode->isOffsetInsideHtmlTag($content, $pos) + ); + } } From 1ab88a3f399e776003c3a9add3cbff1042c1b33d Mon Sep 17 00:00:00 2001 From: alexandergull Date: Sat, 9 May 2026 23:34:38 +0500 Subject: [PATCH 08/17] Fix. Codepilot review. --- .../Shortcodes/EncodeContentSC.php | 9 ++- .../TestContactsEncoderShortCodeEncode.php | 73 +++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/lib/Cleantalk/ApbctWP/ContactsEncoder/Shortcodes/EncodeContentSC.php b/lib/Cleantalk/ApbctWP/ContactsEncoder/Shortcodes/EncodeContentSC.php index 7780e623c..f5a7d7b82 100644 --- a/lib/Cleantalk/ApbctWP/ContactsEncoder/Shortcodes/EncodeContentSC.php +++ b/lib/Cleantalk/ApbctWP/ContactsEncoder/Shortcodes/EncodeContentSC.php @@ -108,8 +108,8 @@ public function changeContentBeforeEncoderModify($content) } // skip encoding if the content is already encoded with hook - // Extract shortcode content to protect it from email encoding - $shortcode_exist_pattern = sprintf('/(\[%s\])(.*?)(\[\/%s\])/s', $this->public_name, $this->public_name); + // Extract shortcode content to protect it from email encoding, supports sc attributes(!) + $shortcode_exist_pattern = sprintf('/(\[%s(?:\s[^\]]*)?\])([\s\S]*?)(\[\/%s\])/s', $this->public_name, $this->public_name); $content = preg_replace_callback($shortcode_exist_pattern, function ($matches) { $placeholder = preg_replace('/EE\_\d+/', 'EE_' . (string)$this->shortcode_counter++, $this->exclusion_wrapper); if (is_null($placeholder)) { @@ -152,7 +152,10 @@ public function changeContentBeforeEncoderModify($content) protected function isShortcodeInsideHtmlTag($content) { preg_match_all( - sprintf('/\[\/?%s\]/', preg_quote($this->public_name, '/')), + sprintf( + '/\[\/?%s(?:\s[^\]]*)?\]/', //supports sc attributes(!) + preg_quote($this->public_name, '/') + ), $content, $matches, PREG_OFFSET_CAPTURE diff --git a/tests/ApbctWP/ContactsEncoder/TestContactsEncoderShortCodeEncode.php b/tests/ApbctWP/ContactsEncoder/TestContactsEncoderShortCodeEncode.php index e9e6cd7da..5691f12dd 100644 --- a/tests/ApbctWP/ContactsEncoder/TestContactsEncoderShortCodeEncode.php +++ b/tests/ApbctWP/ContactsEncoder/TestContactsEncoderShortCodeEncode.php @@ -139,4 +139,77 @@ public function testOffsetDetectionOutsideHtmlTag() $this->shortcode->isOffsetInsideHtmlTag($content, $pos) ); } + + public function testShortcodeWithAttributesIsProcessed() + { + $content = '[apbct_encode_data mode="blur"]Test[/apbct_encode_data]'; + + $result = $this->shortcode->changeContentBeforeEncoderModify($content); + + $this->assertStringContainsString( + '%%APBCT_SHORT_CODE_INCLUDE_EE_0%%', + $result + ); + + $this->assertNotEquals($content, $result); + } + + public function testShortcodeWithAttributesIsDetectedInHtmlContext() + { + $content = 'X'; + + $result = $this->shortcode->changeContentBeforeEncoderModify($content); + + // must be blocked due to HTML attribute context + $this->assertEquals($content, $result); + } + + public function testMixedShortcodesSafeAndUnsafe() + { + $content = + '[apbct_encode_data]SAFE[/apbct_encode_data]' . + '' . + 'X'; + + $result = $this->shortcode->changeContentBeforeEncoderModify($content); + + // because of current design: full block is skipped if ANY HTML-unsafe shortcode exists + $this->assertEquals($content, $result); + } + + public function testPlaceholderNeverAppearsInsideHtmlAttribute() + { + $content = 'X'; + + $result = $this->shortcode->changeContentBeforeEncoderModify($content); + + $this->assertStringNotContainsString('%%APBCT_SHORT_CODE_INCLUDE_EE_0%%', $result); + } + + public function testCallbackEscapesReplacingText() + { + $result = $this->shortcode->callback( + ['replacing_text' => ''], + 'content', + 'apbct_encode_data' + ); + + $this->assertStringNotContainsString('