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();
+	}
+}