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
24 changes: 3 additions & 21 deletions cleantalk.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
Domain Path: /i18n
*/

use Cleantalk\Antispam\ScriptsIntegration\CleantalkScriptsIntegrator;
use Cleantalk\Antispam\ProtectByShortcode;
use Cleantalk\ApbctWP\Activator;
use Cleantalk\ApbctWP\AdminNotices;
Expand Down Expand Up @@ -597,27 +598,8 @@ function apbct_write_js_errors($data)

// Public actions
if ( ! is_admin() && ! apbct_is_ajax() && ! apbct_is_customize_preview() ) {
if (
apbct_is_plugin_active('fluentformpro/fluentformpro.php') &&
(
apbct_is_in_uri('ff_landing=') ||
(
// Load scripts for logged in users if constant is defined
apbct_is_user_logged_in() &&
(defined('APBCT_FF_JS_SCRIPTS_LOAD') &&
APBCT_FF_JS_SCRIPTS_LOAD == true)
)
)
) {
add_action('wp_head', function () {
echo '<script data-pagespeed-no-defer="" src="'
. APBCT_URL_PATH
. '/js/apbct-public-bundle.min.js'
. '?ver=' . APBCT_VERSION . '" id="ct_public_functions-js"></script>';
echo '<script src="' . APBCT_BOT_DETECTOR_SCRIPT_URL . '?ver='
. APBCT_VERSION . '" async id="ct_bot_detector-js" data-wp-strategy="async"></script>';
}, 100);
}
$sci = new CleantalkScriptsIntegrator();
$sci->run();

SFWUpdateHelper::processSFWOutdatedError($apbct);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

namespace Cleantalk\Antispam\ScriptsIntegration;

class CleantalkScriptsIntegrator
{
/**
* List of plugins selected for inline script integration,
* indexed by their corresponding WordPress hook name.
*
* @var ScriptIntegrationPlugin[]
*/
public $plugins_loaded = [];

/**
* Executes the integration process.
*
* This method:
* - Retrieves available integrations
* - Filters plugins that should be loaded
* - Registers WordPress hooks to execute plugin integration logic
*
* @return void
*/
public function run()
{
$integrations = $this->getIntegrations();

$this->plugins_loaded = !empty($integrations)
? $this->getPluginsToInline($integrations)
: [];

if (!empty($this->plugins_loaded)) {
foreach ($this->plugins_loaded as $_hook => $plugin) {
if ($plugin instanceof ScriptIntegrationPlugin) {
add_action($_hook, function () use ($plugin) {
$plugin->integrate();
}, 100);
}
}
}
}

/**
* Filters plugins that are eligible for inline script integration.
*
* A plugin is included only if:
* - It is active
* - It matches the current URI context
* - It passes additional runtime checks
*
* Each hook can only be assigned to one plugin (first match wins).
*
* @param ScriptIntegrationPlugin[] $integrations List of available plugin integrations.
* @return ScriptIntegrationPlugin[] Filtered plugins indexed by hook name.
*/
public function getPluginsToInline($integrations)
{
$plugins_loaded = [];

foreach ($integrations as $plugin) {
if (
$plugin->is_plugin_active &&
$plugin->is_in_uri &&
$plugin->additional_checks_passed
) {
if (!isset($plugins_loaded[$plugin->hook_name])) {
$plugins_loaded[$plugin->hook_name] = $plugin;
}
}
Comment on lines +62 to +70
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getPluginsToInline() requires is_in_uri AND additional_checks_passed, which prevents integrations from supporting “URI match OR alternative condition” (e.g., the FluentForm case that previously allowed loading for logged-in users via APBCT_FF_JS_SCRIPTS_LOAD). If additionalChecks() is meant to extend/override the URI gating, update the condition to use ( $plugin->is_in_uri || $plugin->additional_checks_passed ) and adjust the base ScriptIntegrationPlugin::additionalChecks() default accordingly (e.g., return false) so scripts don’t load everywhere by default.

Copilot uses AI. Check for mistakes.
}

return $plugins_loaded;
}

/**
* Returns a list of all available plugin integrations.
*
* Each integration defines:
* - Activation rules
* - Context conditions (URI, environment, etc.)
* - Hook target for script injection
*
* @return ScriptIntegrationPlugin[]
*/
public function getIntegrations()
{
try {
$integrations = [
new GiveWPScript(),
new FluentFormScript(),
];
} catch (\Exception $e) {
$integrations = [];
}

Comment on lines +88 to +96
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getIntegrations() wraps creation of all integrations in a single try/catch; if one integration throws (e.g., missing dependency), it drops the entire list. To keep other integrations working, instantiate each integration in its own try/catch (or validate prerequisites before new ...()), and only skip the failing one; optionally log/trace the exception for debugging.

Suggested change
try {
$integrations = [
new GiveWPScript(),
new FluentFormScript(),
];
} catch (\Exception $e) {
$integrations = [];
}
$integrations = [];
try {
$integrations[] = new GiveWPScript();
} catch (\Throwable $e) {
}
try {
$integrations[] = new FluentFormScript();
} catch (\Throwable $e) {
}

Copilot uses AI. Check for mistakes.
return $integrations;
}
}
29 changes: 29 additions & 0 deletions lib/Cleantalk/Antispam/ScriptsIntegration/FluentFormScript.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace Cleantalk\Antispam\ScriptsIntegration;

class FluentFormScript extends ScriptIntegrationPlugin
{
public $hook_name = 'wp_head';
public $plugin_file = 'fluentformpro/fluentformpro.php';
public $uri_chunk = 'ff_landing=';

public function integrate()
{
echo '<script data-pagespeed-no-defer="" src="'
. APBCT_URL_PATH
. '/js/apbct-public-bundle.min.js'
. '?ver=' . APBCT_VERSION . '" id="ct_public_functions-js"></script>';
echo '<script src="' . APBCT_BOT_DETECTOR_SCRIPT_URL . '?ver='
. APBCT_VERSION . '" async id="ct_bot_detector-js" data-wp-strategy="async"></script>';
}

public function additionalChecks()
{
return $this->is_in_uri || (
function_exists('apbct_is_user_logged_in') &&
apbct_is_user_logged_in() &&
(defined('APBCT_FF_JS_SCRIPTS_LOAD') && APBCT_FF_JS_SCRIPTS_LOAD == true)
);
Comment on lines +21 to +27
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FluentFormScript::additionalChecks() currently returns $this->is_in_uri || (...), but CleantalkScriptsIntegrator::getPluginsToInline() already requires $plugin->is_in_uri to be true. That makes the logged-in/APBCT_FF_JS_SCRIPTS_LOAD branch ineffective (regression vs the previous condition in cleantalk.php where URI match was optional). Consider changing the integrator selection logic to allow (is_in_uri || additional_checks_passed) (and make the default additionalChecks() return false), or move the OR logic into an override of isInUri() so the integrator’s is_in_uri gate still works.

Suggested change
public function additionalChecks()
{
return $this->is_in_uri || (
function_exists('apbct_is_user_logged_in') &&
apbct_is_user_logged_in() &&
(defined('APBCT_FF_JS_SCRIPTS_LOAD') && APBCT_FF_JS_SCRIPTS_LOAD == true)
);
public function isInUri()
{
return parent::isInUri() || (
function_exists('apbct_is_user_logged_in') &&
apbct_is_user_logged_in() &&
defined('APBCT_FF_JS_SCRIPTS_LOAD') &&
APBCT_FF_JS_SCRIPTS_LOAD == true
);
}
public function additionalChecks()
{
return false;

Copilot uses AI. Check for mistakes.
}
}
32 changes: 32 additions & 0 deletions lib/Cleantalk/Antispam/ScriptsIntegration/GiveWPScript.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Cleantalk\Antispam\ScriptsIntegration;

class GiveWPScript extends ScriptIntegrationPlugin
{
public $hook_name = 'givewp_donation_form_enqueue_scripts';
public $plugin_file = 'give/give.php';
public $uri_chunk = 'givewp-route=donation-form-view';

/**
* @return void
* @psalm-suppress InvalidArgument
*/
public function integrate()
{
// Bot detector
if ( apbct__is_bot_detector_enabled() && ! apbct_bot_detector_scripts_exclusion()) {
// Attention! Skip old enqueue way for external script.
wp_enqueue_script(
'ct_bot_detector',
APBCT_BOT_DETECTOR_SCRIPT_URL,
[],
APBCT_VERSION,
array(
'in_footer' => true,
'strategy' => 'async'
)
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

namespace Cleantalk\Antispam\ScriptsIntegration;

abstract class ScriptIntegrationPlugin
{
public $is_plugin_active;
public $is_in_uri;
public $additional_checks_passed;

public $plugin_file;
public $uri_chunk;
public $hook_name;

public function __construct()
{
if (
!isset($this->plugin_file) ||
!is_string($this->plugin_file) ||
!isset($this->uri_chunk) ||
!is_string($this->uri_chunk) ||
!isset($this->hook_name) ||
!is_string($this->hook_name)
) {
throw new \Exception('Plugin file, URI chunk and hook name must be set');
}

Comment on lines +17 to +27
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The constructor throws a generic \Exception with a non-specific message when required properties aren’t set. Using a more specific exception type (e.g., \InvalidArgumentException) and including the concrete class name and the missing/invalid fields in the message would make integration failures much easier to diagnose (especially since getIntegrations() currently swallows exceptions).

Suggested change
if (
!isset($this->plugin_file) ||
!is_string($this->plugin_file) ||
!isset($this->uri_chunk) ||
!is_string($this->uri_chunk) ||
!isset($this->hook_name) ||
!is_string($this->hook_name)
) {
throw new \Exception('Plugin file, URI chunk and hook name must be set');
}
$invalid_fields = array();
if (!isset($this->plugin_file) || !is_string($this->plugin_file)) {
$invalid_fields[] = 'plugin_file';
}
if (!isset($this->uri_chunk) || !is_string($this->uri_chunk)) {
$invalid_fields[] = 'uri_chunk';
}
if (!isset($this->hook_name) || !is_string($this->hook_name)) {
$invalid_fields[] = 'hook_name';
}
if (!empty($invalid_fields)) {
throw new \InvalidArgumentException(
sprintf(
'Invalid script integration configuration for %s: missing or invalid field(s): %s',
static::class,
implode(', ', $invalid_fields)
)
);
}

Copilot uses AI. Check for mistakes.
$this->is_plugin_active = $this->isPluginActive($this->plugin_file);
$this->is_in_uri = $this->isInUri($this->uri_chunk);
$this->additional_checks_passed = $this->additionalChecks();
}

/**
* Executes the plugin integration logic.
*
* This method must be implemented by each concrete integration class
* and is responsible for registering scripts, hooks, or other behaviors.
*
* @return void
*/
abstract public function integrate();

/**
* Checks whether a given WordPress plugin is active.
*
* @param string $plugin_file Path to the plugin main file.
* @return bool True if the plugin is active, false otherwise.
*/
public function isPluginActive($plugin_file)
{
return apbct_is_plugin_active($plugin_file);
}

/**
* Checks whether the current request URI contains a specific substring.
*
* @param string $uri_chunk URI fragment to search for in the current request URI.
* @return bool True if the URI fragment is found, false otherwise.
*/
public function isInUri($uri_chunk)
{
return apbct_is_in_uri($uri_chunk);
}

/**
* Performs additional runtime checks required for plugin activation.
*
* This method can be overridden in child classes to implement
* custom validation logic.
*
* @return bool True if all additional checks pass, false otherwise.
*/
public function additionalChecks()
{
return true;
}
}
Loading
Loading