Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions lib/Cleantalk/Antispam/IntegrationsByClass/WPSearchForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
namespace Cleantalk\Antispam\IntegrationsByClass;

use Cleantalk\ApbctWP\Honeypot;
use Cleantalk\ApbctWP\Variables\AltSessions;
use Cleantalk\ApbctWP\Variables\Server;
use DOMDocument;

/**
Expand Down Expand Up @@ -90,12 +92,86 @@ public function apbctFormSearchAddFields($form_html)

$result = str_replace('<form', '<form ' . $form_sign, $form_html);
$result = str_replace('</form>', Honeypot::generateHoneypotField('search_form', $form_method) . '</form>', $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 restricted :(
$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);
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;
$current = is_array($current) ? $current : [];

if (!isset($current[$drawn_for_uri])) {
$current[$drawn_for_uri] = 1;
AltSessions::set('search_form_ready', $current);
Comment on lines +144 to +148
}
}
Comment thread
Copilot marked this conversation as resolved.

/**
* 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
*
Expand All @@ -117,6 +193,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(
Expand Down
120 changes: 120 additions & 0 deletions tests/Antispam/IntegrationsByClass/TestWPSearchForm.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php

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;
Comment thread
alexandergull marked this conversation as resolved.

class TestWPSearchForm extends TestCase
{
/**
* @var array
*/
private $serverBackup = array();

public function setUp(): void
{
global $wpdb;
parent::setUp();

$this->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'] = '';

$creator = new DbTablesCreator();
$creator->createTable($wpdb->prefix . 'cleantalk_sessions');

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'));
Comment thread
alexandergull marked this conversation as resolved.
}

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/']);
}
}
Loading