fix: wire is_hard_drop_enabled + drop_score_threshold settings into Classifier filter chain
Settings::register_classifier_filters() registers priority-5 callbacks on cf7stg_classifier_thresholds and cf7stg_hard_drop_rules so the UI options actually influence Classifier behaviour. Called from Plugin::boot(). User filters at priority 10 override these defaults as expected. Adds 4 tests covering the wired behaviour and override priority. Also adds dry_run_drop? to MessageFormatter::build() @param docblock. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -24,7 +24,8 @@ final class MessageFormatter
|
|||||||
* reason:string,
|
* reason:string,
|
||||||
* ip:string,
|
* ip:string,
|
||||||
* user_agent:string,
|
* user_agent:string,
|
||||||
* is_retro:bool
|
* is_retro:bool,
|
||||||
|
* dry_run_drop?:bool
|
||||||
* } $ctx
|
* } $ctx
|
||||||
*
|
*
|
||||||
* @return array{text:string,reply_markup:array|null}
|
* @return array{text:string,reply_markup:array|null}
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ final class Plugin
|
|||||||
|
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
|
// Inject silent-drop options into Classifier filter chain
|
||||||
|
Settings::register_classifier_filters();
|
||||||
|
|
||||||
// Register runtime hooks
|
// Register runtime hooks
|
||||||
Dispatcher::register_hooks();
|
Dispatcher::register_hooks();
|
||||||
(new CF7Source())->register();
|
(new CF7Source())->register();
|
||||||
|
|||||||
@@ -171,4 +171,27 @@ final class Settings
|
|||||||
'reset_at' => $today,
|
'reset_at' => $today,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register default callbacks that inject silent-drop options into the
|
||||||
|
* Classifier's filter chain. Runs at plugin boot. User filters at the
|
||||||
|
* default priority (10) override these (priority 5).
|
||||||
|
*/
|
||||||
|
public static function register_classifier_filters(): void
|
||||||
|
{
|
||||||
|
add_filter('cf7stg_classifier_thresholds', static function ($thresholds) {
|
||||||
|
if (is_array($thresholds)) {
|
||||||
|
$thresholds['drop'] = self::get_drop_score_threshold();
|
||||||
|
}
|
||||||
|
return $thresholds;
|
||||||
|
}, 5);
|
||||||
|
|
||||||
|
add_filter('cf7stg_hard_drop_rules', static function ($rules) {
|
||||||
|
if (is_array($rules) && !self::is_hard_drop_enabled()) {
|
||||||
|
$rules['short_latin_name'] = false;
|
||||||
|
$rules['placeholder_fields'] = false;
|
||||||
|
}
|
||||||
|
return $rules;
|
||||||
|
}, 5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
namespace Cf7stg\Tests;
|
namespace Cf7stg\Tests;
|
||||||
|
|
||||||
use Cf7stg\Classifier;
|
use Cf7stg\Classifier;
|
||||||
|
use Cf7stg\Settings;
|
||||||
|
|
||||||
final class ClassifierDropTest extends Cf7stgTestCase
|
final class ClassifierDropTest extends Cf7stgTestCase
|
||||||
{
|
{
|
||||||
@@ -283,4 +284,55 @@ final class ClassifierDropTest extends Cf7stgTestCase
|
|||||||
$r = Classifier::classify($fields, ['reason' => 'Akismet']);
|
$r = Classifier::classify($fields, ['reason' => 'Akismet']);
|
||||||
self::assertSame('drop', $r['label']);
|
self::assertSame('drop', $r['label']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_hard_drop_disabled_via_settings_skips_drop(): void
|
||||||
|
{
|
||||||
|
Settings::set_hard_drop_enabled(false);
|
||||||
|
Settings::register_classifier_filters();
|
||||||
|
$fields = require __DIR__ . '/fixtures/spam-screenshot-1.php';
|
||||||
|
$r = Classifier::classify($fields, ['reason' => 'Akismet']);
|
||||||
|
// With hard-rule disabled, screenshot-1 has no positive signals AND score isn't ≤ -5
|
||||||
|
// (placeholders by themselves give no score penalty), so label should NOT be 'drop'.
|
||||||
|
self::assertNotSame('drop', $r['label']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_drop_score_threshold_from_settings_applied(): void
|
||||||
|
{
|
||||||
|
Settings::set_drop_score_threshold(-3);
|
||||||
|
Settings::register_classifier_filters();
|
||||||
|
// URL gives -3 score, no positive signals, no hard-rule trigger.
|
||||||
|
// With threshold lowered to -3, this should fire score-based drop.
|
||||||
|
$fields = ['msg' => 'http://x.com'];
|
||||||
|
$r = Classifier::classify($fields, ['reason' => 'Akismet']);
|
||||||
|
self::assertSame('drop', $r['label']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_settings_threshold_can_be_overridden_by_user_filter(): void
|
||||||
|
{
|
||||||
|
Settings::set_drop_score_threshold(-3);
|
||||||
|
Settings::register_classifier_filters();
|
||||||
|
// User filter at priority 10 should override Settings default at priority 5.
|
||||||
|
add_filter('cf7stg_classifier_thresholds', static function ($t) {
|
||||||
|
$t['drop'] = -10;
|
||||||
|
return $t;
|
||||||
|
}, 10);
|
||||||
|
$fields = ['msg' => 'http://x.com']; // score = -3
|
||||||
|
$r = Classifier::classify($fields, ['reason' => 'Akismet']);
|
||||||
|
// -3 is NOT ≤ -10, so no drop
|
||||||
|
self::assertNotSame('drop', $r['label']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_filter_disables_placeholder_fields_rule(): void
|
||||||
|
{
|
||||||
|
add_filter('cf7stg_hard_drop_rules', static function ($rules) {
|
||||||
|
$rules['placeholder_fields'] = false;
|
||||||
|
return $rules;
|
||||||
|
});
|
||||||
|
// Form WITHOUT name field — digit-only values are valid placeholders (^[A-Za-z0-9]{1,2}$)
|
||||||
|
// but FieldDetector::find_name rejects them (has digits), so short_latin_name cannot fire.
|
||||||
|
// With placeholder_fields rule also disabled → no hard-rule fires → no drop.
|
||||||
|
$fields = ['a' => '11', 'b' => '22', 'c' => '33', 'd' => '44'];
|
||||||
|
$r = Classifier::classify($fields, ['reason' => 'Akismet']);
|
||||||
|
self::assertNotSame('drop', $r['label']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user