diff --git a/integrations/block-data-api.php b/integrations/block-data-api.php index 43334807a5..540efd359d 100644 --- a/integrations/block-data-api.php +++ b/integrations/block-data-api.php @@ -22,6 +22,14 @@ class BlockDataApiIntegration extends Integration { */ protected string $version = '1.0'; + /** + * Returns `true` if `Block Data API` is already available e.g. via customer code. We will use + * this function to prevent activating of integration from platform side. + */ + public function is_loaded(): bool { + return defined( 'VIP_BLOCK_DATA_API_LOADED' ); + } + /** * Applies hooks to load Block Data API plugin. * @@ -30,8 +38,11 @@ class BlockDataApiIntegration extends Integration { public function load(): void { // Wait until plugins_loaded to give precedence to the plugin in the customer repo. add_action( 'plugins_loaded', function() { - // Do not load plugin if already loaded by customer code. - if ( defined( 'VIP_BLOCK_DATA_API_LOADED' ) ) { + // Return if the integration is already loaded. + // + // In activate() method we do make sure to not activate the integration if its already loaded + // but still adding it here as a safety measure i.e. if load() is called directly. + if ( $this->is_loaded() ) { return; } @@ -44,4 +55,9 @@ public function load(): void { } } ); } + + /** + * Configure `Block Data API` for VIP Platform. + */ + public function configure(): void {} } diff --git a/integrations/integration.php b/integrations/integration.php index 5e1f0913cd..f1e3f85e14 100644 --- a/integrations/integration.php +++ b/integrations/integration.php @@ -63,10 +63,17 @@ public function __construct( string $slug ) { * @private */ public function activate( array $options = [] ): void { + // If integration is already available in customer code then don't activate it from platform side. + if ( $this->is_loaded() ) { + trigger_error( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error + sprintf( 'Prevented activating of integration with slug "%s" because it is already loaded.', esc_html( $this->slug ) ), + E_USER_WARNING + ); + } + // Don't do anything if integration is already activated. if ( $this->is_active() ) { trigger_error( sprintf( 'VIP Integration with slug "%s" is already activated.', esc_html( $this->get_slug() ) ), E_USER_WARNING ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error - return; } $this->is_active = true; @@ -102,6 +109,14 @@ public function get_slug(): string { return $this->slug; } + /** + * Returns `true` if the integration is already available e.g. via customer code. We will use + * this function to prevent activating of integration again. + * + * @private + */ + abstract public function is_loaded(): bool; + /** * Implement custom action and filter calls to load integration here. * @@ -112,4 +127,14 @@ public function get_slug(): string { * @private */ abstract public function load(): void; + + /** + * Configure the integration for VIP platform. + * + * If we want to implement functionality only if the integration is enabled via VIP + * then we will use this function. + * + * @private + */ + abstract public function configure(): void; } diff --git a/integrations/integrations.php b/integrations/integrations.php index 2690f41906..6c9a4db598 100644 --- a/integrations/integrations.php +++ b/integrations/integrations.php @@ -69,6 +69,11 @@ public function activate_platform_integrations() { $this->activate( $slug, [ 'config' => $vip_config->get_site_config(), ] ); + + // If integration is activated successfully without any error then configure. + if ( $integration->is_active() ) { + $integration->configure(); + } } } } diff --git a/integrations/parsely.php b/integrations/parsely.php index cf44d8cebd..c7d08dc607 100644 --- a/integrations/parsely.php +++ b/integrations/parsely.php @@ -13,14 +13,25 @@ * @private */ class ParselyIntegration extends Integration { + /** + * Returns `true` if `Parse.ly` is already available e.g. customer code. We will use + * this function to prevent loading of integration again from platform side. + */ + public function is_loaded(): bool { + return class_exists( 'Parsely' ) || class_exists( 'Parsely\Parsely' ); + } + /** * Loads the plugin. * * @private */ public function load(): void { - // Do not load plugin if already loaded by customer code. - if ( class_exists( 'Parsely\Parsely' ) ) { + // Return if the integration is already loaded. + // + // In activate() method we do make sure to not activate the integration if its already loaded + // but still adding it here as a safety measure i.e. if load() is called directly. + if ( $this->is_loaded() ) { return; } @@ -28,9 +39,19 @@ public function load(): void { // handled via Automattic\VIP\WP_Parsely_Integration (ideally we should move // all the implementation here such that there will be only one way of managing // the plugin). - define( 'VIP_PARSELY_ENABLED', true ); + if ( ! defined( 'VIP_PARSELY_ENABLED' ) ) { + define( 'VIP_PARSELY_ENABLED', true ); + } + } + /** + * Configure `Parse.ly` for VIP Platform. + * + * @private + */ + public function configure(): void { add_filter( 'wp_parsely_credentials', array( $this, 'wp_parsely_credentials_callback' ) ); + add_filter( 'wp_parsely_managed_options', array( $this, 'wp_parsely_managed_options_callback' ) ); } /** @@ -55,4 +76,24 @@ public function wp_parsely_credentials_callback( $original_credentials ) { // and we have to hide the credential banner warning or more. return array_merge( array( 'is_managed' => true ), $credentials ); } + + /** + * Callback for `wp_parsely_managed_options` filter. + * + * @param array $value Value passed to filter. + * + * @return array + */ + public function wp_parsely_managed_options_callback( $value ) { + return array( + 'force_https_canonicals' => true, + 'meta_type' => 'repeated_metas', + // Managed options that will obey the values on database. + 'cats_as_tags' => null, + 'content_id_prefix' => null, + 'logo' => null, + 'lowercase_tags' => null, + 'use_top_level_cats' => null, + ); + } } diff --git a/tests/integrations/fake-integration.php b/tests/integrations/fake-integration.php index edce900d4e..9fe8f036bc 100644 --- a/tests/integrations/fake-integration.php +++ b/tests/integrations/fake-integration.php @@ -11,5 +11,11 @@ class FakeIntegration extends Integration { + public function is_loaded(): bool { + return false; + } + public function load(): void { } + + public function configure(): void { } } diff --git a/tests/integrations/test-integration.php b/tests/integrations/test-integration.php index eca587fb57..b70c68a199 100644 --- a/tests/integrations/test-integration.php +++ b/tests/integrations/test-integration.php @@ -9,6 +9,7 @@ // phpcs:disable Squiz.Commenting.ClassComment.Missing, Squiz.Commenting.FunctionComment.Missing, Squiz.Commenting.FunctionComment.MissingParamComment +use PHPUnit\Framework\MockObject\MockObject; use WP_UnitTestCase; require_once __DIR__ . '/fake-integration.php'; @@ -36,6 +37,22 @@ public function test__activate_is_setting_up_the_plugins_config(): void { $this->assertEquals( [ 'config_test' ], $integration->get_config() ); } + public function test__calling_activate_when_the_integration_is_already_loaded_does_not_activate_the_integration_again(): void { + $this->expectException( 'PHPUnit_Framework_Error_Warning' ); + $this->expectExceptionMessage( 'Prevented activating of integration with slug "fake" because it is already loaded.' ); + /** + * Integration mock. + * + * @var MockObject|FakeIntegration + */ + $integration_mock = $this->getMockBuilder( FakeIntegration::class )->setConstructorArgs( [ 'fake' ] )->setMethods( [ 'is_loaded' ] )->getMock(); + $integration_mock->expects( $this->once() )->method( 'is_loaded' )->willReturn( true ); + + $integration_mock->activate(); + + $this->assertFalse( $integration_mock->is_active() ); + } + public function test__calling_activate_twice_on_same_integration_does_not_activate_the_plugin_second_time(): void { $this->expectException( 'PHPUnit_Framework_Error_Warning' ); $this->expectExceptionMessage( 'VIP Integration with slug "fake" is already activated.' ); @@ -44,6 +61,8 @@ public function test__calling_activate_twice_on_same_integration_does_not_activa $integration->activate(); $integration->activate(); + + $this->assertFalse( $integration->is_active() ); } public function test__is_active_returns_false_when_integration_is_not_active(): void { diff --git a/tests/integrations/test-integrations.php b/tests/integrations/test-integrations.php index 2da3e65f0e..e4b848ba71 100644 --- a/tests/integrations/test-integrations.php +++ b/tests/integrations/test-integrations.php @@ -52,6 +52,31 @@ public function test__integrations_are_activating_based_on_given_vip_config(): v $this->assertEquals( [], $integration_3->get_config() ); } + public function test__configure_is_getting_called_when_the_integration_is_activated_via_vip_config(): void { + $config_mock = $this->getMockBuilder( IntegrationVipConfig::class )->disableOriginalConstructor()->setMethods( [ 'is_active_via_vip' ] )->getMock(); + $config_mock->expects( $this->once() )->method( 'is_active_via_vip' )->willReturn( true ); + /** + * Integrations mock. + * + * @var MockObject|Integrations + */ + $integrations_mock = $this->getMockBuilder( Integrations::class )->setMethods( [ 'get_integration_vip_config' ] )->getMock(); + $integrations_mock->expects( $this->once() )->method( 'get_integration_vip_config' )->willReturn( $config_mock ); + /** + * Integration mock. + * + * @var MockObject|FakeIntegration + */ + $integration_mock = $this->getMockBuilder( FakeIntegration::class )->setConstructorArgs( [ 'fake' ] )->setMethods( [ 'configure' ] )->getMock(); + $integration_mock->expects( $this->once() )->method( 'configure' ); + + $integrations_mock->register( $integration_mock ); + $integrations_mock->activate_platform_integrations(); + + $this->assertTrue( $integration_mock->is_active() ); + $this->assertEquals( [], $integration_mock->get_config() ); + } + public function test__get_integration_vip_config_returns_instance_of_IntegrationVipConfig(): void { $integrations = new Integrations(); diff --git a/tests/integrations/test-parsely.php b/tests/integrations/test-parsely.php index 32905bbccf..713d93397d 100644 --- a/tests/integrations/test-parsely.php +++ b/tests/integrations/test-parsely.php @@ -7,33 +7,64 @@ namespace Automattic\VIP\Integrations; +use PHPUnit\Framework\MockObject\MockObject; use WP_UnitTestCase; use function Automattic\Test\Utils\get_class_property_as_public; -use function Automattic\Test\Utils\is_parsely_disabled; -use function Automattic\VIP\WP_Parsely_Integration\maybe_load_plugin; // phpcs:disable Squiz.Commenting.ClassComment.Missing, Squiz.Commenting.FunctionComment.Missing, Squiz.Commenting.VariableComment.Missing class VIP_Parsely_Integration_Test extends WP_UnitTestCase { private string $slug = 'parsely'; - public function test__load_call_is_defining_the_enabled_constant_and_adding_filter_if_plugin_is_not_enabled_already(): void { + public function test_is_loaded_returns_true_if_parsley_exist(): void { + require_once __DIR__ . '/../../wp-parsely/wp-parsely.php'; + $parsely_integration = new ParselyIntegration( $this->slug ); + $this->assertTrue( $parsely_integration->is_loaded() ); + } - maybe_load_plugin(); - $parsely_integration->load( [] ); + public function test__load_call_returns_without_setting_constant_if_parsely_is_already_loaded(): void { + /** + * Integration mock. + * + * @var MockObject|ParselyIntegration + */ + $parsely_integration_mock = $this->getMockBuilder( ParselyIntegration::class )->setConstructorArgs( [ 'parsely' ] )->setMethods( [ 'is_loaded' ] )->getMock(); + $parsely_integration_mock->expects( $this->once() )->method( 'is_loaded' )->willReturn( true ); + $preload_state = defined( 'VIP_PARSELY_ENABLED' ); - if ( is_parsely_disabled() ) { - $this->assertTrue( defined( 'VIP_PARSELY_ENABLED' ) ); - $this->assertEquals( 10, has_filter( 'wp_parsely_credentials', [ $parsely_integration, 'wp_parsely_credentials_callback' ] ) ); + $parsely_integration_mock->load(); + + $this->assertEquals( $preload_state, defined( 'VIP_PARSELY_ENABLED' ) ); + } - return; + public function test__load_call_is_setting_the_enabled_constant_if_no_constant_is_defined(): void { + /** + * Integration mock. + * + * @var MockObject|ParselyIntegration + */ + $parsely_integration_mock = $this->getMockBuilder( ParselyIntegration::class )->setConstructorArgs( [ 'parsely' ] )->setMethods( [ 'is_loaded' ] )->getMock(); + $parsely_integration_mock->expects( $this->once() )->method( 'is_loaded' )->willReturn( false ); + $existing_value = defined( 'VIP_PARSELY_ENABLED' ) ? VIP_PARSELY_ENABLED : null; + + $parsely_integration_mock->load(); + + if ( is_null( $existing_value ) || true == $existing_value ) { + $this->assertTrue( VIP_PARSELY_ENABLED ); + } else { + $this->assertFalse( defined( 'VIP_PARSELY_ENABLED' ) ); } + } + + public function test__configure_is_adding_necessary_hooks_need_for_configuration_on_vip_platform(): void { + $parsely_integration = new ParselyIntegration( $this->slug ); - // Indicates enablement via filter or option. - $this->assertFalse( defined( 'VIP_PARSELY_ENABLED' ) ); - $this->assertFalse( has_filter( 'wp_parsely_credentials' ) ); + $parsely_integration->configure(); + + $this->assertEquals( 10, has_filter( 'wp_parsely_credentials', [ $parsely_integration, 'wp_parsely_credentials_callback' ] ) ); + $this->assertEquals( 10, has_filter( 'wp_parsely_managed_options', [ $parsely_integration, 'wp_parsely_managed_options_callback' ] ) ); } public function test__wp_parsely_credentials_callback_returns_original_credentials_of_the_integration_if_platform_config_is_empty(): void { @@ -67,4 +98,19 @@ public function test__wp_parsely_credentials_callback_returns_platform_credentia 'api_secret' => null, ], $callback_value ); } + + public function test__wp_parsely_managed_options_callback_returns_all_managed_options(): void { + $parsely_integration = new ParselyIntegration( $this->slug ); + $callback_value = $parsely_integration->wp_parsely_managed_options_callback( false ); + + $this->assertEquals( [ + 'force_https_canonicals' => true, + 'meta_type' => 'repeated_metas', + 'cats_as_tags' => null, + 'content_id_prefix' => null, + 'logo' => null, + 'lowercase_tags' => null, + 'use_top_level_cats' => null, + ], $callback_value ); + } }