From baf49bee9a0627902715ecf565f97fe1888d2c88 Mon Sep 17 00:00:00 2001 From: alexandergull Date: Fri, 26 Jun 2026 15:58:51 +0500 Subject: [PATCH 1/4] Udp. Search form. Process requests only if a native search form is used. --- .../IntegrationsByClass/WPSearchForm.php | 79 ++++++++++++ .../IntegrationsByClass/TestWPSearchForm.php | 116 ++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 tests/Antispam/IntegrationsByClass/TestWPSearchForm.php diff --git a/lib/Cleantalk/Antispam/IntegrationsByClass/WPSearchForm.php b/lib/Cleantalk/Antispam/IntegrationsByClass/WPSearchForm.php index 03f302061..2a68d1169 100644 --- a/lib/Cleantalk/Antispam/IntegrationsByClass/WPSearchForm.php +++ b/lib/Cleantalk/Antispam/IntegrationsByClass/WPSearchForm.php @@ -3,6 +3,8 @@ namespace Cleantalk\Antispam\IntegrationsByClass; use Cleantalk\ApbctWP\Honeypot; +use Cleantalk\ApbctWP\Variables\AltSessions; +use Cleantalk\ApbctWP\Variables\Server; use DOMDocument; /** @@ -90,12 +92,80 @@ public function apbctFormSearchAddFields($form_html) $result = str_replace('', Honeypot::generateHoneypotField('search_form', $form_method) . '', $result); + self::setSearchFormDrawn(); return $result; } return $form_html; } + /** + * Marks a protected native WordPress search form as submitted for the given URI. + * + * Removes the URI from the alternative-session storage after the search request is + * received, so the same rendered form state cannot be reused for repeated checks. + * If no tracked forms remain, stores false because alternative sessions do not + * support an empty array value. + * + * @param string $drawn_for_uri URI path where the protected search form was rendered. + * @return void + */ + public static function setSearchFormSent($drawn_for_uri) + { + $current = AltSessions::get('search_form_ready'); + $current = is_string($current) ? json_decode($current, true) : $current; + if (!empty($current) && is_array($current) && isset($current[$drawn_for_uri])) { + unset($current[$drawn_for_uri]); + } + if (empty($current)) { + // prepare for alt sessions, empty array is restriced :( + $current = false; + } + AltSessions::set('search_form_ready', $current); + } + + /** + * Stores the current URI as having a protected native WordPress search form rendered. + * + * The stored URI is later used to verify that an incoming native search request + * came from a page where CleanTalk added protection fields to the search form. + * + * @return void + */ + public static function setSearchFormDrawn() + { + $drawn_for_uri = parse_url(Server::getString('REQUEST_URI'), PHP_URL_PATH); + $current = AltSessions::get('search_form_ready'); + if (empty($current)) { + $current = []; + $current[$drawn_for_uri] = 1; + } + AltSessions::set('search_form_ready', $current); + } + + /** + * Checks whether the current request refers to a previously rendered protected search form. + * + * Reads the stored search form URI list from alternative sessions and compares each + * stored URI with the current HTTP referer. Returns the matched URI path when found, + * otherwise returns false. + * + * @return false|string URI path where the protected form was rendered, or false if no match exists. + */ + public static function isSearchFormDrawn() + { + $current = AltSessions::get('search_form_ready'); + $current = is_string($current) ? json_decode($current, true) : $current; + if (!empty($current) && is_array($current)) { + foreach ($current as $drawn_for_uri => $_val) { + if (is_string($drawn_for_uri) && apbct_is_in_referer($drawn_for_uri)) { + return $drawn_for_uri; + } + } + } + return false; + } + /** * Test default search string for spam * @@ -117,6 +187,15 @@ public function testSpam($search) return $search; } + // do checks only if the form was built via apbct for the visitor on the uri + $form_is_ready_for_uri = self::isSearchFormDrawn(); + if (false !== $form_is_ready_for_uri) { + self::setSearchFormSent($form_is_ready_for_uri); + } else { + do_action('apbct_skipped_request', __FILE__ . ' -> ' . __FUNCTION__ . '(): native form has not been drawn ' . __LINE__, $_GET); + return $search; + } + $user = apbct_is_user_logged_in() ? wp_get_current_user() : null; $data = array( diff --git a/tests/Antispam/IntegrationsByClass/TestWPSearchForm.php b/tests/Antispam/IntegrationsByClass/TestWPSearchForm.php new file mode 100644 index 000000000..0b09f29e0 --- /dev/null +++ b/tests/Antispam/IntegrationsByClass/TestWPSearchForm.php @@ -0,0 +1,116 @@ +serverBackup = $_SERVER; + Server::getInstance()->variables = []; + + $_SERVER['HTTP_USER_AGENT'] = 'phpunit-user-agent'; + $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'en-US,en;q=0.9'; + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + $_SERVER['REQUEST_URI'] = '/search-source-page/'; + $_SERVER['HTTP_REFERER'] = ''; + + AltSessions::wipe(); + } + + public function tearDown(): void + { + AltSessions::wipe(); + + $_SERVER = $this->serverBackup; + + parent::tearDown(); + } + + public function testSetSearchFormDrawnStoresCurrentRequestPath() + { + $_SERVER['REQUEST_URI'] = '/search-source-page/?foo=bar'; + + WPSearchForm::setSearchFormDrawn(); + + $stored = AltSessions::get('search_form_ready'); + $stored = is_string($stored) ? json_decode($stored, true) : $stored; + $this->assertIsArray($stored); + $this->assertArrayHasKey('/search-source-page/', $stored); + $this->assertSame(1, $stored['/search-source-page/']); + } + + public function testIsSearchFormDrawnReturnsStoredPathIfRefererMatches() + { + $_SERVER['REQUEST_URI'] = '/search-source-page/'; + WPSearchForm::setSearchFormDrawn(); + + $_SERVER['HTTP_REFERER'] = 'https://example.test/search-source-page/?s=query'; + + $this->assertSame( + '/search-source-page/', + WPSearchForm::isSearchFormDrawn() + ); + } + + public function testIsSearchFormDrawnReturnsFalseIfRefererDoesNotMatch() + { + $_SERVER['REQUEST_URI'] = '/search-source-page/'; + WPSearchForm::setSearchFormDrawn(); + + $_SERVER['HTTP_REFERER'] = 'https://example.test/another-page/?s=query'; + + $this->assertFalse(WPSearchForm::isSearchFormDrawn()); + } + + public function testIsSearchFormDrawnReturnsFalseIfNoStoredSearchFormExists() + { + $_SERVER['HTTP_REFERER'] = 'https://example.test/search-source-page/?s=query'; + + $this->assertFalse(WPSearchForm::isSearchFormDrawn()); + } + + public function testSetSearchFormSentRemovesStoredPath() + { + $_SERVER['REQUEST_URI'] = '/search-source-page/'; + WPSearchForm::setSearchFormDrawn(); + + WPSearchForm::setSearchFormSent('/search-source-page/'); + + $this->assertSame(0, AltSessions::get('search_form_ready')); + } + + public function testSetSearchFormSentKeepsOtherStoredPaths() + { + AltSessions::set( + 'search_form_ready', + array( + '/first-page/' => 1, + '/second-page/' => 1, + ) + ); + + WPSearchForm::setSearchFormSent('/first-page/'); + + $stored = AltSessions::get('search_form_ready'); + $stored = is_string($stored) ? json_decode($stored, true) : $stored; + + $this->assertIsArray($stored); + $this->assertArrayNotHasKey('/first-page/', $stored); + $this->assertArrayHasKey('/second-page/', $stored); + $this->assertSame(1, $stored['/second-page/']); + } +} From 5d3f4264711a577b293d0e5946f04cac7112eb69 Mon Sep 17 00:00:00 2001 From: alexandergull Date: Fri, 26 Jun 2026 16:51:02 +0500 Subject: [PATCH 2/4] CP. Minor fixes. --- .../Antispam/IntegrationsByClass/WPSearchForm.php | 8 +++++--- tests/Antispam/IntegrationsByClass/TestWPSearchForm.php | 1 - 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/Cleantalk/Antispam/IntegrationsByClass/WPSearchForm.php b/lib/Cleantalk/Antispam/IntegrationsByClass/WPSearchForm.php index 2a68d1169..93ae3be7d 100644 --- a/lib/Cleantalk/Antispam/IntegrationsByClass/WPSearchForm.php +++ b/lib/Cleantalk/Antispam/IntegrationsByClass/WPSearchForm.php @@ -118,7 +118,7 @@ public static function setSearchFormSent($drawn_for_uri) unset($current[$drawn_for_uri]); } if (empty($current)) { - // prepare for alt sessions, empty array is restriced :( + // prepare for alt sessions, empty array is restricted :( $current = false; } AltSessions::set('search_form_ready', $current); @@ -135,11 +135,13 @@ public static function setSearchFormSent($drawn_for_uri) public static function setSearchFormDrawn() { $drawn_for_uri = parse_url(Server::getString('REQUEST_URI'), PHP_URL_PATH); + $drawn_for_uri = (is_string($drawn_for_uri) && $drawn_for_uri !== '') ? $drawn_for_uri : '/'; $current = AltSessions::get('search_form_ready'); - if (empty($current)) { + $current = is_string($current) ? json_decode($current, true) : $current; + if (!is_array(($current))) { $current = []; - $current[$drawn_for_uri] = 1; } + $current[$drawn_for_uri] = 1; AltSessions::set('search_form_ready', $current); } diff --git a/tests/Antispam/IntegrationsByClass/TestWPSearchForm.php b/tests/Antispam/IntegrationsByClass/TestWPSearchForm.php index 0b09f29e0..08970be9a 100644 --- a/tests/Antispam/IntegrationsByClass/TestWPSearchForm.php +++ b/tests/Antispam/IntegrationsByClass/TestWPSearchForm.php @@ -5,7 +5,6 @@ use Cleantalk\Antispam\IntegrationsByClass\WPSearchForm; use Cleantalk\ApbctWP\Variables\AltSessions; use Cleantalk\ApbctWP\Variables\Server; -use Cleantalk\Variables\ServerVariables; use PHPUnit\Framework\TestCase; class TestWPSearchForm extends TestCase From da5b5a67d3ea17202e96cc8735973c31dd375ed6 Mon Sep 17 00:00:00 2001 From: alexandergull Date: Fri, 26 Jun 2026 16:55:17 +0500 Subject: [PATCH 3/4] Fix. Create tables on PHP unit. --- tests/Antispam/IntegrationsByClass/TestWPSearchForm.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/Antispam/IntegrationsByClass/TestWPSearchForm.php b/tests/Antispam/IntegrationsByClass/TestWPSearchForm.php index 08970be9a..b54452cce 100644 --- a/tests/Antispam/IntegrationsByClass/TestWPSearchForm.php +++ b/tests/Antispam/IntegrationsByClass/TestWPSearchForm.php @@ -3,6 +3,7 @@ namespace Antispam\IntegrationsByClass; use Cleantalk\Antispam\IntegrationsByClass\WPSearchForm; +use Cleantalk\ApbctWP\UpdatePlugin\DbTablesCreator; use Cleantalk\ApbctWP\Variables\AltSessions; use Cleantalk\ApbctWP\Variables\Server; use PHPUnit\Framework\TestCase; @@ -16,6 +17,7 @@ class TestWPSearchForm extends TestCase public function setUp(): void { + global $wpdb; parent::setUp(); $this->serverBackup = $_SERVER; @@ -27,6 +29,9 @@ public function setUp(): void $_SERVER['REQUEST_URI'] = '/search-source-page/'; $_SERVER['HTTP_REFERER'] = ''; + $creator = new DbTablesCreator(); + $creator->createTable($wpdb->prefix . 'cleantalk_sessions'); + AltSessions::wipe(); } From 3b732a01eca0c1bfe9d522c61a4f06f0da7aaa47 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 26 Jun 2026 17:01:18 +0500 Subject: [PATCH 4/4] CP. Reduce calls. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../Antispam/IntegrationsByClass/WPSearchForm.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/Cleantalk/Antispam/IntegrationsByClass/WPSearchForm.php b/lib/Cleantalk/Antispam/IntegrationsByClass/WPSearchForm.php index 93ae3be7d..0d228c17f 100644 --- a/lib/Cleantalk/Antispam/IntegrationsByClass/WPSearchForm.php +++ b/lib/Cleantalk/Antispam/IntegrationsByClass/WPSearchForm.php @@ -135,14 +135,18 @@ public static function setSearchFormSent($drawn_for_uri) public static function setSearchFormDrawn() { $drawn_for_uri = parse_url(Server::getString('REQUEST_URI'), PHP_URL_PATH); - $drawn_for_uri = (is_string($drawn_for_uri) && $drawn_for_uri !== '') ? $drawn_for_uri : '/'; + if (!is_string($drawn_for_uri) || $drawn_for_uri === '') { + return; + } + $current = AltSessions::get('search_form_ready'); $current = is_string($current) ? json_decode($current, true) : $current; - if (!is_array(($current))) { - $current = []; + $current = is_array($current) ? $current : []; + + if (!isset($current[$drawn_for_uri])) { + $current[$drawn_for_uri] = 1; + AltSessions::set('search_form_ready', $current); } - $current[$drawn_for_uri] = 1; - AltSessions::set('search_form_ready', $current); } /**