Commit b9a4093a07 for woocommerce
commit b9a4093a07148bfdd5fb4fde3615687e7539bd1c
Author: Vasily Belolapotkov <vasily.belolapotkov@automattic.com>
Date: Tue Dec 16 14:50:02 2025 +0100
[Fraud Protection] Initialize core infrastructure and DI container (#62413)
* Add setting for experimental Fraud Protection feature
* Add Fraud Protection feature controller handling feature init, and logging
diff --git a/plugins/woocommerce/includes/class-woocommerce.php b/plugins/woocommerce/includes/class-woocommerce.php
index a677085e3f..f9ccb73282 100644
--- a/plugins/woocommerce/includes/class-woocommerce.php
+++ b/plugins/woocommerce/includes/class-woocommerce.php
@@ -17,6 +17,7 @@ use Automattic\WooCommerce\Internal\ComingSoon\ComingSoonRequestHandler;
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
use Automattic\WooCommerce\Internal\DownloadPermissionsAdjuster;
use Automattic\WooCommerce\Internal\Features\FeaturesController;
+use Automattic\WooCommerce\Internal\FraudProtection\FraudProtectionController;
use Automattic\WooCommerce\Internal\MCP\MCPAdapterProvider;
use Automattic\WooCommerce\Internal\Abilities\AbilitiesRegistry;
use Automattic\WooCommerce\Internal\ProductAttributesLookup\DataRegenerator;
@@ -371,6 +372,7 @@ final class WooCommerce {
$container->get( Automattic\WooCommerce\Internal\Orders\OrderAttributionController::class )->register();
$container->get( Automattic\WooCommerce\Internal\Orders\OrderAttributionBlocksController::class )->register();
$container->get( Automattic\WooCommerce\Internal\CostOfGoodsSold\CostOfGoodsSoldController::class )->register();
+ $container->get( Automattic\WooCommerce\Internal\FraudProtection\FraudProtectionController::class )->register();
$container->get( Automattic\WooCommerce\Internal\Admin\Settings\PaymentsController::class )->register();
$container->get( Automattic\WooCommerce\Internal\Admin\Settings\PaymentsProviders\WooPayments\WooPaymentsController::class )->register();
$container->get( Automattic\WooCommerce\Internal\Utilities\LegacyRestApiStub::class )->register();
diff --git a/plugins/woocommerce/src/Internal/FraudProtection/FraudProtectionController.php b/plugins/woocommerce/src/Internal/FraudProtection/FraudProtectionController.php
new file mode 100644
index 0000000000..a9d7e36747
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/FraudProtection/FraudProtectionController.php
@@ -0,0 +1,96 @@
+<?php
+/**
+ * FraudProtectionController class file.
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\Internal\FraudProtection;
+
+use Automattic\WooCommerce\Internal\Features\FeaturesController;
+use Automattic\WooCommerce\Internal\RegisterHooksInterface;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Main controller for fraud protection features.
+ *
+ * This class orchestrates all fraud protection components and ensures
+ * zero-impact when the feature flag is disabled.
+ *
+ * @since 10.5.0
+ * @internal This class is part of the internal API and is subject to change without notice.
+ */
+class FraudProtectionController implements RegisterHooksInterface {
+
+ /**
+ * Features controller instance.
+ *
+ * @var FeaturesController
+ */
+ private FeaturesController $features_controller;
+
+ /**
+ * Register hooks.
+ */
+ public function register(): void {
+ add_action( 'init', array( $this, 'on_init' ) );
+ }
+
+ /**
+ * Initialize the instance, runs when the instance is created by the dependency injection container.
+ *
+ * @internal
+ * @param FeaturesController $features_controller The instance of FeaturesController to use.
+ */
+ final public function init( FeaturesController $features_controller ): void {
+ $this->features_controller = $features_controller;
+ }
+
+ /**
+ * Hook into WordPress on init.
+ *
+ * @internal
+ */
+ public function on_init(): void {
+ // Bail if the feature is not enabled.
+ if ( ! $this->feature_is_enabled() ) {
+ return;
+ }
+
+ // Future implementation: Register hooks and initialize components here.
+ // For now, this is a placeholder for the infrastructure.
+ }
+
+ /**
+ * Check if fraud protection feature is enabled.
+ *
+ * This method can be used by other fraud protection classes to check
+ * the feature flag status.
+ *
+ * @return bool True if enabled.
+ */
+ public function feature_is_enabled(): bool {
+ return $this->features_controller->feature_is_enabled( 'fraud_protection' );
+ }
+
+ /**
+ * Log helper method for consistent logging across all fraud protection components.
+ *
+ * This static method ensures all fraud protection logs are written with
+ * the same 'woo-fraud-protection' source for easy filtering in WooCommerce logs.
+ *
+ * @param string $level Log level (emergency, alert, critical, error, warning, notice, info, debug).
+ * @param string $message Log message.
+ * @param array $context Optional context data.
+ *
+ * @return void
+ */
+ public static function log( string $level, string $message, array $context = array() ): void {
+ wc_get_logger()->log(
+ $level,
+ $message,
+ array_merge( $context, array( 'source' => 'woo-fraud-protection' ) )
+ );
+ }
+}
diff --git a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/FraudProtectionControllerTest.php b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/FraudProtectionControllerTest.php
new file mode 100644
index 0000000000..1efd7c8b58
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/FraudProtectionControllerTest.php
@@ -0,0 +1,175 @@
+<?php
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\Tests\Internal\FraudProtection;
+
+use Automattic\WooCommerce\Internal\Features\FeaturesController;
+use Automattic\WooCommerce\Internal\FraudProtection\FraudProtectionController;
+
+/**
+ * Tests for the FraudProtectionController class.
+ */
+class FraudProtectionControllerTest extends \WC_Unit_Test_Case {
+
+ /**
+ * Get a fresh controller instance with reset container.
+ *
+ * @return FraudProtectionController
+ */
+ private function get_fresh_controller(): FraudProtectionController {
+ $container = wc_get_container();
+ $container->reset_all_resolved();
+ return $container->get( FraudProtectionController::class );
+ }
+
+ /**
+ * Test logging functionality.
+ */
+ public function test_log_writes_to_woo_fraud_protection_source(): void {
+ // Mock the logger.
+ $logger = $this->getMockBuilder( \WC_Logger_Interface::class )
+ ->getMock();
+
+ // Expect the log method to be called with correct parameters.
+ $logger->expects( $this->once() )
+ ->method( 'log' )
+ ->with(
+ $this->equalTo( 'info' ),
+ $this->equalTo( 'Test message' ),
+ $this->equalTo( array( 'source' => 'woo-fraud-protection' ) )
+ );
+
+ // Replace the logger with our mock.
+ add_filter(
+ 'woocommerce_logging_class',
+ function () use ( $logger ) {
+ return $logger;
+ }
+ );
+
+ // Call the log method.
+ FraudProtectionController::log( 'info', 'Test message' );
+ }
+
+ /**
+ * Test logging with context data.
+ */
+ public function test_log_merges_context_with_source(): void {
+ // Mock the logger.
+ $logger = $this->getMockBuilder( \WC_Logger_Interface::class )
+ ->getMock();
+
+ $expected_context = array(
+ 'foo' => 'bar',
+ 'source' => 'woo-fraud-protection',
+ );
+
+ // Expect the log method to be called with merged context.
+ $logger->expects( $this->once() )
+ ->method( 'log' )
+ ->with(
+ $this->equalTo( 'debug' ),
+ $this->equalTo( 'Test with context' ),
+ $this->equalTo( $expected_context )
+ );
+
+ // Replace the logger with our mock.
+ add_filter(
+ 'woocommerce_logging_class',
+ function () use ( $logger ) {
+ return $logger;
+ }
+ );
+
+ // Call the log method with context.
+ FraudProtectionController::log( 'debug', 'Test with context', array( 'foo' => 'bar' ) );
+ }
+
+ /**
+ * Test that on_init does nothing when feature is disabled.
+ */
+ public function test_no_hooks_when_feature_disabled(): void {
+ // Ensure feature is disabled.
+ update_option( 'woocommerce_feature_fraud_protection_enabled', 'no' );
+
+ // Get a fresh controller instance.
+ $controller = $this->get_fresh_controller();
+
+ // Count hooks before calling on_init.
+ global $wp_filter;
+ $hook_count_before = count( $wp_filter );
+
+ // Call on_init.
+ $controller->on_init();
+
+ // Count hooks after - should be the same (no new hooks registered).
+ $hook_count_after = count( $wp_filter );
+
+ // Note: This is a basic test. In a full implementation, we would check
+ // for specific hooks that should be registered when enabled.
+ $this->assertEquals( $hook_count_before, $hook_count_after );
+ }
+
+ /**
+ * Test that register method registers init action.
+ */
+ public function test_register_registers_init_action(): void {
+ // Get a fresh controller instance.
+ $controller = $this->get_fresh_controller();
+
+ // Call register.
+ $controller->register();
+
+ // Check if the init action is registered for our callback.
+ $priority = has_action( 'init', array( $controller, 'on_init' ) );
+
+ // The priority should be 10 (default).
+ $this->assertSame( 10, $priority, 'Init action should be registered with default priority 10' );
+ }
+
+ /**
+ * Test that feature_is_enabled returns true when feature is enabled.
+ */
+ public function test_feature_is_enabled_returns_true_when_enabled(): void {
+ // Enable the feature.
+ update_option( 'woocommerce_feature_fraud_protection_enabled', 'yes' );
+
+ // Get a fresh controller instance to pick up the option change.
+ $controller = $this->get_fresh_controller();
+
+ // Check if the method returns true.
+ $this->assertTrue( $controller->feature_is_enabled() );
+ }
+
+ /**
+ * Test that feature_is_enabled returns false when feature is disabled.
+ */
+ public function test_feature_is_enabled_returns_false_when_disabled(): void {
+ // Disable the feature.
+ update_option( 'woocommerce_feature_fraud_protection_enabled', 'no' );
+
+ // Get a fresh controller instance to pick up the option change.
+ $controller = $this->get_fresh_controller();
+
+ // Check if the method returns false.
+ $this->assertFalse( $controller->feature_is_enabled() );
+ }
+
+ /**
+ * Cleanup after test.
+ */
+ public function tearDown(): void {
+ parent::tearDown();
+
+ // Clean up any filters or options.
+ remove_all_filters( 'woocommerce_logging_class' );
+ delete_option( 'woocommerce_feature_fraud_protection_enabled' );
+
+ // Remove any init hooks registered by the controller.
+ remove_all_actions( 'init' );
+
+ // Reset container.
+ wc_get_container()->reset_all_resolved();
+ }
+}