From 41f3cd0a616cd679e060cabb1210e33ed66d0dcc Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 6 May 2025 18:40:42 +0200 Subject: [PATCH 01/63] =?UTF-8?q?=F0=9F=A7=91=E2=80=8D=F0=9F=92=BB=20Add?= =?UTF-8?q?=20Google=20Pay=203DS=20test=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-googlepay/docs/payment-test.html | 286 ++++++++++++++++++ 1 file changed, 286 insertions(+) create mode 100644 modules/ppcp-googlepay/docs/payment-test.html diff --git a/modules/ppcp-googlepay/docs/payment-test.html b/modules/ppcp-googlepay/docs/payment-test.html new file mode 100644 index 000000000..514f4b69c --- /dev/null +++ b/modules/ppcp-googlepay/docs/payment-test.html @@ -0,0 +1,286 @@ + + + + + Google Pay Test + + + +
+
+

Google Pay with PayPal SDK Integration

+

Click the Google Pay button below to complete a test payment.

+
+
+
+
+ + + + + From 676306f7935401b21d924d5836e1165661fe9742 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 6 May 2025 19:01:46 +0200 Subject: [PATCH 02/63] =?UTF-8?q?=F0=9F=92=84=20Add=20form=20to=20enter=20?= =?UTF-8?q?client=20ID=20and=20secret?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-googlepay/docs/payment-test.html | 94 +++++++++++++++++-- 1 file changed, 84 insertions(+), 10 deletions(-) diff --git a/modules/ppcp-googlepay/docs/payment-test.html b/modules/ppcp-googlepay/docs/payment-test.html index 514f4b69c..d9378639f 100644 --- a/modules/ppcp-googlepay/docs/payment-test.html +++ b/modules/ppcp-googlepay/docs/payment-test.html @@ -7,21 +7,79 @@
-
-

Google Pay with PayPal SDK Integration

-

Click the Google Pay button below to complete a test payment.

+

Google Pay with PayPal SDK Integration

+
+

+ PayPal client ID: + +

+ + + From 3e84cd9e6cf5f04d227bb636fba9726aa459b0cb Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 8 May 2025 12:25:58 +0200 Subject: [PATCH 05/63] =?UTF-8?q?=F0=9F=92=A1=20Document=20the=20URL=20to?= =?UTF-8?q?=20the=20test=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-axo/docs/payment-test.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/ppcp-axo/docs/payment-test.html b/modules/ppcp-axo/docs/payment-test.html index e3b1aa81b..8f49287a0 100644 --- a/modules/ppcp-axo/docs/payment-test.html +++ b/modules/ppcp-axo/docs/payment-test.html @@ -1,5 +1,8 @@ + From 1203d6e8c55ddc0a30c83e53610dbf314c2ccc86 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 16 May 2025 15:39:48 +0200 Subject: [PATCH 06/63] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Improve=20the=20Goog?= =?UTF-8?q?le=20Pay=20test=20script?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Main change: The SCA selector is always visible and can be changed independently of the client credentials --- modules/ppcp-googlepay/docs/payment-test.html | 214 +++++++++++------- 1 file changed, 129 insertions(+), 85 deletions(-) diff --git a/modules/ppcp-googlepay/docs/payment-test.html b/modules/ppcp-googlepay/docs/payment-test.html index 85823a29c..214886d60 100644 --- a/modules/ppcp-googlepay/docs/payment-test.html +++ b/modules/ppcp-googlepay/docs/payment-test.html @@ -1,62 +1,145 @@ Google Pay Test + +

Google Pay with PayPal SDK Integration

-

- PayPal client ID: +

+
+ PayPal client ID: + +
-

+
+
+ + +
+ + - From 30b7a592c5bbfbe11e2f163625c82c4a7ee50bb4 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 4 Jul 2025 17:09:36 +0400 Subject: [PATCH 07/63] Create a centralized service for a path to the main plugin file. --- src/services.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/services.php b/src/services.php index 99654f2fb..eab917b95 100644 --- a/src/services.php +++ b/src/services.php @@ -30,4 +30,6 @@ return array( 'http.redirector' => function( ContainerInterface $container ) : RedirectorInterface { return new WpRedirector(); }, + 'ppcp.plugin-path' => static fn(): string => + dirname( realpath( __FILE__ ) ?: __FILE__, 2 ) . '/woocommerce-paypal-payments.php', ); From 0caa74c021cd1167891df6c4b1d4e4ab7b0335ac Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 4 Jul 2025 17:10:37 +0400 Subject: [PATCH 08/63] Refactor the code to use the new service --- modules/ppcp-admin-notices/services.php | 9 +-------- modules/ppcp-applepay/services.php | 9 +-------- modules/ppcp-axo-block/services.php | 10 +--------- modules/ppcp-axo/services.php | 9 +-------- modules/ppcp-blocks/services.php | 10 +--------- modules/ppcp-button/services.php | 5 +---- modules/ppcp-compat/services.php | 10 +--------- modules/ppcp-googlepay/services.php | 9 +-------- .../services.php | 10 +--------- modules/ppcp-onboarding/services.php | 5 +---- modules/ppcp-order-tracking/services.php | 10 +--------- modules/ppcp-paylater-block/services.php | 10 +--------- modules/ppcp-paylater-configurator/services.php | 12 +----------- modules/ppcp-paylater-wc-blocks/services.php | 10 +--------- modules/ppcp-paypal-subscriptions/services.php | 10 +--------- modules/ppcp-save-payment-methods/services.php | 10 +--------- modules/ppcp-settings/services.php | 10 +--------- modules/ppcp-uninstall/services.php | 10 +--------- modules/ppcp-vaulting/services.php | 5 +---- modules/ppcp-wc-gateway/services.php | 10 ++-------- modules/ppcp-webhooks/services.php | 5 +---- 21 files changed, 22 insertions(+), 166 deletions(-) diff --git a/modules/ppcp-admin-notices/services.php b/modules/ppcp-admin-notices/services.php index 1d9f22b8b..8170916bc 100644 --- a/modules/ppcp-admin-notices/services.php +++ b/modules/ppcp-admin-notices/services.php @@ -18,14 +18,7 @@ use WooCommerce\PayPalCommerce\AdminNotices\Endpoint\MuteMessageEndpoint; return array( 'admin-notices.url' => static function ( ContainerInterface $container ): string { - $path = realpath( __FILE__ ); - if ( false === $path ) { - return ''; - } - return plugins_url( - '/modules/ppcp-admin-notices/', - dirname( $path, 3 ) . '/woocommerce-paypal-payments.php' - ); + return plugins_url( '/modules/ppcp-admin-notices/', $container->get( 'ppcp.plugin-path' ) ); }, 'admin-notices.renderer' => static function ( ContainerInterface $container ): RendererInterface { return new Renderer( diff --git a/modules/ppcp-applepay/services.php b/modules/ppcp-applepay/services.php index 7112b1f82..77cab8af7 100644 --- a/modules/ppcp-applepay/services.php +++ b/modules/ppcp-applepay/services.php @@ -136,14 +136,7 @@ return array( return false; }, 'applepay.url' => static function ( ContainerInterface $container ): string { - $path = realpath( __FILE__ ); - if ( false === $path ) { - return ''; - } - return plugins_url( - '/modules/ppcp-applepay/', - dirname( $path, 3 ) . '/woocommerce-paypal-payments.php' - ); + return plugins_url( '/modules/ppcp-applepay/', $container->get( 'ppcp.plugin-path' ) ); }, 'applepay.sdk_script_url' => static function ( ContainerInterface $container ): string { return 'https://applepay.cdn-apple.com/jsapi/v1/apple-pay-sdk.js'; diff --git a/modules/ppcp-axo-block/services.php b/modules/ppcp-axo-block/services.php index a93d9096c..cd45753df 100644 --- a/modules/ppcp-axo-block/services.php +++ b/modules/ppcp-axo-block/services.php @@ -18,15 +18,7 @@ return array( return true; }, 'axoblock.url' => static function ( ContainerInterface $container ) : string { - /** - * The path cannot be false. - * - * @psalm-suppress PossiblyFalseArgument - */ - return plugins_url( - '/modules/ppcp-axo-block/', - dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' - ); + return plugins_url( '/modules/ppcp-axo-block/', $container->get( 'ppcp.plugin-path' ) ); }, 'axoblock.method' => static function ( ContainerInterface $container ) : AxoBlockPaymentMethod { return new AxoBlockPaymentMethod( diff --git a/modules/ppcp-axo/services.php b/modules/ppcp-axo/services.php index bd9882487..226fe2fb2 100644 --- a/modules/ppcp-axo/services.php +++ b/modules/ppcp-axo/services.php @@ -59,14 +59,7 @@ return array( }, 'axo.url' => static function ( ContainerInterface $container ): string { - $path = realpath( __FILE__ ); - if ( false === $path ) { - return ''; - } - return plugins_url( - '/modules/ppcp-axo/', - dirname( $path, 3 ) . '/woocommerce-paypal-payments.php' - ); + return plugins_url( '/modules/ppcp-axo/', $container->get( 'ppcp.plugin-path' ) ); }, 'axo.manager' => static function ( ContainerInterface $container ): AxoManager { diff --git a/modules/ppcp-blocks/services.php b/modules/ppcp-blocks/services.php index 996240a79..5f7dfe5bf 100644 --- a/modules/ppcp-blocks/services.php +++ b/modules/ppcp-blocks/services.php @@ -17,15 +17,7 @@ use WC_Cart; return array( 'blocks.url' => static function ( ContainerInterface $container ): string { - /** - * The path cannot be false. - * - * @psalm-suppress PossiblyFalseArgument - */ - return plugins_url( - '/modules/ppcp-blocks/', - dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' - ); + return plugins_url( '/modules/ppcp-blocks/', $container->get( 'ppcp.plugin-path' ) ); }, 'blocks.method' => static function ( ContainerInterface $container ): PayPalPaymentMethod { return new PayPalPaymentMethod( diff --git a/modules/ppcp-button/services.php b/modules/ppcp-button/services.php index a9bbdde85..7eb31c987 100644 --- a/modules/ppcp-button/services.php +++ b/modules/ppcp-button/services.php @@ -174,10 +174,7 @@ return array( ); }, 'button.url' => static function ( ContainerInterface $container ): string { - return plugins_url( - '/modules/ppcp-button/', - dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' - ); + return plugins_url( '/modules/ppcp-button/', $container->get( 'ppcp.plugin-path' ) ); }, 'button.pay-now-contexts' => static function ( ContainerInterface $container ): array { $defaults = array( 'checkout', 'pay-now' ); diff --git a/modules/ppcp-compat/services.php b/modules/ppcp-compat/services.php index c862d2bc8..8e6021436 100644 --- a/modules/ppcp-compat/services.php +++ b/modules/ppcp-compat/services.php @@ -101,15 +101,7 @@ return array( }, 'compat.module.url' => static function ( ContainerInterface $container ): string { - /** - * The path cannot be false. - * - * @psalm-suppress PossiblyFalseArgument - */ - return plugins_url( - '/modules/ppcp-compat/', - dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' - ); + return plugins_url( '/modules/ppcp-compat/', $container->get( 'ppcp.plugin-path' ) ); }, 'compat.assets' => function( ContainerInterface $container ) : CompatAssets { diff --git a/modules/ppcp-googlepay/services.php b/modules/ppcp-googlepay/services.php index 8a610833e..8b9489b0b 100644 --- a/modules/ppcp-googlepay/services.php +++ b/modules/ppcp-googlepay/services.php @@ -205,14 +205,7 @@ return array( }, 'googlepay.url' => static function ( ContainerInterface $container ): string { - $path = realpath( __FILE__ ); - if ( false === $path ) { - return ''; - } - return plugins_url( - '/modules/ppcp-googlepay/', - dirname( $path, 3 ) . '/woocommerce-paypal-payments.php' - ); + return plugins_url( '/modules/ppcp-googlepay/', $container->get( 'ppcp.plugin-path' ) ); }, 'googlepay.sdk_url' => static function ( ContainerInterface $container ): string { diff --git a/modules/ppcp-local-alternative-payment-methods/services.php b/modules/ppcp-local-alternative-payment-methods/services.php index b2dc270fb..56ff0e10c 100644 --- a/modules/ppcp-local-alternative-payment-methods/services.php +++ b/modules/ppcp-local-alternative-payment-methods/services.php @@ -14,15 +14,7 @@ use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\LocalApmProductSta return array( 'ppcp-local-apms.url' => static function ( ContainerInterface $container ): string { - /** - * The path cannot be false. - * - * @psalm-suppress PossiblyFalseArgument - */ - return plugins_url( - '/modules/ppcp-local-alternative-payment-methods/', - dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' - ); + return plugins_url( '/modules/ppcp-local-alternative-payment-methods/', $container->get( 'ppcp.plugin-path' ) ); }, 'ppcp-local-apms.payment-methods' => static function( ContainerInterface $container ): array { return array( diff --git a/modules/ppcp-onboarding/services.php b/modules/ppcp-onboarding/services.php index 71bab9570..c1d4be1f8 100644 --- a/modules/ppcp-onboarding/services.php +++ b/modules/ppcp-onboarding/services.php @@ -123,10 +123,7 @@ return array( }, 'onboarding.url' => static function ( ContainerInterface $container ): string { - return plugins_url( - '/modules/ppcp-onboarding/', - dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' - ); + return plugins_url( '/modules/ppcp-onboarding/', $container->get( 'ppcp.plugin-path' ) ); }, 'onboarding.endpoint.login-seller' => static function ( ContainerInterface $container ) : LoginSellerEndpoint { diff --git a/modules/ppcp-order-tracking/services.php b/modules/ppcp-order-tracking/services.php index ad7aaa3ea..004389f17 100644 --- a/modules/ppcp-order-tracking/services.php +++ b/modules/ppcp-order-tracking/services.php @@ -43,15 +43,7 @@ return array( ); }, 'order-tracking.module.url' => static function ( ContainerInterface $container ): string { - /** - * The path cannot be false. - * - * @psalm-suppress PossiblyFalseArgument - */ - return plugins_url( - '/modules/ppcp-order-tracking/', - dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' - ); + return plugins_url( '/modules/ppcp-order-tracking/', $container->get( 'ppcp.plugin-path' ) ); }, 'order-tracking.meta-box.renderer' => static function ( ContainerInterface $container ): MetaBoxRenderer { return new MetaBoxRenderer( diff --git a/modules/ppcp-paylater-block/services.php b/modules/ppcp-paylater-block/services.php index 0b6e02ee5..a9a1a8b38 100644 --- a/modules/ppcp-paylater-block/services.php +++ b/modules/ppcp-paylater-block/services.php @@ -14,15 +14,7 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; return array( 'paylater-block.url' => static function ( ContainerInterface $container ): string { - /** - * Cannot return false for this path. - * - * @psalm-suppress PossiblyFalseArgument - */ - return plugins_url( - '/modules/ppcp-paylater-block/', - dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' - ); + return plugins_url( '/modules/ppcp-paylater-block/', $container->get( 'ppcp.plugin-path' ) ); }, 'paylater-block.renderer' => static function (): PayLaterBlockRenderer { return new PayLaterBlockRenderer(); diff --git a/modules/ppcp-paylater-configurator/services.php b/modules/ppcp-paylater-configurator/services.php index 2f71b3845..d6ace8e2d 100644 --- a/modules/ppcp-paylater-configurator/services.php +++ b/modules/ppcp-paylater-configurator/services.php @@ -19,17 +19,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; return array( 'paylater-configurator.url' => static function ( ContainerInterface $container ): string { - /** - * The return value must not contain a trailing slash. - * - * Cannot return false for this path. - * - * @psalm-suppress PossiblyFalseArgument - */ - return plugins_url( - '/modules/ppcp-paylater-configurator', - dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' - ); + return plugins_url( '/modules/ppcp-paylater-configurator', $container->get( 'ppcp.plugin-path' ) ); }, 'paylater-configurator.factory.config' => static function ( ContainerInterface $container ): ConfigFactory { return new ConfigFactory(); diff --git a/modules/ppcp-paylater-wc-blocks/services.php b/modules/ppcp-paylater-wc-blocks/services.php index 76168bbf2..6acf200de 100644 --- a/modules/ppcp-paylater-wc-blocks/services.php +++ b/modules/ppcp-paylater-wc-blocks/services.php @@ -13,15 +13,7 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; return array( 'paylater-wc-blocks.url' => static function ( ContainerInterface $container ): string { - /** - * Cannot return false for this path. - * - * @psalm-suppress PossiblyFalseArgument - */ - return plugins_url( - '/modules/ppcp-paylater-wc-blocks/', - dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' - ); + return plugins_url( '/modules/ppcp-paylater-wc-blocks/', $container->get( 'ppcp.plugin-path' ) ); }, 'paylater-wc-blocks.cart-renderer' => static function ( ContainerInterface $container ): PayLaterWCBlocksRenderer { diff --git a/modules/ppcp-paypal-subscriptions/services.php b/modules/ppcp-paypal-subscriptions/services.php index 86fb81376..7ba7c63c9 100644 --- a/modules/ppcp-paypal-subscriptions/services.php +++ b/modules/ppcp-paypal-subscriptions/services.php @@ -30,15 +30,7 @@ return array( ); }, 'paypal-subscriptions.module.url' => static function ( ContainerInterface $container ): string { - /** - * The path cannot be false. - * - * @psalm-suppress PossiblyFalseArgument - */ - return plugins_url( - '/modules/ppcp-paypal-subscriptions/', - dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' - ); + return plugins_url( '/modules/ppcp-paypal-subscriptions/', $container->get( 'ppcp.plugin-path' ) ); }, 'paypal-subscriptions.renewal-handler' => static function ( ContainerInterface $container ): RenewalHandler { return new RenewalHandler( $container->get( 'woocommerce.logger.woocommerce' ) ); diff --git a/modules/ppcp-save-payment-methods/services.php b/modules/ppcp-save-payment-methods/services.php index b448142cc..da58d26f6 100644 --- a/modules/ppcp-save-payment-methods/services.php +++ b/modules/ppcp-save-payment-methods/services.php @@ -87,15 +87,7 @@ return array( ); }, 'save-payment-methods.module.url' => static function ( ContainerInterface $container ): string { - /** - * The path cannot be false. - * - * @psalm-suppress PossiblyFalseArgument - */ - return plugins_url( - '/modules/ppcp-save-payment-methods/', - dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' - ); + return plugins_url( '/modules/ppcp-save-payment-methods/', $container->get( 'ppcp.plugin-path' ) ); }, 'save-payment-methods.endpoint.create-setup-token' => static function ( ContainerInterface $container ): CreateSetupToken { return new CreateSetupToken( diff --git a/modules/ppcp-settings/services.php b/modules/ppcp-settings/services.php index 03fca0f49..9988124db 100644 --- a/modules/ppcp-settings/services.php +++ b/modules/ppcp-settings/services.php @@ -75,15 +75,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\MerchantDetails; return array( 'settings.url' => static function ( ContainerInterface $container ) : string { - /** - * The path cannot be false. - * - * @psalm-suppress PossiblyFalseArgument - */ - return plugins_url( - '/modules/ppcp-settings/', - dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' - ); + return plugins_url( '/modules/ppcp-settings/', $container->get( 'ppcp.plugin-path' ) ); }, 'settings.data.onboarding' => static function ( ContainerInterface $container ) : OnboardingProfile { $can_use_casual_selling = $container->get( 'settings.casual-selling.eligible' ); diff --git a/modules/ppcp-uninstall/services.php b/modules/ppcp-uninstall/services.php index 629f1164b..7c0df1698 100644 --- a/modules/ppcp-uninstall/services.php +++ b/modules/ppcp-uninstall/services.php @@ -79,15 +79,7 @@ return array( }, 'uninstall.module-url' => static function ( ContainerInterface $container ): string { - /** - * The path cannot be false. - * - * @psalm-suppress PossiblyFalseArgument - */ - return plugins_url( - '/modules/ppcp-uninstall/', - dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' - ); + return plugins_url( '/modules/ppcp-uninstall/', $container->get( 'ppcp.plugin-path' ) ); }, 'uninstall.clear-db-assets' => function( ContainerInterface $container ) : ClearDatabaseAssets { diff --git a/modules/ppcp-vaulting/services.php b/modules/ppcp-vaulting/services.php index da0d1d209..58710e694 100644 --- a/modules/ppcp-vaulting/services.php +++ b/modules/ppcp-vaulting/services.php @@ -13,10 +13,7 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; return array( 'vaulting.module-url' => static function ( ContainerInterface $container ): string { - return plugins_url( - '/modules/ppcp-vaulting/', - dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' - ); + return plugins_url( '/modules/ppcp-vaulting/', $container->get( 'ppcp.plugin-path' ) ); }, 'vaulting.repository.payment-token' => static function ( ContainerInterface $container ): PaymentTokenRepository { $factory = $container->get( 'api.factory.payment-token' ); diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index b1101dec7..1005a77c9 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -1351,19 +1351,13 @@ return array( ); }, 'wcgateway.url' => static function ( ContainerInterface $container ): string { - return plugins_url( - $container->get( 'wcgateway.relative-path' ), - dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' - ); + return plugins_url( $container->get( 'wcgateway.relative-path' ), $container->get( 'ppcp.plugin-path' ) ); }, 'wcgateway.relative-path' => static function( ContainerInterface $container ): string { return 'modules/ppcp-wc-gateway/'; }, 'wcgateway.absolute-path' => static function( ContainerInterface $container ): string { - return plugin_dir_path( - dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' - ) . - $container->get( 'wcgateway.relative-path' ); + return plugin_dir_path( $container->get( 'ppcp.plugin-path' ) ) . $container->get( 'wcgateway.relative-path' ); }, 'wcgateway.endpoint.return-url' => static function ( ContainerInterface $container ) : ReturnUrlEndpoint { $gateway = $container->get( 'wcgateway.paypal-gateway' ); diff --git a/modules/ppcp-webhooks/services.php b/modules/ppcp-webhooks/services.php index e000a1690..96a072267 100644 --- a/modules/ppcp-webhooks/services.php +++ b/modules/ppcp-webhooks/services.php @@ -220,9 +220,6 @@ return array( }, 'webhook.module-url' => static function ( ContainerInterface $container ): string { - return plugins_url( - '/modules/ppcp-webhooks/', - dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' - ); + return plugins_url( '/modules/ppcp-webhooks/', $container->get( 'ppcp.plugin-path' ) ); }, ); From e713abfd75ef2389dd2bd815d0024df561c3a31f Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 4 Jul 2025 17:11:20 +0400 Subject: [PATCH 09/63] Just return `__FILE__` when the `realpath` fails. --- .../src/PayLaterBlockModule.php | 7 +------ .../src/PayLaterWCBlocksModule.php | 14 ++------------ .../src/Service/ScriptDataHandler.php | 9 ++------- modules/ppcp-settings/src/SettingsModule.php | 7 +------ 4 files changed, 6 insertions(+), 31 deletions(-) diff --git a/modules/ppcp-paylater-block/src/PayLaterBlockModule.php b/modules/ppcp-paylater-block/src/PayLaterBlockModule.php index 32d8dc762..bc6d45123 100644 --- a/modules/ppcp-paylater-block/src/PayLaterBlockModule.php +++ b/modules/ppcp-paylater-block/src/PayLaterBlockModule.php @@ -101,13 +101,8 @@ class PayLaterBlockModule implements ServiceModule, ExtendingModule, ExecutableM ) ); - /** - * Cannot return false for this path. - * - * @psalm-suppress PossiblyFalseArgument - */ register_block_type( - dirname( realpath( __FILE__ ), 2 ), + dirname( realpath( __FILE__ ) ?: __FILE__, 2 ), array( 'render_callback' => function ( array $attributes ) use ( $c ) { $renderer = $c->get( 'paylater-block.renderer' ); diff --git a/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksModule.php b/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksModule.php index 2a629b8f0..0099f7b32 100644 --- a/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksModule.php +++ b/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksModule.php @@ -192,13 +192,8 @@ class PayLaterWCBlocksModule implements ServiceModule, ExtendingModule, Executab return; } - /** - * Cannot return false for this path. - * - * @psalm-suppress PossiblyFalseArgument - */ register_block_type( - dirname( realpath( __FILE__ ), 2 ) . '/resources/js/CartPayLaterMessagesBlock', + dirname( realpath( __FILE__ ) ?: __FILE__, 2 ) . '/resources/js/CartPayLaterMessagesBlock', array( 'render_callback' => function ( array $attributes ) use ( $c ) { return PayLaterWCBlocksUtils::render_paylater_block( @@ -211,13 +206,8 @@ class PayLaterWCBlocksModule implements ServiceModule, ExtendingModule, Executab ) ); - /** - * Cannot return false for this path. - * - * @psalm-suppress PossiblyFalseArgument - */ register_block_type( - dirname( realpath( __FILE__ ), 2 ) . '/resources/js/CheckoutPayLaterMessagesBlock', + dirname( $path, 2 ) . '/resources/js/CheckoutPayLaterMessagesBlock', array( 'render_callback' => function ( array $attributes ) use ( $c ) { return PayLaterWCBlocksUtils::render_paylater_block( diff --git a/modules/ppcp-settings/src/Service/ScriptDataHandler.php b/modules/ppcp-settings/src/Service/ScriptDataHandler.php index 22e3fe49f..7f83ce4cf 100644 --- a/modules/ppcp-settings/src/Service/ScriptDataHandler.php +++ b/modules/ppcp-settings/src/Service/ScriptDataHandler.php @@ -109,7 +109,7 @@ class ScriptDataHandler { * * @psalm-suppress UnresolvableInclude */ - $script_asset_file = require dirname( realpath( __FILE__ ) ?: '', 3 ) . '/assets/index.asset.php'; + $script_asset_file = require dirname( realpath( __FILE__ ) ?: __FILE__, 3 ) . '/assets/index.asset.php'; $module_url = $this->settings_url; @@ -127,12 +127,7 @@ class ScriptDataHandler { 'woocommerce-paypal-payments', ); - /** - * Require resolves. - * - * @psalm-suppress UnresolvableInclude - */ - $style_asset_file = require dirname( realpath( __FILE__ ) ?: '', 3 ) . '/assets/style.asset.php'; + $style_asset_file = require dirname( realpath( __FILE__ ) ?: __FILE__, 3 ) . '/assets/style.asset.php'; wp_register_style( 'ppcp-admin-settings', diff --git a/modules/ppcp-settings/src/SettingsModule.php b/modules/ppcp-settings/src/SettingsModule.php index ce6c9fbd0..008e25b19 100644 --- a/modules/ppcp-settings/src/SettingsModule.php +++ b/modules/ppcp-settings/src/SettingsModule.php @@ -104,12 +104,7 @@ class SettingsModule implements ServiceModule, ExecutableModule { static function () use ( $container ) { $module_url = $container->get( 'settings.url' ); - /** - * Require resolves. - * - * @psalm-suppress UnresolvableInclude - */ - $script_asset_file = require dirname( realpath( __FILE__ ) ?: '', 2 ) . '/assets/switchSettingsUi.asset.php'; + $script_asset_file = require dirname( realpath( __FILE__ ) ?: __FILE__, 2 ) . '/assets/switchSettingsUi.asset.php'; wp_register_script( 'ppcp-switch-settings-ui', From 1cf46c356d99208dfacebc23da9d9c3ac36636b4 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 4 Jul 2025 17:37:49 +0400 Subject: [PATCH 10/63] Fix the psalm --- .../ppcp-paylater-wc-blocks/src/PayLaterWCBlocksModule.php | 4 +++- modules/ppcp-settings/src/Service/ScriptDataHandler.php | 1 + modules/ppcp-settings/src/SettingsModule.php | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksModule.php b/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksModule.php index 0099f7b32..a3769cb31 100644 --- a/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksModule.php +++ b/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksModule.php @@ -192,8 +192,10 @@ class PayLaterWCBlocksModule implements ServiceModule, ExtendingModule, Executab return; } + $path = realpath( __FILE__ ) ?: __FILE__; + register_block_type( - dirname( realpath( __FILE__ ) ?: __FILE__, 2 ) . '/resources/js/CartPayLaterMessagesBlock', + dirname( $path, 2 ) . '/resources/js/CartPayLaterMessagesBlock', array( 'render_callback' => function ( array $attributes ) use ( $c ) { return PayLaterWCBlocksUtils::render_paylater_block( diff --git a/modules/ppcp-settings/src/Service/ScriptDataHandler.php b/modules/ppcp-settings/src/Service/ScriptDataHandler.php index 7f83ce4cf..743578158 100644 --- a/modules/ppcp-settings/src/Service/ScriptDataHandler.php +++ b/modules/ppcp-settings/src/Service/ScriptDataHandler.php @@ -127,6 +127,7 @@ class ScriptDataHandler { 'woocommerce-paypal-payments', ); + /** @psalm-suppress UnresolvableInclude */ $style_asset_file = require dirname( realpath( __FILE__ ) ?: __FILE__, 3 ) . '/assets/style.asset.php'; wp_register_style( diff --git a/modules/ppcp-settings/src/SettingsModule.php b/modules/ppcp-settings/src/SettingsModule.php index 008e25b19..548c12023 100644 --- a/modules/ppcp-settings/src/SettingsModule.php +++ b/modules/ppcp-settings/src/SettingsModule.php @@ -104,6 +104,7 @@ class SettingsModule implements ServiceModule, ExecutableModule { static function () use ( $container ) { $module_url = $container->get( 'settings.url' ); + /** @psalm-suppress UnresolvableInclude */ $script_asset_file = require dirname( realpath( __FILE__ ) ?: __FILE__, 2 ) . '/assets/switchSettingsUi.asset.php'; wp_register_script( From 235a9f45cfaa51a5e19d7a993a7ba0784e4060b4 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 4 Jul 2025 19:26:10 +0400 Subject: [PATCH 11/63] Add services to use `Package::PROPERTIES` of `inpsyde/modularity` --- src/services.php | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/services.php b/src/services.php index eab917b95..03a0c699d 100644 --- a/src/services.php +++ b/src/services.php @@ -10,26 +10,35 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce; use Dhii\Versions\StringVersionFactory; +use Inpsyde\Modularity\Properties\Properties; use WooCommerce\PayPalCommerce\Http\RedirectorInterface; use WooCommerce\PayPalCommerce\Http\WpRedirector; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Package; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WpOop\WordPress\Plugin\PluginInterface; return array( - 'ppcp.plugin' => function( ContainerInterface $container ) : PluginInterface { + 'ppcp.plugin' => function( ContainerInterface $container ) : PluginInterface { $factory = new FilePathPluginFactory( new StringVersionFactory() ); return $factory->createPluginFromFilePath( dirname( realpath( __FILE__ ), 2 ) . '/woocommerce-paypal-payments.php' ); }, - 'ppcp.asset-version' => function( ContainerInterface $container ) : string { + 'ppcp.asset-version' => function( ContainerInterface $container ) : string { $plugin = $container->get( 'ppcp.plugin' ); assert( $plugin instanceof PluginInterface ); return (string) $plugin->getVersion(); }, - 'http.redirector' => function( ContainerInterface $container ) : RedirectorInterface { + 'http.redirector' => function( ContainerInterface $container ) : RedirectorInterface { return new WpRedirector(); }, - 'ppcp.plugin-path' => static fn(): string => - dirname( realpath( __FILE__ ) ?: __FILE__, 2 ) . '/woocommerce-paypal-payments.php', + 'ppcp.path-to-plugin-folder' => function( ContainerInterface $container ) : string { + /** @var Properties $properties */ + $properties = $container->get( Package::PROPERTIES ); + + return $properties->basePath(); + }, + 'ppcp.path-to-plugin-main-file' => function( ContainerInterface $container ) : string { + return $container->get( 'ppcp.path-to-plugin-folder' ) . '/woocommerce-paypal-payments.php'; + }, ); From 943a0d197c8d4107f68a0703b45bbc5f6e23ddc3 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 4 Jul 2025 19:27:13 +0400 Subject: [PATCH 12/63] Use the `'ppcp.path-to-plugin-main-file'` config --- modules/ppcp-admin-notices/services.php | 2 +- modules/ppcp-applepay/services.php | 2 +- modules/ppcp-axo-block/services.php | 2 +- modules/ppcp-axo/services.php | 2 +- modules/ppcp-blocks/services.php | 2 +- modules/ppcp-button/services.php | 2 +- modules/ppcp-compat/services.php | 2 +- modules/ppcp-googlepay/services.php | 2 +- modules/ppcp-local-alternative-payment-methods/services.php | 2 +- modules/ppcp-onboarding/services.php | 2 +- modules/ppcp-order-tracking/services.php | 2 +- modules/ppcp-paylater-block/services.php | 2 +- modules/ppcp-paylater-configurator/services.php | 2 +- modules/ppcp-paylater-wc-blocks/services.php | 2 +- modules/ppcp-paypal-subscriptions/services.php | 2 +- modules/ppcp-save-payment-methods/services.php | 2 +- modules/ppcp-settings/services.php | 5 +++-- modules/ppcp-uninstall/services.php | 2 +- modules/ppcp-vaulting/services.php | 2 +- modules/ppcp-wc-gateway/services.php | 4 ++-- modules/ppcp-webhooks/services.php | 2 +- 21 files changed, 24 insertions(+), 23 deletions(-) diff --git a/modules/ppcp-admin-notices/services.php b/modules/ppcp-admin-notices/services.php index 8170916bc..b2adb7178 100644 --- a/modules/ppcp-admin-notices/services.php +++ b/modules/ppcp-admin-notices/services.php @@ -18,7 +18,7 @@ use WooCommerce\PayPalCommerce\AdminNotices\Endpoint\MuteMessageEndpoint; return array( 'admin-notices.url' => static function ( ContainerInterface $container ): string { - return plugins_url( '/modules/ppcp-admin-notices/', $container->get( 'ppcp.plugin-path' ) ); + return plugins_url( '/modules/ppcp-admin-notices/', $container->get( 'ppcp.path-to-plugin-main-file' ) ); }, 'admin-notices.renderer' => static function ( ContainerInterface $container ): RendererInterface { return new Renderer( diff --git a/modules/ppcp-applepay/services.php b/modules/ppcp-applepay/services.php index 77cab8af7..add3c14c3 100644 --- a/modules/ppcp-applepay/services.php +++ b/modules/ppcp-applepay/services.php @@ -136,7 +136,7 @@ return array( return false; }, 'applepay.url' => static function ( ContainerInterface $container ): string { - return plugins_url( '/modules/ppcp-applepay/', $container->get( 'ppcp.plugin-path' ) ); + return plugins_url( '/modules/ppcp-applepay/', $container->get( 'ppcp.path-to-plugin-main-file' ) ); }, 'applepay.sdk_script_url' => static function ( ContainerInterface $container ): string { return 'https://applepay.cdn-apple.com/jsapi/v1/apple-pay-sdk.js'; diff --git a/modules/ppcp-axo-block/services.php b/modules/ppcp-axo-block/services.php index cd45753df..91bd5d63f 100644 --- a/modules/ppcp-axo-block/services.php +++ b/modules/ppcp-axo-block/services.php @@ -18,7 +18,7 @@ return array( return true; }, 'axoblock.url' => static function ( ContainerInterface $container ) : string { - return plugins_url( '/modules/ppcp-axo-block/', $container->get( 'ppcp.plugin-path' ) ); + return plugins_url( '/modules/ppcp-axo-block/', $container->get( 'ppcp.path-to-plugin-main-file' ) ); }, 'axoblock.method' => static function ( ContainerInterface $container ) : AxoBlockPaymentMethod { return new AxoBlockPaymentMethod( diff --git a/modules/ppcp-axo/services.php b/modules/ppcp-axo/services.php index 226fe2fb2..6701a4044 100644 --- a/modules/ppcp-axo/services.php +++ b/modules/ppcp-axo/services.php @@ -59,7 +59,7 @@ return array( }, 'axo.url' => static function ( ContainerInterface $container ): string { - return plugins_url( '/modules/ppcp-axo/', $container->get( 'ppcp.plugin-path' ) ); + return plugins_url( '/modules/ppcp-axo/', $container->get( 'ppcp.path-to-plugin-main-file' ) ); }, 'axo.manager' => static function ( ContainerInterface $container ): AxoManager { diff --git a/modules/ppcp-blocks/services.php b/modules/ppcp-blocks/services.php index 5f7dfe5bf..df2d68bde 100644 --- a/modules/ppcp-blocks/services.php +++ b/modules/ppcp-blocks/services.php @@ -17,7 +17,7 @@ use WC_Cart; return array( 'blocks.url' => static function ( ContainerInterface $container ): string { - return plugins_url( '/modules/ppcp-blocks/', $container->get( 'ppcp.plugin-path' ) ); + return plugins_url( '/modules/ppcp-blocks/', $container->get( 'ppcp.path-to-plugin-main-file' ) ); }, 'blocks.method' => static function ( ContainerInterface $container ): PayPalPaymentMethod { return new PayPalPaymentMethod( diff --git a/modules/ppcp-button/services.php b/modules/ppcp-button/services.php index 7eb31c987..6ff098b1b 100644 --- a/modules/ppcp-button/services.php +++ b/modules/ppcp-button/services.php @@ -174,7 +174,7 @@ return array( ); }, 'button.url' => static function ( ContainerInterface $container ): string { - return plugins_url( '/modules/ppcp-button/', $container->get( 'ppcp.plugin-path' ) ); + return plugins_url( '/modules/ppcp-button/', $container->get( 'ppcp.path-to-plugin-main-file' ) ); }, 'button.pay-now-contexts' => static function ( ContainerInterface $container ): array { $defaults = array( 'checkout', 'pay-now' ); diff --git a/modules/ppcp-compat/services.php b/modules/ppcp-compat/services.php index 8e6021436..fa59051ff 100644 --- a/modules/ppcp-compat/services.php +++ b/modules/ppcp-compat/services.php @@ -101,7 +101,7 @@ return array( }, 'compat.module.url' => static function ( ContainerInterface $container ): string { - return plugins_url( '/modules/ppcp-compat/', $container->get( 'ppcp.plugin-path' ) ); + return plugins_url( '/modules/ppcp-compat/', $container->get( 'ppcp.path-to-plugin-main-file' ) ); }, 'compat.assets' => function( ContainerInterface $container ) : CompatAssets { diff --git a/modules/ppcp-googlepay/services.php b/modules/ppcp-googlepay/services.php index 8b9489b0b..f92c13f27 100644 --- a/modules/ppcp-googlepay/services.php +++ b/modules/ppcp-googlepay/services.php @@ -205,7 +205,7 @@ return array( }, 'googlepay.url' => static function ( ContainerInterface $container ): string { - return plugins_url( '/modules/ppcp-googlepay/', $container->get( 'ppcp.plugin-path' ) ); + return plugins_url( '/modules/ppcp-googlepay/', $container->get( 'ppcp.path-to-plugin-main-file' ) ); }, 'googlepay.sdk_url' => static function ( ContainerInterface $container ): string { diff --git a/modules/ppcp-local-alternative-payment-methods/services.php b/modules/ppcp-local-alternative-payment-methods/services.php index 56ff0e10c..e4e584a7c 100644 --- a/modules/ppcp-local-alternative-payment-methods/services.php +++ b/modules/ppcp-local-alternative-payment-methods/services.php @@ -14,7 +14,7 @@ use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\LocalApmProductSta return array( 'ppcp-local-apms.url' => static function ( ContainerInterface $container ): string { - return plugins_url( '/modules/ppcp-local-alternative-payment-methods/', $container->get( 'ppcp.plugin-path' ) ); + return plugins_url( '/modules/ppcp-local-alternative-payment-methods/', $container->get( 'ppcp.path-to-plugin-main-file' ) ); }, 'ppcp-local-apms.payment-methods' => static function( ContainerInterface $container ): array { return array( diff --git a/modules/ppcp-onboarding/services.php b/modules/ppcp-onboarding/services.php index c1d4be1f8..256e80520 100644 --- a/modules/ppcp-onboarding/services.php +++ b/modules/ppcp-onboarding/services.php @@ -123,7 +123,7 @@ return array( }, 'onboarding.url' => static function ( ContainerInterface $container ): string { - return plugins_url( '/modules/ppcp-onboarding/', $container->get( 'ppcp.plugin-path' ) ); + return plugins_url( '/modules/ppcp-onboarding/', $container->get( 'ppcp.path-to-plugin-main-file' ) ); }, 'onboarding.endpoint.login-seller' => static function ( ContainerInterface $container ) : LoginSellerEndpoint { diff --git a/modules/ppcp-order-tracking/services.php b/modules/ppcp-order-tracking/services.php index 004389f17..53cedbea5 100644 --- a/modules/ppcp-order-tracking/services.php +++ b/modules/ppcp-order-tracking/services.php @@ -43,7 +43,7 @@ return array( ); }, 'order-tracking.module.url' => static function ( ContainerInterface $container ): string { - return plugins_url( '/modules/ppcp-order-tracking/', $container->get( 'ppcp.plugin-path' ) ); + return plugins_url( '/modules/ppcp-order-tracking/', $container->get( 'ppcp.path-to-plugin-main-file' ) ); }, 'order-tracking.meta-box.renderer' => static function ( ContainerInterface $container ): MetaBoxRenderer { return new MetaBoxRenderer( diff --git a/modules/ppcp-paylater-block/services.php b/modules/ppcp-paylater-block/services.php index a9a1a8b38..77af1fa3e 100644 --- a/modules/ppcp-paylater-block/services.php +++ b/modules/ppcp-paylater-block/services.php @@ -14,7 +14,7 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; return array( 'paylater-block.url' => static function ( ContainerInterface $container ): string { - return plugins_url( '/modules/ppcp-paylater-block/', $container->get( 'ppcp.plugin-path' ) ); + return plugins_url( '/modules/ppcp-paylater-block/', $container->get( 'ppcp.path-to-plugin-main-file' ) ); }, 'paylater-block.renderer' => static function (): PayLaterBlockRenderer { return new PayLaterBlockRenderer(); diff --git a/modules/ppcp-paylater-configurator/services.php b/modules/ppcp-paylater-configurator/services.php index d6ace8e2d..a955ff92a 100644 --- a/modules/ppcp-paylater-configurator/services.php +++ b/modules/ppcp-paylater-configurator/services.php @@ -19,7 +19,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; return array( 'paylater-configurator.url' => static function ( ContainerInterface $container ): string { - return plugins_url( '/modules/ppcp-paylater-configurator', $container->get( 'ppcp.plugin-path' ) ); + return plugins_url( '/modules/ppcp-paylater-configurator', $container->get( 'ppcp.path-to-plugin-main-file' ) ); }, 'paylater-configurator.factory.config' => static function ( ContainerInterface $container ): ConfigFactory { return new ConfigFactory(); diff --git a/modules/ppcp-paylater-wc-blocks/services.php b/modules/ppcp-paylater-wc-blocks/services.php index 6acf200de..3fef772c3 100644 --- a/modules/ppcp-paylater-wc-blocks/services.php +++ b/modules/ppcp-paylater-wc-blocks/services.php @@ -13,7 +13,7 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; return array( 'paylater-wc-blocks.url' => static function ( ContainerInterface $container ): string { - return plugins_url( '/modules/ppcp-paylater-wc-blocks/', $container->get( 'ppcp.plugin-path' ) ); + return plugins_url( '/modules/ppcp-paylater-wc-blocks/', $container->get( 'ppcp.path-to-plugin-main-file' ) ); }, 'paylater-wc-blocks.cart-renderer' => static function ( ContainerInterface $container ): PayLaterWCBlocksRenderer { diff --git a/modules/ppcp-paypal-subscriptions/services.php b/modules/ppcp-paypal-subscriptions/services.php index 7ba7c63c9..008f5c5dc 100644 --- a/modules/ppcp-paypal-subscriptions/services.php +++ b/modules/ppcp-paypal-subscriptions/services.php @@ -30,7 +30,7 @@ return array( ); }, 'paypal-subscriptions.module.url' => static function ( ContainerInterface $container ): string { - return plugins_url( '/modules/ppcp-paypal-subscriptions/', $container->get( 'ppcp.plugin-path' ) ); + return plugins_url( '/modules/ppcp-paypal-subscriptions/', $container->get( 'ppcp.path-to-plugin-main-file' ) ); }, 'paypal-subscriptions.renewal-handler' => static function ( ContainerInterface $container ): RenewalHandler { return new RenewalHandler( $container->get( 'woocommerce.logger.woocommerce' ) ); diff --git a/modules/ppcp-save-payment-methods/services.php b/modules/ppcp-save-payment-methods/services.php index da58d26f6..0d5347c11 100644 --- a/modules/ppcp-save-payment-methods/services.php +++ b/modules/ppcp-save-payment-methods/services.php @@ -87,7 +87,7 @@ return array( ); }, 'save-payment-methods.module.url' => static function ( ContainerInterface $container ): string { - return plugins_url( '/modules/ppcp-save-payment-methods/', $container->get( 'ppcp.plugin-path' ) ); + return plugins_url( '/modules/ppcp-save-payment-methods/', $container->get( 'ppcp.path-to-plugin-main-file' ) ); }, 'save-payment-methods.endpoint.create-setup-token' => static function ( ContainerInterface $container ): CreateSetupToken { return new CreateSetupToken( diff --git a/modules/ppcp-settings/services.php b/modules/ppcp-settings/services.php index 9988124db..aab7fd68e 100644 --- a/modules/ppcp-settings/services.php +++ b/modules/ppcp-settings/services.php @@ -75,7 +75,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\MerchantDetails; return array( 'settings.url' => static function ( ContainerInterface $container ) : string { - return plugins_url( '/modules/ppcp-settings/', $container->get( 'ppcp.plugin-path' ) ); + return plugins_url( '/modules/ppcp-settings/', $container->get( 'ppcp.path-to-plugin-main-file' ) ); }, 'settings.data.onboarding' => static function ( ContainerInterface $container ) : OnboardingProfile { $can_use_casual_selling = $container->get( 'settings.casual-selling.eligible' ); @@ -370,7 +370,8 @@ return array( $merchant_id = $container->get( 'api.partner_merchant_id' ); $button_language_choices = $container->get( 'wcgateway.wp-paypal-locales-map' ); $partner_attribution = $container->get( 'api.helper.partner-attribution' ); - return new ScriptDataHandler( $settings, $settings_url, $paylater_is_available, $store_country, $merchant_id, $button_language_choices, $partner_attribution ); + $path_to_module_assets_folder = $container->get( 'ppcp.path-to-plugin-folder' ) . 'modules/ppcp-settings/assets'; + return new ScriptDataHandler( $settings, $settings_url, $paylater_is_available, $store_country, $merchant_id, $button_language_choices, $partner_attribution, $path_to_module_assets_folder ); }, 'settings.ajax.switch_ui' => static function ( ContainerInterface $container ) : SwitchSettingsUiEndpoint { return new SwitchSettingsUiEndpoint( diff --git a/modules/ppcp-uninstall/services.php b/modules/ppcp-uninstall/services.php index 7c0df1698..10c6fe88e 100644 --- a/modules/ppcp-uninstall/services.php +++ b/modules/ppcp-uninstall/services.php @@ -79,7 +79,7 @@ return array( }, 'uninstall.module-url' => static function ( ContainerInterface $container ): string { - return plugins_url( '/modules/ppcp-uninstall/', $container->get( 'ppcp.plugin-path' ) ); + return plugins_url( '/modules/ppcp-uninstall/', $container->get( 'ppcp.path-to-plugin-main-file' ) ); }, 'uninstall.clear-db-assets' => function( ContainerInterface $container ) : ClearDatabaseAssets { diff --git a/modules/ppcp-vaulting/services.php b/modules/ppcp-vaulting/services.php index 58710e694..a1797b324 100644 --- a/modules/ppcp-vaulting/services.php +++ b/modules/ppcp-vaulting/services.php @@ -13,7 +13,7 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; return array( 'vaulting.module-url' => static function ( ContainerInterface $container ): string { - return plugins_url( '/modules/ppcp-vaulting/', $container->get( 'ppcp.plugin-path' ) ); + return plugins_url( '/modules/ppcp-vaulting/', $container->get( 'ppcp.path-to-plugin-main-file' ) ); }, 'vaulting.repository.payment-token' => static function ( ContainerInterface $container ): PaymentTokenRepository { $factory = $container->get( 'api.factory.payment-token' ); diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 1005a77c9..e22c4f38d 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -1351,13 +1351,13 @@ return array( ); }, 'wcgateway.url' => static function ( ContainerInterface $container ): string { - return plugins_url( $container->get( 'wcgateway.relative-path' ), $container->get( 'ppcp.plugin-path' ) ); + return plugins_url( $container->get( 'wcgateway.relative-path' ), $container->get( 'ppcp.path-to-plugin-main-file' ) ); }, 'wcgateway.relative-path' => static function( ContainerInterface $container ): string { return 'modules/ppcp-wc-gateway/'; }, 'wcgateway.absolute-path' => static function( ContainerInterface $container ): string { - return plugin_dir_path( $container->get( 'ppcp.plugin-path' ) ) . $container->get( 'wcgateway.relative-path' ); + return plugin_dir_path( $container->get( 'ppcp.path-to-plugin-main-file' ) ) . $container->get( 'wcgateway.relative-path' ); }, 'wcgateway.endpoint.return-url' => static function ( ContainerInterface $container ) : ReturnUrlEndpoint { $gateway = $container->get( 'wcgateway.paypal-gateway' ); diff --git a/modules/ppcp-webhooks/services.php b/modules/ppcp-webhooks/services.php index 96a072267..dcfe8df7a 100644 --- a/modules/ppcp-webhooks/services.php +++ b/modules/ppcp-webhooks/services.php @@ -220,6 +220,6 @@ return array( }, 'webhook.module-url' => static function ( ContainerInterface $container ): string { - return plugins_url( '/modules/ppcp-webhooks/', $container->get( 'ppcp.plugin-path' ) ); + return plugins_url( '/modules/ppcp-webhooks/', $container->get( 'ppcp.path-to-plugin-main-file' ) ); }, ); From 86cae6ca4af16ed9d5b4ee6d6b685773a345f137 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 4 Jul 2025 19:27:26 +0400 Subject: [PATCH 13/63] Use the `'ppcp.path-to-plugin-folder'` config --- .../src/PayLaterBlockModule.php | 2 +- .../src/PayLaterWCBlocksModule.php | 6 ++--- .../src/Service/ScriptDataHandler.php | 25 +++++++++++-------- modules/ppcp-settings/src/SettingsModule.php | 2 +- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/modules/ppcp-paylater-block/src/PayLaterBlockModule.php b/modules/ppcp-paylater-block/src/PayLaterBlockModule.php index bc6d45123..0e528ae80 100644 --- a/modules/ppcp-paylater-block/src/PayLaterBlockModule.php +++ b/modules/ppcp-paylater-block/src/PayLaterBlockModule.php @@ -102,7 +102,7 @@ class PayLaterBlockModule implements ServiceModule, ExtendingModule, ExecutableM ); register_block_type( - dirname( realpath( __FILE__ ) ?: __FILE__, 2 ), + $c->get( 'ppcp.path-to-plugin-folder' ) . 'modules/ppcp-paylater-block/', array( 'render_callback' => function ( array $attributes ) use ( $c ) { $renderer = $c->get( 'paylater-block.renderer' ); diff --git a/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksModule.php b/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksModule.php index a3769cb31..c44028c95 100644 --- a/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksModule.php +++ b/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksModule.php @@ -192,10 +192,10 @@ class PayLaterWCBlocksModule implements ServiceModule, ExtendingModule, Executab return; } - $path = realpath( __FILE__ ) ?: __FILE__; + $path_to_module_js_folder = $c->get( 'ppcp.path-to-plugin-folder' ) . 'modules/ppcp-paylater-wc-blocks/resources/js/'; register_block_type( - dirname( $path, 2 ) . '/resources/js/CartPayLaterMessagesBlock', + $path_to_module_js_folder . 'CartPayLaterMessagesBlock', array( 'render_callback' => function ( array $attributes ) use ( $c ) { return PayLaterWCBlocksUtils::render_paylater_block( @@ -209,7 +209,7 @@ class PayLaterWCBlocksModule implements ServiceModule, ExtendingModule, Executab ); register_block_type( - dirname( $path, 2 ) . '/resources/js/CheckoutPayLaterMessagesBlock', + $path_to_module_js_folder . 'CheckoutPayLaterMessagesBlock', array( 'render_callback' => function ( array $attributes ) use ( $c ) { return PayLaterWCBlocksUtils::render_paylater_block( diff --git a/modules/ppcp-settings/src/Service/ScriptDataHandler.php b/modules/ppcp-settings/src/Service/ScriptDataHandler.php index 743578158..41c799799 100644 --- a/modules/ppcp-settings/src/Service/ScriptDataHandler.php +++ b/modules/ppcp-settings/src/Service/ScriptDataHandler.php @@ -59,6 +59,8 @@ class ScriptDataHandler { */ protected PartnerAttribution $partner_attribution; + protected string $path_to_module_assets_folder; + /** * ScriptDataHandler constructor. * @@ -69,6 +71,7 @@ class ScriptDataHandler { * @param string $merchant_id The merchant ID. * @param array $button_language_choices The button language choices. * @param PartnerAttribution $partner_attribution The partner attribution object. + * @param string $path_to_module_assets_folder The path to mpdule assets folder. */ public function __construct( Settings $settings, @@ -77,15 +80,17 @@ class ScriptDataHandler { string $store_country, string $merchant_id, array $button_language_choices, - PartnerAttribution $partner_attribution + PartnerAttribution $partner_attribution, + string $path_to_module_assets_folder ) { - $this->settings = $settings; - $this->settings_url = $settings_url; - $this->paylater_is_available = $paylater_is_available; - $this->store_country = $store_country; - $this->merchant_id = $merchant_id; - $this->button_language_choices = $button_language_choices; - $this->partner_attribution = $partner_attribution; + $this->settings = $settings; + $this->settings_url = $settings_url; + $this->paylater_is_available = $paylater_is_available; + $this->store_country = $store_country; + $this->merchant_id = $merchant_id; + $this->button_language_choices = $button_language_choices; + $this->partner_attribution = $partner_attribution; + $this->path_to_module_assets_folder = $path_to_module_assets_folder; } /** @@ -109,7 +114,7 @@ class ScriptDataHandler { * * @psalm-suppress UnresolvableInclude */ - $script_asset_file = require dirname( realpath( __FILE__ ) ?: __FILE__, 3 ) . '/assets/index.asset.php'; + $script_asset_file = require $this->path_to_module_assets_folder . '/index.asset.php'; $module_url = $this->settings_url; @@ -128,7 +133,7 @@ class ScriptDataHandler { ); /** @psalm-suppress UnresolvableInclude */ - $style_asset_file = require dirname( realpath( __FILE__ ) ?: __FILE__, 3 ) . '/assets/style.asset.php'; + $style_asset_file = require $this->path_to_module_assets_folder . '/style.asset.php'; wp_register_style( 'ppcp-admin-settings', diff --git a/modules/ppcp-settings/src/SettingsModule.php b/modules/ppcp-settings/src/SettingsModule.php index 548c12023..4ec089193 100644 --- a/modules/ppcp-settings/src/SettingsModule.php +++ b/modules/ppcp-settings/src/SettingsModule.php @@ -105,7 +105,7 @@ class SettingsModule implements ServiceModule, ExecutableModule { $module_url = $container->get( 'settings.url' ); /** @psalm-suppress UnresolvableInclude */ - $script_asset_file = require dirname( realpath( __FILE__ ) ?: __FILE__, 2 ) . '/assets/switchSettingsUi.asset.php'; + $script_asset_file = require $container->get( 'ppcp.path-to-plugin-folder' ) . 'modules/ppcp-settings/assets/switchSettingsUi.asset.php'; wp_register_script( 'ppcp-switch-settings-ui', From 238ebc094920271217798560ffaaf126b4e75294 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 4 Jul 2025 20:18:23 +0400 Subject: [PATCH 14/63] Fix the plugin main file path --- bootstrap.php | 2 +- src/services.php | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/bootstrap.php b/bootstrap.php index 673f34473..588f0b01d 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -32,7 +32,7 @@ return function ( $modules = apply_filters( 'woocommerce_paypal_payments_modules', $modules ); // Initialize plugin. - $properties = PluginProperties::new( __FILE__ ); + $properties = PluginProperties::new( "$root_dir/woocommerce-paypal-payments.php" ); $bootstrap = Package::new( $properties ); foreach ( $modules as $module ) { diff --git a/src/services.php b/src/services.php index 03a0c699d..562d448be 100644 --- a/src/services.php +++ b/src/services.php @@ -39,6 +39,9 @@ return array( return $properties->basePath(); }, 'ppcp.path-to-plugin-main-file' => function( ContainerInterface $container ) : string { - return $container->get( 'ppcp.path-to-plugin-folder' ) . '/woocommerce-paypal-payments.php'; + /** @var Properties $properties */ + $properties = $container->get( Package::PROPERTIES ); + + return $properties->pluginMainFile(); }, ); From b483115883c8f8a92ff0cc2b787b29acbb2e7a55 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 4 Jul 2025 22:05:58 +0400 Subject: [PATCH 15/63] Update lib packages --- composer.json | 3 +- composer.lock | 310 +++++++++- .../Dhii/Container/AliasingContainer.php | 2 + .../Dhii/Container/CachingContainer.php | 2 +- .../CompositeCachingServiceProvider.php | 8 +- .../Dhii/Container/DelegatingContainer.php | 1 + .../Dhii/Container/DeprefixingContainer.php | 5 +- lib/packages/Dhii/Container/Dictionary.php | 4 +- .../Dhii/Container/FlashContainer.php | 2 + .../Dhii/Container/HierarchyContainer.php | 2 + .../Dhii/Container/MappingContainer.php | 2 + .../Dhii/Container/MaskingContainer.php | 2 + lib/packages/Dhii/Container/NoOpContainer.php | 3 +- lib/packages/Dhii/Container/PathContainer.php | 2 + .../Dhii/Container/PrefixingContainer.php | 45 +- .../Dhii/Container/SegmentingContainer.php | 2 + .../Dhii/Container/ServiceProvider.php | 12 +- .../Dhii/Container/SimpleCacheContainer.php | 9 +- .../Dhii/Container/TaggingServiceProvider.php | 121 ++++ .../Container/Util/StringTranslatingTrait.php | 1 + .../Container/ContainerConfigurator.php | 70 +-- .../Container/PackageProxyContainer.php | 42 +- .../Container/ReadOnlyContainer.php | 102 ++-- .../Container/ServiceExtensions.php | 194 +++++++ .../Modularity/Module/ExecutableModule.php | 1 - .../Modularity/Module/ExtendingModule.php | 8 +- .../Modularity/Module/FactoryModule.php | 5 +- .../Inpsyde/Modularity/Module/Module.php | 2 - .../Module/ModuleClassNameIdTrait.php | 7 +- .../Modularity/Module/ServiceModule.php | 8 +- lib/packages/Inpsyde/Modularity/Package.php | 534 ++++++++++-------- .../Modularity/Properties/BaseProperties.php | 61 +- .../Properties/LibraryProperties.php | 217 ++++--- .../Properties/PluginProperties.php | 74 +-- .../Modularity/Properties/Properties.php | 17 +- .../Modularity/Properties/ThemeProperties.php | 35 +- .../Container/ContainerExceptionInterface.php | 4 +- src/services.php | 2 +- 38 files changed, 1323 insertions(+), 598 deletions(-) create mode 100644 lib/packages/Dhii/Container/TaggingServiceProvider.php create mode 100644 lib/packages/Inpsyde/Modularity/Container/ServiceExtensions.php diff --git a/composer.json b/composer.json index 8443e53f1..e413904cf 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,8 @@ "php-stubs/wordpress-stubs": "^5.0@stable", "php-stubs/woocommerce-stubs": "^8.0@stable", "vimeo/psalm": "^4.0", - "vlucas/phpdotenv": "^5" + "vlucas/phpdotenv": "^5", + "coenjacobs/mozart": "^0.7.1" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 180c71364..908b9e94c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2fa610ed883c0868838d3008b7127cbf", + "content-hash": "6ca9c2c7864d2649617db6d3850382c5", "packages": [ { "name": "container-interop/service-provider", @@ -904,6 +904,64 @@ }, "time": "2021-11-11T15:53:55+00:00" }, + { + "name": "coenjacobs/mozart", + "version": "0.7.1", + "source": { + "type": "git", + "url": "https://github.com/coenjacobs/mozart.git", + "reference": "dbcdeb992d20d9c8914eef090f9a0d684bb1102c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/coenjacobs/mozart/zipball/dbcdeb992d20d9c8914eef090f9a0d684bb1102c", + "reference": "dbcdeb992d20d9c8914eef090f9a0d684bb1102c", + "shasum": "" + }, + "require": { + "league/flysystem": "^1.0", + "php": "^7.3|^8.0", + "symfony/console": "^4|^5", + "symfony/finder": "^4|^5" + }, + "require-dev": { + "mheap/phpunit-github-actions-printer": "^1.4", + "phpunit/phpunit": "^8.5", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.4" + }, + "bin": [ + "bin/mozart" + ], + "type": "library", + "autoload": { + "psr-4": { + "CoenJacobs\\Mozart\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Coen Jacobs", + "email": "coenjacobs@gmail.com" + } + ], + "description": "Composes all dependencies as a package inside a WordPress plugin", + "support": { + "issues": "https://github.com/coenjacobs/mozart/issues", + "source": "https://github.com/coenjacobs/mozart/tree/0.7.1" + }, + "funding": [ + { + "url": "https://github.com/coenjacobs", + "type": "github" + } + ], + "time": "2021-02-02T21:37:03+00:00" + }, { "name": "composer/package-versions-deprecated", "version": "1.11.99.5", @@ -1828,31 +1886,34 @@ }, { "name": "inpsyde/modularity", - "version": "1.10.0", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/inpsyde/modularity.git", - "reference": "2119d0e32706741a3c6dc0a85d908ec19ebf142e" + "reference": "e1ca1c81b7b663355906b586525d21ac5d46bc65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/inpsyde/modularity/zipball/2119d0e32706741a3c6dc0a85d908ec19ebf142e", - "reference": "2119d0e32706741a3c6dc0a85d908ec19ebf142e", + "url": "https://api.github.com/repos/inpsyde/modularity/zipball/e1ca1c81b7b663355906b586525d21ac5d46bc65", + "reference": "e1ca1c81b7b663355906b586525d21ac5d46bc65", "shasum": "" }, "require": { "ext-json": "*", - "php": ">=7.4 <8.4", + "php": ">=7.4", "psr/container": "^1.1.0 || ^2" }, "require-dev": { "brain/monkey": "^2.6.1", - "inpsyde/php-coding-standards": "^2@dev", - "inpsyde/wp-stubs-versions": "dev-latest", + "inpsyde/wp-stubs-versions": "6.7", "mikey179/vfsstream": "^v1.6.11", + "phpstan/phpstan": "^2.1.1", + "phpstan/phpstan-deprecation-rules": "^2.0.1", + "phpstan/phpstan-mockery": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0.4", "phpunit/phpunit": "^9.6.19", - "roots/wordpress-no-content": "@dev", - "vimeo/psalm": "^5.24.0" + "swissspidy/phpstan-no-private": "^v1.0.0", + "syde/phpcs": "^1.0.0" }, "type": "library", "extra": { @@ -1871,18 +1932,168 @@ ], "authors": [ { - "name": "Inpsyde GmbH", - "email": "hello@inpsyde.com", - "homepage": "https://inpsyde.com/", + "name": "Syde GmbH", + "email": "hello@syde.com", + "homepage": "https://syde.com/", "role": "Company" } ], "description": "Modular PSR-11 implementation for WordPress plugins, themes or libraries.", "support": { "issues": "https://github.com/inpsyde/modularity/issues", - "source": "https://github.com/inpsyde/modularity/tree/1.10.0" + "source": "https://github.com/inpsyde/modularity/tree/1.12.0" }, - "time": "2024-09-03T10:42:50+00:00" + "time": "2025-05-09T12:13:17+00:00" + }, + { + "name": "league/flysystem", + "version": "1.1.10", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "3239285c825c152bcc315fe0e87d6b55f5972ed1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/3239285c825c152bcc315fe0e87d6b55f5972ed1", + "reference": "3239285c825c152bcc315fe0e87d6b55f5972ed1", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "league/mime-type-detection": "^1.3", + "php": "^7.2.5 || ^8.0" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "phpspec/prophecy": "^1.11.1", + "phpunit/phpunit": "^8.5.8" + }, + "suggest": { + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "Cloud Files", + "WebDAV", + "abstraction", + "aws", + "cloud", + "copy.com", + "dropbox", + "file systems", + "files", + "filesystem", + "filesystems", + "ftp", + "rackspace", + "remote", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/1.1.10" + }, + "funding": [ + { + "url": "https://offset.earth/frankdejonge", + "type": "other" + } + ], + "time": "2022-10-04T09:16:37+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.16.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2024-09-21T08:32:55+00:00" }, { "name": "mockery/mockery", @@ -4528,6 +4739,69 @@ ], "time": "2023-01-24T14:02:46+00:00" }, + { + "name": "symfony/finder", + "version": "v5.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "63741784cd7b9967975eec610b256eed3ede022b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/63741784cd7b9967975eec610b256eed3ede022b", + "reference": "63741784cd7b9967975eec610b256eed3ede022b", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v5.4.45" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-28T13:32:08+00:00" + }, { "name": "symfony/polyfill-ctype", "version": "v1.30.0", @@ -5541,8 +5815,8 @@ "aliases": [], "minimum-stability": "dev", "stability-flags": { - "php-stubs/woocommerce-stubs": 0, - "php-stubs/wordpress-stubs": 0 + "php-stubs/wordpress-stubs": 0, + "php-stubs/woocommerce-stubs": 0 }, "prefer-stable": true, "prefer-lowest": false, @@ -5550,7 +5824,7 @@ "php": "^7.4 | ^8.0", "ext-json": "*" }, - "platform-dev": {}, + "platform-dev": [], "platform-overrides": { "php": "7.4" }, diff --git a/lib/packages/Dhii/Container/AliasingContainer.php b/lib/packages/Dhii/Container/AliasingContainer.php index 606113b83..a1d2e046d 100644 --- a/lib/packages/Dhii/Container/AliasingContainer.php +++ b/lib/packages/Dhii/Container/AliasingContainer.php @@ -64,6 +64,8 @@ class AliasingContainer implements ContainerInterface */ public function has($key) { + $key = (string) $key; + return $this->inner->has($this->getInnerKey($key)); } diff --git a/lib/packages/Dhii/Container/CachingContainer.php b/lib/packages/Dhii/Container/CachingContainer.php index f62abacb9..cafc2f8e6 100644 --- a/lib/packages/Dhii/Container/CachingContainer.php +++ b/lib/packages/Dhii/Container/CachingContainer.php @@ -64,7 +64,7 @@ class CachingContainer implements ContainerInterface throw new NotFoundException($this->__('Key "%1$s" not found in inner container', [$key]), 0, $e); } catch (Exception $e) { throw new ContainerException( - $this->__('Could not retrieve value for key "%1$s from inner container', [$key]), + $this->__('Could not retrieve value for key "%1$s" from inner container', [$key]), 0, $e ); diff --git a/lib/packages/Dhii/Container/CompositeCachingServiceProvider.php b/lib/packages/Dhii/Container/CompositeCachingServiceProvider.php index 13bbb38f3..65187182c 100644 --- a/lib/packages/Dhii/Container/CompositeCachingServiceProvider.php +++ b/lib/packages/Dhii/Container/CompositeCachingServiceProvider.php @@ -38,7 +38,8 @@ class CompositeCachingServiceProvider implements ServiceProviderInterface } /** - * {@inheritDoc} + * @inheritDoc + * * @psalm-suppress InvalidNullableReturnType * It isn't actually going to return null ever, because $factories will be filled during indexing. */ @@ -56,7 +57,8 @@ class CompositeCachingServiceProvider implements ServiceProviderInterface } /** - * {@inheritDoc} + * @inheritDoc + * * @psalm-suppress InvalidNullableReturnType * It isn't actually going to return null ever, because $factories will be filled during indexing. */ @@ -78,7 +80,7 @@ class CompositeCachingServiceProvider implements ServiceProviderInterface * * Caches them internally. * - * @param iterable|ServiceProviderInterface[] $providers The providers to index. + * @param iterable $providers The providers to index. */ protected function indexProviderDefinitions(iterable $providers): void { diff --git a/lib/packages/Dhii/Container/DelegatingContainer.php b/lib/packages/Dhii/Container/DelegatingContainer.php index e322579d9..d2ffd5768 100644 --- a/lib/packages/Dhii/Container/DelegatingContainer.php +++ b/lib/packages/Dhii/Container/DelegatingContainer.php @@ -89,6 +89,7 @@ class DelegatingContainer implements ContainerInterface public function has($id) { $services = $this->provider->getFactories(); + $id = (string) $id; return array_key_exists($id, $services); } diff --git a/lib/packages/Dhii/Container/DeprefixingContainer.php b/lib/packages/Dhii/Container/DeprefixingContainer.php index 8c6f14389..65fe531f6 100644 --- a/lib/packages/Dhii/Container/DeprefixingContainer.php +++ b/lib/packages/Dhii/Container/DeprefixingContainer.php @@ -84,7 +84,10 @@ class DeprefixingContainer implements ContainerInterface */ public function has($key) { - return $this->inner->has($this->getInnerKey($key)) || (!$this->strict && $this->inner->has($key)); + $key = (string) $key; + $realKey = $this->getInnerKey($key); + + return $this->inner->has($realKey) || (!$this->strict && $this->inner->has($key)); } /** diff --git a/lib/packages/Dhii/Container/Dictionary.php b/lib/packages/Dhii/Container/Dictionary.php index 2f61df85c..84433963b 100644 --- a/lib/packages/Dhii/Container/Dictionary.php +++ b/lib/packages/Dhii/Container/Dictionary.php @@ -5,13 +5,13 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Vendor\Dhii\Container; use ArrayIterator; -use Traversable; use WooCommerce\PayPalCommerce\Vendor\Dhii\Collection\WritableContainerInterface; use WooCommerce\PayPalCommerce\Vendor\Dhii\Collection\WritableMapInterface; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\Exception\NotFoundException; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\Util\StringTranslatingTrait; use IteratorAggregate; use RangeException; +use Traversable; /** * A simple mutable dictionary, i.e. an enumerable key-value map. @@ -54,6 +54,8 @@ class Dictionary implements */ public function has($key) { + $key = (string) $key; + $isHas = array_key_exists($key, $this->data); return $isHas; diff --git a/lib/packages/Dhii/Container/FlashContainer.php b/lib/packages/Dhii/Container/FlashContainer.php index 4a7a3e836..c2506aadc 100644 --- a/lib/packages/Dhii/Container/FlashContainer.php +++ b/lib/packages/Dhii/Container/FlashContainer.php @@ -61,6 +61,8 @@ class FlashContainer implements */ public function has($key) { + $key = (string) $key; + return array_key_exists($key, $this->flashData); } diff --git a/lib/packages/Dhii/Container/HierarchyContainer.php b/lib/packages/Dhii/Container/HierarchyContainer.php index 7c85b8f15..41550492e 100644 --- a/lib/packages/Dhii/Container/HierarchyContainer.php +++ b/lib/packages/Dhii/Container/HierarchyContainer.php @@ -88,6 +88,8 @@ class HierarchyContainer implements ContainerInterface */ public function has($key) { + $key = (string) $key; + return array_key_exists($key, $this->data); } } diff --git a/lib/packages/Dhii/Container/MappingContainer.php b/lib/packages/Dhii/Container/MappingContainer.php index 10b21d52c..ee482cc38 100644 --- a/lib/packages/Dhii/Container/MappingContainer.php +++ b/lib/packages/Dhii/Container/MappingContainer.php @@ -85,6 +85,8 @@ class MappingContainer implements ContainerInterface */ public function has($key) { + $key = (string) $key; + return $this->inner->has($key); } } diff --git a/lib/packages/Dhii/Container/MaskingContainer.php b/lib/packages/Dhii/Container/MaskingContainer.php index f2a721033..570a89825 100644 --- a/lib/packages/Dhii/Container/MaskingContainer.php +++ b/lib/packages/Dhii/Container/MaskingContainer.php @@ -80,6 +80,8 @@ class MaskingContainer implements ContainerInterface */ public function has($key) { + $key = (string) $key; + return $this->isExposed($key) && $this->inner->has($key); } diff --git a/lib/packages/Dhii/Container/NoOpContainer.php b/lib/packages/Dhii/Container/NoOpContainer.php index 143c4eb7f..68f151d48 100644 --- a/lib/packages/Dhii/Container/NoOpContainer.php +++ b/lib/packages/Dhii/Container/NoOpContainer.php @@ -12,6 +12,7 @@ use WooCommerce\PayPalCommerce\Vendor\Dhii\Collection\WritableMapInterface; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\Exception\ContainerException; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\Exception\NotFoundException; use IteratorAggregate; +use Traversable; /** * A container that does nothing. @@ -92,7 +93,7 @@ class NoOpContainer implements /** * @inheritDoc */ - public function getIterator() + public function getIterator(): Traversable { return new ArrayIterator([]); } diff --git a/lib/packages/Dhii/Container/PathContainer.php b/lib/packages/Dhii/Container/PathContainer.php index 5b0187278..c418e5ea3 100644 --- a/lib/packages/Dhii/Container/PathContainer.php +++ b/lib/packages/Dhii/Container/PathContainer.php @@ -127,6 +127,8 @@ class PathContainer implements ContainerInterface */ public function has($key) { + $key = (string) $key; + /** * @psalm-suppress InvalidCatch * The base interface does not extend Throwable, but in fact everything that is possible diff --git a/lib/packages/Dhii/Container/PrefixingContainer.php b/lib/packages/Dhii/Container/PrefixingContainer.php index f41afc226..895a2c14d 100644 --- a/lib/packages/Dhii/Container/PrefixingContainer.php +++ b/lib/packages/Dhii/Container/PrefixingContainer.php @@ -5,9 +5,12 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Vendor\Dhii\Container; use WooCommerce\PayPalCommerce\Vendor\Dhii\Collection\ContainerInterface; +use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\Exception\ContainerException; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\Exception\NotFoundException; +use Exception; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface as PsrContainerInterface; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\NotFoundExceptionInterface; +use RuntimeException; /** * A container implementation that wraps around an inner container and prefixes its keys, requiring consumers to @@ -89,11 +92,18 @@ class PrefixingContainer implements ContainerInterface */ public function has($key) { + $key = (string) $key; if (!$this->isPrefixed($key) && $this->strict) { return false; } - return $this->inner->has($this->unprefix($key)) || (!$this->strict && $this->inner->has($key)); + try { + $realKey = $this->unprefix($key); + } catch (Exception $e) { + throw new ContainerException(sprintf('Could not unprefix key "%1$s"', $key), 0, $e); + } + + return $this->inner->has($realKey) || (!$this->strict && $this->inner->has($key)); } /** @@ -108,7 +118,7 @@ class PrefixingContainer implements ContainerInterface protected function unprefix(string $key): string { return $this->isPrefixed($key) - ? substr($key, strlen($this->prefix)) + ? $this->substring($key, strlen($this->prefix)) : $key; } @@ -125,4 +135,35 @@ class PrefixingContainer implements ContainerInterface { return strlen($this->prefix) > 0 && strpos($key, $this->prefix) === 0; } + + /** + * Extracts a substring from the specified string. + * + * @see substr() + * + * @param string $string The string to extract from. + * @param int $offset The char position, at which to start extraction. + * @param int|null $length The char position, at which to end extraction; unlimited if `null`. + * + * @return string The extracted substring. + * + * @throws RuntimeException If unable to extract. + */ + protected function substring(string $string, int $offset = 0, ?int $length = null): string + { + $length = $length ?? strlen($string) - $offset; + $substring = substr($string, $offset, $length); + if ($substring === false) { + throw new RuntimeException( + sprintf( + 'Could not extract substring starting at %1$d of length %2$s from string "%3$s"', + $offset, + $length ?: 'null', + $string + ) + ); + } + + return $substring; + } } diff --git a/lib/packages/Dhii/Container/SegmentingContainer.php b/lib/packages/Dhii/Container/SegmentingContainer.php index 27647605d..35cf09336 100644 --- a/lib/packages/Dhii/Container/SegmentingContainer.php +++ b/lib/packages/Dhii/Container/SegmentingContainer.php @@ -114,6 +114,8 @@ class SegmentingContainer implements ContainerInterface */ public function has($key) { + $key = (string) $key; + return $this->inner->has($key); } } diff --git a/lib/packages/Dhii/Container/ServiceProvider.php b/lib/packages/Dhii/Container/ServiceProvider.php index 39dc8d012..ac3f9e33e 100644 --- a/lib/packages/Dhii/Container/ServiceProvider.php +++ b/lib/packages/Dhii/Container/ServiceProvider.php @@ -5,22 +5,22 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Vendor\Dhii\Container; use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; /** * A value object capable of providing services. * - * @package Dhii\Di + * @psalm-type Factory = callable(ContainerInterface): mixed + * @psalm-type Extension = callable(ContainerInterface, mixed): mixed */ class ServiceProvider implements ServiceProviderInterface { + /** @var callable[] */ + protected array $factories; /** * @var callable[] */ - protected $factories; - /** - * @var callable[] - */ - protected $extensions; + protected array $extensions; /** * @param callable[] $factories A map of service name to service factory. diff --git a/lib/packages/Dhii/Container/SimpleCacheContainer.php b/lib/packages/Dhii/Container/SimpleCacheContainer.php index eba8e29bb..516f8e9de 100644 --- a/lib/packages/Dhii/Container/SimpleCacheContainer.php +++ b/lib/packages/Dhii/Container/SimpleCacheContainer.php @@ -55,6 +55,7 @@ class SimpleCacheContainer implements */ public function has($id) { + $id = (string) $id; $storage = $this->storage; try { @@ -77,7 +78,11 @@ class SimpleCacheContainer implements try { $storage->set($key, $value, $ttl); } catch (Exception $e) { - throw new ContainerException(sprintf('Could not set key "%1$s" with value "%2$s"', $key, $value), 0, $e); + throw new ContainerException( + sprintf('Could not set key "%1$s" with value "%2$s"', $key, (string) $value), + 0, + $e + ); } } @@ -105,7 +110,7 @@ class SimpleCacheContainer implements try { $storage->clear(); } catch (Exception $e) { - throw new ContainerException(sprintf('Could not clear container'), 0, $e); + throw new ContainerException('Could not clear container', 0, $e); } } } diff --git a/lib/packages/Dhii/Container/TaggingServiceProvider.php b/lib/packages/Dhii/Container/TaggingServiceProvider.php new file mode 100644 index 000000000..9f7e81b86 --- /dev/null +++ b/lib/packages/Dhii/Container/TaggingServiceProvider.php @@ -0,0 +1,121 @@ + */ + protected array $factories; + /** @var array */ + protected array $extensions; + + public function __construct(ServiceProviderInterface $inner) + { + $this->factories = $inner->getFactories(); + $this->extensions = $inner->getExtensions(); + $this->indexTags(); + } + + /** + * @inheritDoc + */ + public function getFactories() + { + return $this->factories; + } + + /** + * @inheritDoc + */ + public function getExtensions() + { + return $this->extensions; + } + + /** + * Indexes tagged factories, and creates factories and extensions for tags. + * + * @throws ReflectionException If problem obtaining factory reflection. + */ + protected function indexTags(): void + { + $tags = []; + + foreach ($this->factories as $serviceName => $factory) { + if (is_string($factory)) { + continue; + } + + $reflection = is_object($factory) && get_class($factory) === 'Closure' + ? new ReflectionFunction($factory) + : new ReflectionObject($factory); + $docBlock = $reflection->getDocComment(); + + // No docblock + if ($docBlock === false) { + continue; + } + + $factoryTags = $this->getTagsFromDocBlock($docBlock); + foreach ($factoryTags as $tag) { + if (!isset($tags[$tag]) || !is_array($tags[$tag])) { + $tags[$tag] = []; + } + $tags[$tag][] = $serviceName; + } + } + + foreach ($tags as $tag => $taggedServiceNames) { + $this->factories[$tag] = fn (): array => []; + $this->extensions[$tag] = function (ContainerInterface $c, array $prev) use ($taggedServiceNames): array { + return array_merge( + $prev, + array_map(fn (string $serviceName) => $c->get($serviceName), $taggedServiceNames) + ); + }; + } + } + + /** + * Retrieves tags names that are part of a docBlock. + * + * @link https://www.php.net/manual/en/reflectionclass.getdoccomment.php#118606 + * + * @param string $docBlock The docBlock. + * + * @return array A list of tag names. + */ + protected function getTagsFromDocBlock(string $docBlock): array + { + $regex = '#^\s*/?\**\s*(@tag\s*(?P[^\s]+))#m'; + preg_match_all($regex, $docBlock, $matches); + + return $matches['tags']; + } +} diff --git a/lib/packages/Dhii/Container/Util/StringTranslatingTrait.php b/lib/packages/Dhii/Container/Util/StringTranslatingTrait.php index 206d7e282..c799b4a37 100644 --- a/lib/packages/Dhii/Container/Util/StringTranslatingTrait.php +++ b/lib/packages/Dhii/Container/Util/StringTranslatingTrait.php @@ -27,6 +27,7 @@ trait StringTranslatingTrait */ protected function __(string $string, array $args = array(), $context = null): string { + $context = (string) $context; $string = $this->_translate($string, $context); array_unshift($args, $string); return call_user_func_array('sprintf', $args); diff --git a/lib/packages/Inpsyde/Modularity/Container/ContainerConfigurator.php b/lib/packages/Inpsyde/Modularity/Container/ContainerConfigurator.php index 429869912..810fe1119 100644 --- a/lib/packages/Inpsyde/Modularity/Container/ContainerConfigurator.php +++ b/lib/packages/Inpsyde/Modularity/Container/ContainerConfigurator.php @@ -1,52 +1,38 @@ - */ - private $services = []; + /** @var array */ + private array $services = []; + /** @var array */ + private array $factoryIds = []; + private ServiceExtensions $extensions; + private ?ContainerInterface $compiledContainer = null; + /** @var ContainerInterface[] */ + private array $containers = []; /** - * @var array - */ - private $factoryIds = []; - - /** - * @var array> - */ - private $extensions = []; - - /** - * @var ContainerInterface[] - */ - private $containers = []; - - /** - * @var null|ContainerInterface - */ - private $compiledContainer; - - /** - * ContainerConfigurator constructor. - * * @param ContainerInterface[] $containers */ - public function __construct(array $containers = []) + public function __construct(array $containers = [], ?ServiceExtensions $extensions = null) { array_map([$this, 'addContainer'], $containers); + $this->extensions = $extensions ?? new ServiceExtensions(); } /** - * Allowing to add child containers. - * * @param ContainerInterface $container + * @return void */ public function addContainer(ContainerInterface $container): void { @@ -55,7 +41,7 @@ class ContainerConfigurator /** * @param string $id - * @param callable(ContainerInterface $container):mixed $factory + * @param Service $factory */ public function addFactory(string $id, callable $factory): void { @@ -67,8 +53,7 @@ class ContainerConfigurator /** * @param string $id - * @param callable(ContainerInterface $container):mixed $service - * + * @param Service $service * @return void */ public function addService(string $id, callable $service): void @@ -89,7 +74,6 @@ class ContainerConfigurator /** * @param string $id - * * @return bool */ public function hasService(string $id): bool @@ -109,37 +93,31 @@ class ContainerConfigurator /** * @param string $id - * @param callable(mixed $service, ContainerInterface $container):mixed $extender - * + * @param ExtendingService $extender * @return void */ public function addExtension(string $id, callable $extender): void { - if (!isset($this->extensions[$id])) { - $this->extensions[$id] = []; - } - - $this->extensions[$id][] = $extender; + $this->extensions->add($id, $extender); } /** * @param string $id - * * @return bool */ public function hasExtension(string $id): bool { - return isset($this->extensions[$id]); + return $this->extensions->has($id); } /** - * Returns a read only version of this Container. - * * @return ContainerInterface + * + * @phpstan-assert ContainerInterface $this->compiledContainer */ public function createReadOnlyContainer(): ContainerInterface { - if (!$this->compiledContainer) { + if ($this->compiledContainer === null) { $this->compiledContainer = new ReadOnlyContainer( $this->services, $this->factoryIds, diff --git a/lib/packages/Inpsyde/Modularity/Container/PackageProxyContainer.php b/lib/packages/Inpsyde/Modularity/Container/PackageProxyContainer.php index 2c8d79d5d..47cc8c11d 100644 --- a/lib/packages/Inpsyde/Modularity/Container/PackageProxyContainer.php +++ b/lib/packages/Inpsyde/Modularity/Container/PackageProxyContainer.php @@ -10,15 +10,8 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; class PackageProxyContainer implements ContainerInterface { - /** - * @var Package - */ - private $package; - - /** - * @var ContainerInterface|null - */ - private $container; + private Package $package; + private ?ContainerInterface $container = null; /** * @param Package $package @@ -31,8 +24,6 @@ class PackageProxyContainer implements ContainerInterface /** * @param string $id * @return mixed - * - * @throws \Exception */ public function get(string $id) { @@ -44,8 +35,6 @@ class PackageProxyContainer implements ContainerInterface /** * @param string $id * @return bool - * - * @throws \Exception */ public function has(string $id): bool { @@ -55,33 +44,30 @@ class PackageProxyContainer implements ContainerInterface /** * @return bool * - * @throws \Exception - * @psalm-assert-if-true ContainerInterface $this->container + * @phpstan-assert-if-true ContainerInterface $this->container + * @phpstan-assert-if-false null $this->container */ private function tryContainer(): bool { - if ($this->container) { + if ($this->container !== null) { return true; } - /** TODO: We need a better way to deal with status checking besides equality */ if ( - $this->package->statusIs(Package::STATUS_READY) - || $this->package->statusIs(Package::STATUS_BOOTED) + $this->package->hasContainer() + || $this->package->hasReachedStatus(Package::STATUS_INITIALIZED) ) { $this->container = $this->package->container(); } - return (bool)$this->container; + return $this->container !== null; } /** * @param string $id * @return void * - * @throws \Exception - * - * @psalm-assert ContainerInterface $this->container + * @phpstan-assert ContainerInterface $this->container */ private function assertPackageBooted(string $id): void { @@ -90,13 +76,11 @@ class PackageProxyContainer implements ContainerInterface } $name = $this->package->name(); - $status = $this->package->statusIs(Package::STATUS_FAILED) - ? 'is errored' - : 'is not ready yet'; + $status = $this->package->hasFailed() ? 'is errored' : 'is not ready yet'; - throw new class ("Error retrieving service {$id} because package {$name} {$status}.") - extends \Exception - implements ContainerExceptionInterface { + $error = "Error retrieving service {$id} because package {$name} {$status}."; + throw new class (esc_html($error)) extends \Exception implements ContainerExceptionInterface + { }; } } diff --git a/lib/packages/Inpsyde/Modularity/Container/ReadOnlyContainer.php b/lib/packages/Inpsyde/Modularity/Container/ReadOnlyContainer.php index 763ae23ac..cbd06b9a0 100644 --- a/lib/packages/Inpsyde/Modularity/Container/ReadOnlyContainer.php +++ b/lib/packages/Inpsyde/Modularity/Container/ReadOnlyContainer.php @@ -7,58 +7,43 @@ namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Container; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\NotFoundExceptionInterface; +/** + * @phpstan-import-type Service from \WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule + * @phpstan-import-type ExtendingService from \WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule + */ class ReadOnlyContainer implements ContainerInterface { - /** - * @var array - */ - private $services; + /** @var array */ + private array $services; + /** @var array */ + private array $factoryIds; + private ServiceExtensions $extensions; + /** @var ContainerInterface[] */ + private array $containers; + /** @var array */ + private array $resolvedServices = []; /** - * @var array - */ - private $factoryIds; - - /** - * @var array> - */ - private $extensions; - - /** - * Resolved factories. - * - * @var array - */ - private $resolvedServices = []; - - /** - * @var ContainerInterface[] - */ - private $containers; - - /** - * ReadOnlyContainer constructor. - * - * @param array $services + * @param array $services * @param array $factoryIds - * @param array> $extensions + * @param ServiceExtensions|array $extensions * @param ContainerInterface[] $containers */ public function __construct( array $services, array $factoryIds, - array $extensions, + $extensions, array $containers ) { + $this->services = $services; $this->factoryIds = $factoryIds; - $this->extensions = $extensions; + $this->extensions = $this->configureServiceExtensions($extensions); $this->containers = $containers; } /** * @param string $id - * * @return mixed */ public function get(string $id) @@ -69,7 +54,7 @@ class ReadOnlyContainer implements ContainerInterface if (array_key_exists($id, $this->services)) { $service = $this->services[$id]($this); - $resolved = $this->resolveExtensions($id, $service); + $resolved = $this->extensions->resolve($service, $id, $this); if (!isset($this->factoryIds[$id])) { $this->resolvedServices[$id] = $resolved; @@ -83,19 +68,18 @@ class ReadOnlyContainer implements ContainerInterface if ($container->has($id)) { $service = $container->get($id); - return $this->resolveExtensions($id, $service); + return $this->extensions->resolve($service, $id, $this); } } - throw new class ("Service with ID {$id} not found.") - extends \Exception - implements NotFoundExceptionInterface { + $error = "Service with ID {$id} not found."; + throw new class (esc_html($error)) extends \Exception implements NotFoundExceptionInterface + { }; } /** * @param string $id - * * @return bool */ public function has(string $id): bool @@ -118,21 +102,43 @@ class ReadOnlyContainer implements ContainerInterface } /** - * @param string $id - * @param mixed $service + * Support extensions as array or ServiceExtensions instance for backward compatibility. * - * @return mixed + * With PHP 8+ we could use an actual union type, but when we bump to PHP 8 as min supported + * version, we will probably bump major version as well, so we can just get rid of support + * for array. + * + * @param mixed $extensions + * @return ServiceExtensions */ - private function resolveExtensions(string $id, $service) + private function configureServiceExtensions($extensions): ServiceExtensions { - if (!isset($this->extensions[$id])) { - return $service; + if ($extensions instanceof ServiceExtensions) { + return $extensions; } - foreach ($this->extensions[$id] as $extender) { - $service = $extender($service, $this); + if (!is_array($extensions)) { + $type = is_object($extensions) ? get_class($extensions) : gettype($extensions); + throw new \TypeError( + sprintf( + '%s::%s(): Argument #3 ($extensions) must be of type %s|array, %s given', + __CLASS__, + '__construct', + ServiceExtensions::class, + esc_html($type) + ) + ); } - return $service; + $servicesExtensions = new ServiceExtensions(); + foreach ($extensions as $id => $callback) { + /** + * @var string $id + * @var ExtendingService $callback + */ + $servicesExtensions->add($id, $callback); + } + + return $servicesExtensions; } } diff --git a/lib/packages/Inpsyde/Modularity/Container/ServiceExtensions.php b/lib/packages/Inpsyde/Modularity/Container/ServiceExtensions.php new file mode 100644 index 000000000..3b764041f --- /dev/null +++ b/lib/packages/Inpsyde/Modularity/Container/ServiceExtensions.php @@ -0,0 +1,194 @@ +> */ + protected array $extensions = []; + + /** + * @param string $type + * + * @return string + */ + final public static function typeId(string $type): string + { + return "@instanceof<{$type}>"; + } + + /** + * @param string $extensionId + * @param ExtendingService $extender + * + * @return static + */ + public function add(string $extensionId, callable $extender): ServiceExtensions + { + if (!isset($this->extensions[$extensionId])) { + $this->extensions[$extensionId] = []; + } + $this->extensions[$extensionId][] = $extender; + + return $this; + } + + /** + * @param string $extensionId + * + * @return bool + */ + public function has(string $extensionId): bool + { + return isset($this->extensions[$extensionId]); + } + + /** + * @param mixed $service + * @param string $id + * @param Container $container + * + * @return mixed + */ + final public function resolve($service, string $id, Container $container) + { + $service = $this->resolveById($id, $service, $container); + + return is_object($service) + ? $this->resolveByType(get_class($service), $service, $container) + : $service; + } + + /** + * @param string $id + * @param mixed $service + * @param Container $container + * + * @return mixed + */ + protected function resolveById(string $id, $service, Container $container) + { + foreach ($this->extensions[$id] ?? [] as $extender) { + $service = $extender($service, $container); + } + + return $service; + } + + /** + * @param string $className + * @param object $service + * @param Container $container + * @param string[] $extendedClasses + * + * @return mixed + * + * phpcs:disable SlevomatCodingStandard.Complexity.Cognitive.ComplexityTooHigh + * phpcs:disable Syde.Functions.ReturnTypeDeclaration.NoReturnType + */ + protected function resolveByType( + string $className, + object $service, + Container $container, + array $extendedClasses = [] + ) { + // phpcs:enable SlevomatCodingStandard.Complexity.Cognitive.ComplexityTooHigh + // phpcs:enable Syde.Functions.ReturnTypeDeclaration.NoReturnType + + $extendedClasses[] = $className; + + /** @var array> $allCallbacks */ + $allCallbacks = []; + + // 1st group of extensions: targeting exact class + $byClass = $this->extensions[self::typeId($className)] ?? null; + if (($byClass !== null) && ($byClass !== [])) { + $allCallbacks[$className] = $byClass; + } + + // 2nd group of extensions: targeting parent classes + $parents = class_parents($service, false) ?: []; + foreach ($parents as $parentName) { + $byParent = $this->extensions[self::typeId($parentName)] ?? null; + if (($byParent !== null) && ($byParent !== [])) { + $allCallbacks[$parentName] = $byParent; + } + } + + // 3rd group of extensions: targeting implemented interfaces + $interfaces = class_implements($service, false) ?: []; + foreach ($interfaces as $interfaceName) { + $byInterface = $this->extensions[self::typeId($interfaceName)] ?? null; + if (($byInterface !== null) && ($byInterface !== [])) { + $allCallbacks[$interfaceName] = $byInterface; + } + } + + $resultType = self::SERVICE_TYPE_NOT_CHANGED; + /** @var class-string $type */ + foreach ($allCallbacks as $type => $extenders) { + // When the previous group of callbacks resulted in a type change, we need to check + // type before processing next group. + if (($resultType === self::SERVICE_TYPE_CHANGED) && !is_a($service, $type)) { + continue; + } + /** @var object $service */ + [$service, $resultType] = $this->extendByType($type, $service, $container, $extenders); + if ($resultType === self::SERVICE_TYPE_NOT_OBJECT) { + // Service is not an object anymore, let's return it. + return $service; + } + } + + // If type changed since beginning, let's start over. + // We check if class was already extended to avoid infinite recursion. E.g. instead of: + // `-> extend(A): B -> extend(B): A -> *loop* ->` + // we have: + // `-> extend(A): B -> extend(B): A -> return A`. + $newClassName = get_class($service); + if (!in_array($newClassName, $extendedClasses, true)) { + return $this->resolveByType($newClassName, $service, $container, $extendedClasses); + } + + return $service; + } + + /** + * @param class-string $type + * @param object $service + * @param Container $container + * @param list $extenders + * + * @return list{mixed, int} + */ + private function extendByType( + string $type, + object $service, + Container $container, + array $extenders + ): array { + + foreach ($extenders as $extender) { + $service = $extender($service, $container); + if (!is_object($service)) { + return [$service, self::SERVICE_TYPE_NOT_OBJECT]; + } + if (!is_a($service, $type)) { + return [$service, self::SERVICE_TYPE_CHANGED]; + } + } + + return [$service, self::SERVICE_TYPE_NOT_CHANGED]; + } +} diff --git a/lib/packages/Inpsyde/Modularity/Module/ExecutableModule.php b/lib/packages/Inpsyde/Modularity/Module/ExecutableModule.php index 2f7771fb5..b874af21f 100644 --- a/lib/packages/Inpsyde/Modularity/Module/ExecutableModule.php +++ b/lib/packages/Inpsyde/Modularity/Module/ExecutableModule.php @@ -8,7 +8,6 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; interface ExecutableModule extends Module { - /** * Perform actions with objects retrieved from the container. Usually, adding WordPress hooks. * Return true to signal a success, false to signal a failure. diff --git a/lib/packages/Inpsyde/Modularity/Module/ExtendingModule.php b/lib/packages/Inpsyde/Modularity/Module/ExtendingModule.php index 260e1f0c4..0c6f527c5 100644 --- a/lib/packages/Inpsyde/Modularity/Module/ExtendingModule.php +++ b/lib/packages/Inpsyde/Modularity/Module/ExtendingModule.php @@ -4,9 +4,13 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module; +use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; + +/** + * @phpstan-type ExtendingService callable(mixed $service, ContainerInterface $container): mixed + */ interface ExtendingModule extends Module { - /** * Return application services' extensions. * @@ -18,7 +22,7 @@ interface ExtendingModule extends Module * That is done by using as ID (array key in the `extensions` method) the target module ID * and the service ID. * - * @return array + * @return array */ public function extensions(): array; } diff --git a/lib/packages/Inpsyde/Modularity/Module/FactoryModule.php b/lib/packages/Inpsyde/Modularity/Module/FactoryModule.php index 6e686c0c2..85670d95a 100644 --- a/lib/packages/Inpsyde/Modularity/Module/FactoryModule.php +++ b/lib/packages/Inpsyde/Modularity/Module/FactoryModule.php @@ -4,6 +4,9 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module; +/** + * @phpstan-import-type Service from ServiceModule + */ interface FactoryModule extends Module { /** @@ -12,7 +15,7 @@ interface FactoryModule extends Module * Similar to `services`, but object created by given factories are not "cached", but a *new* * instance is returned everytime `get()` is called in the container. * - * @return array + * @return array */ public function factories(): array; } diff --git a/lib/packages/Inpsyde/Modularity/Module/Module.php b/lib/packages/Inpsyde/Modularity/Module/Module.php index 49dd0e5e3..fa5589393 100644 --- a/lib/packages/Inpsyde/Modularity/Module/Module.php +++ b/lib/packages/Inpsyde/Modularity/Module/Module.php @@ -9,12 +9,10 @@ namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module; */ interface Module { - /** * Unique identifier for your Module. * * @return string */ public function id(): string; - } diff --git a/lib/packages/Inpsyde/Modularity/Module/ModuleClassNameIdTrait.php b/lib/packages/Inpsyde/Modularity/Module/ModuleClassNameIdTrait.php index 938b9b753..b5b5245e6 100644 --- a/lib/packages/Inpsyde/Modularity/Module/ModuleClassNameIdTrait.php +++ b/lib/packages/Inpsyde/Modularity/Module/ModuleClassNameIdTrait.php @@ -4,16 +4,11 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module; -/** - * Trait ModuleClassNameIdTrait - * - * @package WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module - */ trait ModuleClassNameIdTrait { - /** * @return string + * * @see Module::id() */ public function id(): string diff --git a/lib/packages/Inpsyde/Modularity/Module/ServiceModule.php b/lib/packages/Inpsyde/Modularity/Module/ServiceModule.php index 4668ccc5f..8aec8a020 100644 --- a/lib/packages/Inpsyde/Modularity/Module/ServiceModule.php +++ b/lib/packages/Inpsyde/Modularity/Module/ServiceModule.php @@ -4,9 +4,13 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module; +use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; + +/** + * @phpstan-type Service callable(ContainerInterface $container): mixed + */ interface ServiceModule extends Module { - /** * Return application services' factories. * @@ -15,7 +19,7 @@ interface ServiceModule extends Module * Services are "cached", so the given factory is called once the first time `get()` is called * in the container, and on subsequent `get()` the same instance is returned again and again. * - * @return array + * @return array */ public function services(): array; } diff --git a/lib/packages/Inpsyde/Modularity/Package.php b/lib/packages/Inpsyde/Modularity/Package.php index f55eaeccb..75f1d3e52 100644 --- a/lib/packages/Inpsyde/Modularity/Package.php +++ b/lib/packages/Inpsyde/Modularity/Package.php @@ -6,19 +6,22 @@ namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Container\ContainerConfigurator; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Container\PackageProxyContainer; -use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\FactoryModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\Module; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties\Properties; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; +/** + * @phpstan-import-type Service from \WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule + * @phpstan-import-type ExtendingService from \WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule + */ class Package { /** * All the hooks fired in this class use this prefix. - * @var string */ private const HOOK_PREFIX = 'inpsyde.modularity.'; @@ -34,14 +37,13 @@ class Package * $container->has(Package::PROPERTIES); * $container->get(Package::PROPERTIES); * - * - * @var string */ public const PROPERTIES = 'properties'; /** - * Custom action to be used to add Modules to the package. + * Custom action to be used to add modules and connect other packages. * It might also be used to access package properties. + * Access container is not possible at this stage. * * @example * @@ -49,67 +51,64 @@ class Package * * add_action( * $package->hookName(Package::ACTION_INIT), - * $callback + * fn (Package $package) => // do something, * ); * */ public const ACTION_INIT = 'init'; /** - * Custom action which is triggered after the application - * is booted to access container and properties. + * Very similar to `ACTION_INIT`, but it is static, so not dependent on package name. + * It passes package name as first argument. * * @example - * - * $package = Package::new(); - * - * add_action( - * $package->hookName(Package::ACTION_READY), - * $callback - * ); - * + * + * add_action( + * Package::ACTION_MODULARITY_INIT, + * fn (string $packageName, Package $package) => // do something, + * 10, + * 2 + * ); + * */ - public const ACTION_READY = 'ready'; + public const ACTION_MODULARITY_INIT = self::HOOK_PREFIX . self::ACTION_INIT; /** - * Custom action which is triggered when a failure happens during the building stage. - * - * @example - * - * $package = Package::new(); - * - * add_action( - * $package->hookName(Package::ACTION_FAILED_BUILD), - * $callback - * ); - * + * Action fired when it is safe to access container. + * Add more modules is not anymore possible at this stage. + */ + public const ACTION_INITIALIZED = 'initialized'; + + /** + * Action fired when plugin finished its bootstrapping process, all its hooks are added. + * Add more modules is not anymore possible at this stage. + */ + public const ACTION_BOOTED = 'ready'; + + /** + * Action fired when anything went wrong during the "build" procedure. */ public const ACTION_FAILED_BUILD = 'failed-build'; /** - * Custom action which is triggered when a failure happens during the booting stage. - * - * @example - * - * $package = Package::new(); - * - * add_action( - * $package->hookName(Package::ACTION_FAILED_BOOT), - * $callback - * ); - * + * Action fired when anything went wrong during the "boot" procedure. */ public const ACTION_FAILED_BOOT = 'failed-boot'; /** - * Custom action which is triggered when a package is connected. + * Action fired when adding a module failed. */ - public const ACTION_PACKAGE_CONNECTED = 'package-connected'; + public const ACTION_FAILED_ADD_MODULE = 'failed-add-module'; /** - * Custom action which is triggered when a package cannot be connected. + * Action fired when a package connection failed. */ - public const ACTION_FAILED_CONNECTION = 'failed-connection'; + public const ACTION_FAILED_CONNECT = 'failed-connection'; + + /** + * Action fired when a package is connected successfully. + */ + public const ACTION_PACKAGE_CONNECTED = 'package-connected'; /** * Module states can be used to get information about your module. @@ -118,7 +117,7 @@ class Package * * $package = Package::new(); * $package->moduleIs(SomeModule::class, Package::MODULE_ADDED); // false - * $package->boot(new SomeModule()); + * $package->addModule(new SomeModule()); * $package->moduleIs(SomeModule::class, Package::MODULE_ADDED); // true * */ @@ -137,90 +136,81 @@ class Package * @example * * $package = Package::new(); - * $package->statusIs(Package::IDLE); // true + * $package->statusIs(Package::STATUS_IDLE); // true + * $package->build(); + * $package->statusIs(Package::STATUS_INITIALIZED); // true * $package->boot(); - * $package->statusIs(Package::BOOTED); // true + * $package->statusIs(Package::STATUS_DONE); // true * */ public const STATUS_IDLE = 2; + public const STATUS_INITIALIZING = 3; public const STATUS_INITIALIZED = 4; - public const STATUS_MODULES_ADDED = 5; - public const STATUS_READY = 7; - public const STATUS_BOOTED = 8; + public const STATUS_BOOTING = 5; + public const STATUS_BOOTED = 7; + public const STATUS_DONE = 8; public const STATUS_FAILED = -8; - /** - * Current state of the application. - * - * @see Package::STATUS_* - * - * @var int - */ - private $status = self::STATUS_IDLE; + // Deprecated flags + /** @deprecated */ + public const STATUS_MODULES_ADDED = self::STATUS_BOOTING; + /** @deprecated */ + public const ACTION_READY = self::ACTION_BOOTED; + /** @deprecated */ + public const ACTION_FAILED_CONNECTION = self::ACTION_FAILED_CONNECT; - /** - * Contains the progress of all modules. - * - * @see Package::moduleProgress() - * - * @var array> - */ - private $moduleStatus = [self::MODULES_ALL => []]; + // Map of status to package-specific and global hook, both optional (i..e, null). + private const STATUSES_ACTIONS_MAP = [ + self::STATUS_INITIALIZING => [self::ACTION_INIT, self::ACTION_MODULARITY_INIT], + self::STATUS_INITIALIZED => [self::ACTION_INITIALIZED, null], + self::STATUS_BOOTED => [self::ACTION_BOOTED, null], + ]; - /** - * Hashmap of where keys are names of connected packages, and values are boolean, true - * if connection was successful. - * - * @see Package::connect() - * - * @var array - */ - private $connectedPackages = []; + private const SUCCESS_STATUSES = [ + self::STATUS_IDLE => self::STATUS_IDLE, + self::STATUS_INITIALIZING => self::STATUS_INITIALIZING, + self::STATUS_INITIALIZED => self::STATUS_INITIALIZED, + self::STATUS_BOOTING => self::STATUS_BOOTING, + self::STATUS_BOOTED => self::STATUS_BOOTED, + self::STATUS_DONE => self::STATUS_DONE, + ]; - /** - * @var list - */ - private $executables = []; + private const OPERATORS = [ + '<' => '<', + '<=' => '<=', + '>' => '>', + '>=' => '>=', + '==' => '==', + '!=' => '!=', + ]; - /** - * @var Properties - */ - private $properties; - - /** - * @var ContainerConfigurator - */ - private $containerConfigurator; - - /** - * @var bool - */ - private $built = false; - - /** - * @var bool - */ - private $hasContainer = false; - - /** - * @var \Throwable|null - */ - private $lastError = null; + /** @var Package::STATUS_* */ + private int $status = self::STATUS_IDLE; + /** @var array> */ + private array $moduleStatus = [self::MODULES_ALL => []]; + /** @var array */ + private array $connectedPackages = []; + /** @var list */ + private array $executables = []; + private Properties $properties; + private ContainerConfigurator $containerConfigurator; + private bool $built = false; + private bool $hasContainer = false; + private ?\Throwable $lastError = null; /** * @param Properties $properties - * @param ContainerInterface[] $containers - * + * @param ContainerInterface ...$containers * @return Package */ - public static function new(Properties $properties, ContainerInterface ...$containers): Package + public static function new(Properties $properties, ContainerInterface ...$containers): Package { return new self($properties, ...$containers); } /** * @param Properties $properties - * @param ContainerInterface[] $containers + * @param ContainerInterface ...$containers */ private function __construct(Properties $properties, ContainerInterface ...$containers) { @@ -229,7 +219,7 @@ class Package $this->containerConfigurator = new ContainerConfigurator($containers); $this->containerConfigurator->addService( self::PROPERTIES, - static function () use ($properties) { + static function () use ($properties): Properties { return $properties; } ); @@ -237,14 +227,14 @@ class Package /** * @param Module $module - * * @return static - * @throws \Exception */ public function addModule(Module $module): Package { try { - $this->assertStatus(self::STATUS_IDLE, sprintf('add module %s', $module->id())); + $reason = sprintf('add module %s', $module->id()); + $this->assertStatus(self::STATUS_FAILED, $reason, '!='); + $this->assertStatus(self::STATUS_INITIALIZING, $reason, '<='); $registeredServices = $this->addModuleServices( $module, @@ -271,7 +261,7 @@ class Package $status = $added ? self::MODULE_ADDED : self::MODULE_NOT_ADDED; $this->moduleProgress($module->id(), $status); } catch (\Throwable $throwable) { - $this->handleFailure($throwable, self::ACTION_FAILED_BUILD); + $this->handleFailure($throwable, self::ACTION_FAILED_ADD_MODULE); } return $this; @@ -280,7 +270,6 @@ class Package /** * @param Package $package * @return bool - * @throws \Exception */ public function connect(Package $package): bool { @@ -290,33 +279,17 @@ class Package } $packageName = $package->name(); - $errorData = ['package' => $packageName, 'status' => $this->status]; - $errorMessage = "Failed connecting package {$packageName}"; // Don't connect, if already connected if (array_key_exists($packageName, $this->connectedPackages)) { - $error = "{$errorMessage} because it was already connected."; - do_action( - $this->hookName(self::ACTION_FAILED_CONNECTION), - $packageName, - new \WP_Error('already_connected', $error, $errorData) - ); - - throw new \Exception($error, 0, $this->lastError); + return $this->handleConnectionFailure($packageName, 'already connected', false); } // Don't connect, if already booted or boot failed - $failed = $this->statusIs(self::STATUS_FAILED); - if ($failed || $this->statusIs(self::STATUS_BOOTED)) { - $status = $failed ? 'errored' : 'booted'; - $error = "{$errorMessage} to a {$status} package."; - do_action( - $this->hookName(self::ACTION_FAILED_CONNECTION), - $packageName, - new \WP_Error("no_connect_on_{$status}", $error, $errorData) - ); - - throw new \Exception($error, 0, $this->lastError); + $failed = $this->hasFailed(); + if ($failed || $this->hasReachedStatus(self::STATUS_INITIALIZED)) { + $reason = $failed ? 'is errored' : 'has a built container already'; + $this->handleConnectionFailure($packageName, "current package {$reason}", true); } $this->connectedPackages[$packageName] = true; @@ -330,9 +303,10 @@ class Package } ); - // If the other package is booted, we can obtain a container, otherwise - // we build a proxy container - $container = $package->statusIs(self::STATUS_BOOTED) + // If we can obtain a container we do, otherwise we build a proxy container + $packageHasContainer = $package->hasReachedStatus(self::STATUS_INITIALIZED) + || $package->hasContainer(); + $container = $packageHasContainer ? $package->container() : new PackageProxyContainer($package); @@ -347,7 +321,10 @@ class Package return true; } catch (\Throwable $throwable) { - if (isset($packageName)) { + if ( + isset($packageName) + && (($this->connectedPackages[$packageName] ?? false) !== true) + ) { $this->connectedPackages[$packageName] = false; } $this->handleFailure($throwable, self::ACTION_FAILED_BUILD); @@ -362,17 +339,26 @@ class Package public function build(): Package { try { - // Don't allow building the application multiple times. + // Be tolerant about things like `$package->build()->build()`. + // Sometimes, from the extern, we might want to call `build()` to ensure the container + // is ready before accessing a service. And in that case we don't want to throw an + // exception if the container is already built. + if ($this->built && $this->statusIs(self::STATUS_INITIALIZED)) { + return $this; + } + + // We expect `build` to be called only after `addModule()` or `connect()` which do + // not change the status, so we expect status to be still "IDLE". + // This will prevent invalid things like calling `build()` from inside something + // hooking ACTION_INIT OR ACTION_INITIALIZED. $this->assertStatus(self::STATUS_IDLE, 'build package'); - do_action( - $this->hookName(self::ACTION_INIT), - $this - ); - // Changing the status here ensures we can not call this method again, and also we can not - // add new modules, because both this and `addModule()` methods check for idle status. - // For backward compatibility, adding new modules via `boot()` will still be possible, even - // if deprecated, at the condition that the container was not yet accessed at that point. + // This will change the status to "INITIALIZING" then fire the action that allow other + // packages to add modules or connect packages. + $this->progress(self::STATUS_INITIALIZING); + + // This will change the status to "INITIALIZED" then fire an action when it is safe to + // access the container, because from this moment on, container is locked from change. $this->progress(self::STATUS_INITIALIZED); } catch (\Throwable $throwable) { $this->handleFailure($throwable, self::ACTION_FAILED_BUILD); @@ -386,37 +372,47 @@ class Package /** * @param Module ...$defaultModules Deprecated, use `addModule()` to add default modules. * @return bool - * - * @throws \Throwable */ public function boot(Module ...$defaultModules): bool { try { + // When package is done, nothing should happen to it calling boot again, but we call + // false to signal something is off. + if ($this->statusIs(self::STATUS_DONE)) { + return false; + } + // Call build() if not called yet, and ensure any new module passed here is added // as well, throwing if the container was already built. $this->doBuild(...$defaultModules); - // Don't allow booting the application multiple times. - $this->assertStatus(self::STATUS_MODULES_ADDED, 'boot application', '<'); - $this->assertStatus(self::STATUS_FAILED, 'boot application', '!='); + // Make sure we call boot() on a non-failed instance, and also make a sanity check + // on the status flow, e.g. prevent calling boot() from an action hook. + $this->assertStatus(self::STATUS_INITIALIZED, 'boot application'); - $this->progress(self::STATUS_MODULES_ADDED); + // This will change status to STATUS_BOOTING "locking" subsequent call to `boot()`, but + // no hook is fired here, because at this point we can not do anything more or less than + // what can be done on the ACTION_INITIALIZED hook, so that hook is sufficient. + $this->progress(self::STATUS_BOOTING); $this->doExecute(); - $this->progress(self::STATUS_READY); - - do_action( - $this->hookName(self::ACTION_READY), - $this - ); + // This will change status to STATUS_BOOTED and then fire an action that make it + // possible to hook on a package that has finished its bootstrapping process, so all its + // "executable" modules have been executed. + $this->progress(self::STATUS_BOOTED); } catch (\Throwable $throwable) { $this->handleFailure($throwable, self::ACTION_FAILED_BOOT); return false; } - $this->progress(self::STATUS_BOOTED); + // This will change the status to DONE and will not fire any action. + // This is a status that proves that everything went well, not only the Package itself, + // but also anything hooking Package's hooks. + // The only way to move out of this status is a failure that might only happen directly + // calling `addModule()`, `connect()` or `build()`. + $this->progress(self::STATUS_DONE); return true; } @@ -439,39 +435,67 @@ class Package ); } + // We expect `boot()` to be called either: + // 1. Directly after `addModule()`/`connect()`, without any `build()` call in between, so + // status is IDLE and `$this->built` is `false`. + // 2. After `build()` is called, so status is INITIALIZED and `$this->built` is `true`. + // Any other usage is not allowed (e.g. calling `boot()` from an hook callback) and in that + // case we return here, giving back control to `boot()` which will throw. + $validFlows = (!$this->built && $this->statusIs(self::STATUS_IDLE)) + || ($this->built && $this->statusIs(self::STATUS_INITIALIZED)); + + if (!$validFlows) { + // If none of the two supported flows happened, we just return handling control back + // to `boot()`, that will throw. + return; + } + if (!$this->built) { - array_map([$this, 'addModule'], $defaultModules); + // First valid flow: `boot()` was called directly after `addModule()`/`connect()` + // without any call to `build()`. We can call `build()` and return, handing control + // back to `boot()`. Before returning, if we had default modules passed to `boot()` we + // already have fired a deprecation, so here we just add them dealing with back-compat. + foreach ($defaultModules as $defaultModule) { + $this->addModule($defaultModule); + } $this->build(); return; } - if ( - !$defaultModules - || ($this->status >= self::STATUS_MODULES_ADDED) - || ($this->statusIs(self::STATUS_FAILED)) - ) { - // if we don't have default modules, there's nothing to do, and if the status is beyond - // "modules added" or is failed, we do nothing as well and let `boot()` throw. + // Second valid flow: we have called `boot()` after `build()`. If we did it correctly, + // without default modules passed to `boot()`, we can just return handing control back + // to `boot()`. + if (!$defaultModules) { return; } + // If here, we have done something like: `$package->build()->boot($module1, $module2)`. + // Passing modules to `boot()` was deprecated when `build()` was introduced, so whoever + // added `build()` should have removed modules passed to `boot()`. + // But we want to keep 100% backward compatibility so we still support this behavior + // until the next major is released. To do that, we simulate IDLE status to prevent + // `addModule()` from throwing when adding default modules. + // But we can do that only if we don't have a compiled container yet. + // If anything hooking ACTION_INIT called `container()` we have a compiled container + // already, and we can't add modules, so we not going to simulate INIT status, which mean + // the `$this->addModule()` call below will throw. $backup = $this->status; - try { - // simulate idle status to prevent `addModule()` from throwing - // only if we don't have a container yet - $this->hasContainer or $this->status = self::STATUS_IDLE; - + if (!$this->hasContainer()) { + $this->status = self::STATUS_IDLE; + } foreach ($defaultModules as $defaultModule) { - // If a module was added by `build()` or `addModule()` we can skip it, a - // deprecation was trigger to make it noticeable without breakage + // If a module was already added via `addModule()` we can skip it, reducing the + // chances of throwing an exception if not needed. if (!$this->moduleIs($defaultModule->id(), self::MODULE_ADDED)) { $this->addModule($defaultModule); } } } finally { - $this->status = $backup; + if (!$this->hasFailed()) { + $this->status = $backup; + } } } @@ -482,7 +506,9 @@ class Package */ private function addModuleServices(Module $module, string $status): bool { + /** @var null|array $services */ $services = null; + /** @var null|callable(string, Service|ExtendingService): void $addCallback */ $addCallback = null; switch ($status) { case self::MODULE_REGISTERED: @@ -499,21 +525,16 @@ class Package break; } - if (!$services) { + if (($services === null) || ($services === []) || ($addCallback === null)) { return false; } $ids = []; - array_walk( - $services, - static function (callable $service, string $id) use ($addCallback, &$ids) { - /** @var callable(string, callable) $addCallback */ - $addCallback($id, $service); - /** @var list $ids */ - $ids[] = $id; - } - ); - /** @var list $ids */ + foreach ($services as $id => $service) { + $addCallback($id, $service); + $ids[] = $id; + } + $this->moduleProgress($module->id(), $status, $ids); return true; @@ -521,8 +542,6 @@ class Package /** * @return void - * - * @throws \Throwable */ private function doExecute(): void { @@ -530,9 +549,7 @@ class Package $success = $executable->run($this->container()); $this->moduleProgress( $executable->id(), - $success - ? self::MODULE_EXECUTED - : self::MODULE_EXECUTION_FAILED + $success ? self::MODULE_EXECUTED : self::MODULE_EXECUTION_FAILED, ); } } @@ -541,15 +558,20 @@ class Package * @param string $moduleId * @param string $status * @param list|null $serviceIds - * - * @return void + * @return void */ - private function moduleProgress(string $moduleId, string $status, ?array $serviceIds = null) - { - isset($this->moduleStatus[$status]) or $this->moduleStatus[$status] = []; + private function moduleProgress( + string $moduleId, + string $status, + ?array $serviceIds = null + ): void { + + if (!isset($this->moduleStatus[$status])) { + $this->moduleStatus[$status] = []; + } $this->moduleStatus[$status][] = $moduleId; - if (!$serviceIds || !$this->properties->isDebug()) { + if (($serviceIds === null) || ($serviceIds === []) || !$this->properties->isDebug()) { $this->moduleStatus[self::MODULES_ALL][] = "{$moduleId} {$status}"; return; @@ -605,10 +627,9 @@ class Package * `inpsyde.modularity.my-plugin` anyway, so the file name is not relevant. * * @param string $suffix - * * @return string - * @see Package::name() * + * @see Package::name() */ public function hookName(string $suffix = ''): string { @@ -631,8 +652,6 @@ class Package /** * @return ContainerInterface - * - * @throws \Exception */ public function container(): ContainerInterface { @@ -642,6 +661,14 @@ class Package return $this->containerConfigurator->createReadOnlyContainer(); } + /** + * @return bool + */ + public function hasContainer(): bool + { + return $this->hasContainer; + } + /** * @return string */ @@ -652,27 +679,94 @@ class Package /** * @param int $status - */ - private function progress(int $status): void - { - $this->status = $status; - } - - /** - * @param int $status - * * @return bool */ public function statusIs(int $status): bool { - return $this->status === $status; + return $this->checkStatus($status); + } + + /** + * @return bool + */ + public function hasFailed(): bool + { + return $this->status === self::STATUS_FAILED; + } + + /** + * @param int $status + * @return bool + */ + public function hasReachedStatus(int $status): bool + { + if ($this->hasFailed()) { + return false; + } + + return isset(self::SUCCESS_STATUSES[$status]) && $this->checkStatus($status, '>='); + } + + /** + * @param int $status + * @param value-of $operator + * @return bool + */ + private function checkStatus(int $status, string $operator = '=='): bool + { + assert(isset(self::OPERATORS[$operator])); + + return version_compare((string) $this->status, (string) $status, $operator); + } + + /** + * @param Package::STATUS_* $status + */ + private function progress(int $status): void + { + $this->status = $status; + + [$packageHookSuffix, $globalHook] = self::STATUSES_ACTIONS_MAP[$status] ?? [null, null]; + if ($packageHookSuffix !== null) { + do_action($this->hookName($packageHookSuffix), $this); + } + if ($globalHook !== null) { + do_action($globalHook, $this->name(), $this); + } + } + + /** + * @param string $packageName + * @param string $reason + * @param bool $throw + * @return bool + */ + private function handleConnectionFailure(string $packageName, string $reason, bool $throw): bool + { + $errorData = ['package' => $packageName, 'status' => $this->status]; + $message = "Failed connecting package {$packageName} because {$reason}."; + + do_action( + $this->hookName(self::ACTION_FAILED_CONNECT), + $packageName, + new \WP_Error('failed_connection', $message, $errorData) + ); + + if ($throw) { + throw new \Exception( + esc_html($message), + 0, + $this->lastError // phpcs:ignore WordPress.Security.EscapeOutput + ); + } + + return false; } /** * @param \Throwable $throwable * @param Package::ACTION_FAILED_* $action * @return void - * @throws \Throwable */ private function handleFailure(\Throwable $throwable, string $action): void { @@ -690,18 +784,15 @@ class Package /** * @param int $status * @param string $action - * @param string $operator - * - * @throws \Exception - * @psalm-suppress ArgumentTypeCoercion + * @param value-of $operator */ private function assertStatus(int $status, string $action, string $operator = '=='): void { - if (!version_compare((string) $this->status, (string) $status, $operator)) { + if (!$this->checkStatus($status, $operator)) { throw new \Exception( - sprintf("Can't %s at this point of application.", $action), + sprintf("Can't %s at this point of application.", esc_html($action)), 0, - $this->lastError + $this->lastError // phpcs:ignore WordPress.Security.EscapeOutput ); } } @@ -713,7 +804,6 @@ class Package * @param string $message * @param string $function * @param string $version - * * @return void */ private function deprecatedArgument(string $message, string $function, string $version): void @@ -721,7 +811,9 @@ class Package do_action('deprecated_argument_run', $function, $message, $version); if (apply_filters('deprecated_argument_trigger_error', true)) { - trigger_error($message, \E_USER_DEPRECATED); + do_action('wp_trigger_error_run', $function, $message, \E_USER_DEPRECATED); + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error + trigger_error(esc_html($message), \E_USER_DEPRECATED); } } } diff --git a/lib/packages/Inpsyde/Modularity/Properties/BaseProperties.php b/lib/packages/Inpsyde/Modularity/Properties/BaseProperties.php index 47bb4db74..2df5ae729 100644 --- a/lib/packages/Inpsyde/Modularity/Properties/BaseProperties.php +++ b/lib/packages/Inpsyde/Modularity/Properties/BaseProperties.php @@ -6,47 +6,30 @@ namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties; class BaseProperties implements Properties { - /** - * @var null|bool - */ - protected $isDebug = null; - - /** - * @var string - */ - protected $baseName; - - /** - * @var string - */ - protected $basePath; - - /** - * @var string|null - */ - protected $baseUrl; - - /** - * @var array - */ - protected $properties; + protected ?bool $isDebug = null; + protected string $baseName; + protected string $basePath; + protected ?string $baseUrl; + /** @var array */ + protected array $properties; /** * @param string $baseName * @param string $basePath * @param string|null $baseUrl - * @param array $properties + * @param array $properties */ protected function __construct( string $baseName, string $basePath, - string $baseUrl = null, + ?string $baseUrl = null, array $properties = [] ) { + $baseName = $this->sanitizeBaseName($baseName); - $basePath = (string) trailingslashit($basePath); - if ($baseUrl) { - $baseUrl = (string) trailingslashit($baseUrl); + $basePath = trailingslashit($basePath); + if ($baseUrl !== null) { + $baseUrl = trailingslashit($baseUrl); } $this->baseName = $baseName; @@ -58,11 +41,13 @@ class BaseProperties implements Properties /** * @param string $name * - * @return string + * @return lowercase-string */ protected function sanitizeBaseName(string $name): string { - substr_count($name, '/') and $name = dirname($name); + if (substr_count($name, '/')) { + $name = dirname($name); + } return strtolower(pathinfo($name, PATHINFO_FILENAME)); } @@ -162,7 +147,9 @@ class BaseProperties implements Properties { $value = $this->get(self::PROP_REQUIRES_WP); - return $value && is_string($value) ? $value : null; + return (($value !== '') && is_string($value)) + ? $value + : null; } /** @@ -172,11 +159,13 @@ class BaseProperties implements Properties { $value = $this->get(self::PROP_REQUIRES_PHP); - return $value && is_string($value) ? $value : null; + return (($value !== '') && is_string($value)) + ? $value + : null; } /** - * @return array + * @return string[] */ public function tags(): array { @@ -185,7 +174,8 @@ class BaseProperties implements Properties /** * @param string $key - * @param null $default + * @param mixed $default + * * @return mixed */ public function get(string $key, $default = null) @@ -195,6 +185,7 @@ class BaseProperties implements Properties /** * @param string $key + * * @return bool */ public function has(string $key): bool diff --git a/lib/packages/Inpsyde/Modularity/Properties/LibraryProperties.php b/lib/packages/Inpsyde/Modularity/Properties/LibraryProperties.php index 5c06577e1..612787e0f 100644 --- a/lib/packages/Inpsyde/Modularity/Properties/LibraryProperties.php +++ b/lib/packages/Inpsyde/Modularity/Properties/LibraryProperties.php @@ -5,17 +5,26 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties; /** - * Class LibraryProperties - * - * @package WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties + * @phpstan-type ComposerAuthor array{ + * name: string, + * email?: string, + * homepage?: string, + * role?: string, + * } + * @phpstan-type ComposerData array{ + * name: string, + * version?: string, + * require?: array, + * require-dev?: array, + * description?: string, + * keywords?: string[], + * authors?: ComposerAuthor[], + * extra?: array{modularity?: array}, + * } */ class LibraryProperties extends BaseProperties { - /** - * Allowed configuration in composer.json "extra.modularity". - * - * @var array - */ + /** Allowed configuration in composer.json "extra.modularity" */ public const EXTRA_KEYS = [ self::PROP_DOMAIN_PATH, self::PROP_NAME, @@ -31,32 +40,33 @@ class LibraryProperties extends BaseProperties * * @return LibraryProperties * - * @throws \Exception - * @psalm-suppress MixedArrayAccess + * phpcs:disable SlevomatCodingStandard.Complexity */ public static function new(string $composerJsonFile, ?string $baseUrl = null): LibraryProperties { - if (!\is_file($composerJsonFile) || !\is_readable($composerJsonFile)) { - throw new \Exception("File {$composerJsonFile} does not exist or is not readable."); - } + // phpcs:enable SlevomatCodingStandard.Complexity - $content = (string) file_get_contents($composerJsonFile); - /** @var array $composerJsonData */ - $composerJsonData = json_decode($content, true); + $composerJsonData = self::readComposerJsonData($composerJsonFile); $properties = Properties::DEFAULT_PROPERTIES; $properties[self::PROP_DESCRIPTION] = $composerJsonData['description'] ?? ''; $properties[self::PROP_TAGS] = $composerJsonData['keywords'] ?? []; $authors = $composerJsonData['authors'] ?? []; + if (!is_array($authors)) { + $authors = []; + } $names = []; - foreach ((array) $authors as $author) { - $name = $author['name'] ?? null; - if ($name && is_string($name)) { + foreach ($authors as $author) { + if (!is_array($author)) { + continue; + } + $name = $author['name'] ?? ''; + if (($name !== '') && is_string($name)) { $names[] = $name; } - $url = $author['homepage'] ?? null; - if ($url && !$properties['authorUri'] && is_string($url)) { + $url = $author['homepage'] ?? ''; + if (($url !== '') && ($properties[self::PROP_AUTHOR_URI] === '') && is_string($url)) { $properties[self::PROP_AUTHOR_URI] = $url; } } @@ -66,6 +76,9 @@ class LibraryProperties extends BaseProperties // Custom settings which can be stored in composer.json "extra.modularity" $extra = $composerJsonData['extra']['modularity'] ?? []; + if (!is_array($extra)) { + $extra = []; + } foreach (self::EXTRA_KEYS as $key) { $properties[$key] = $extra[$key] ?? ''; } @@ -74,39 +87,50 @@ class LibraryProperties extends BaseProperties $properties[self::PROP_REQUIRES_PHP] = self::extractPhpVersion($composerJsonData); // composer.json might have "version" in root - $version = $composerJsonData['version'] ?? null; - if ($version && is_string($version)) { + $version = $composerJsonData['version'] ?? ''; + if (($version !== '') && is_string($version)) { $properties[self::PROP_VERSION] = $version; } [$baseName, $name] = static::buildNames($composerJsonData); $basePath = dirname($composerJsonFile); - if (empty($properties[self::PROP_NAME])) { + if (($properties[self::PROP_NAME] === '') || !is_string($properties[self::PROP_NAME])) { $properties[self::PROP_NAME] = $name; } - return new self( - $baseName, - $basePath, - $baseUrl, - $properties - ); + return new self($baseName, $basePath, $baseUrl, $properties); } /** - * @param array $composerJsonData + * @param string $url + * + * @return static + */ + public function withBaseUrl(string $url): LibraryProperties + { + if ($this->baseUrl !== null) { + throw new \Exception(sprintf('%s::$baseUrl property is not overridable.', __CLASS__)); + } + + $this->baseUrl = trailingslashit($url); + + return $this; + } + + /** + * @param ComposerData $composerJsonData * * @return array{string, string} */ - private static function buildNames(array $composerJsonData): array + protected static function buildNames(array $composerJsonData): array { $composerName = (string) ($composerJsonData['name'] ?? ''); $packageNamePieces = explode('/', $composerName, 2); $basename = implode('-', $packageNamePieces); - // "inpsyde/foo-bar-baz" => "Inpsyde Foo Bar Baz" + // From "syde/foo-bar-baz" to "Syde Foo Bar Baz" $name = mb_convert_case( str_replace(['-', '_', '.'], ' ', implode(' ', $packageNamePieces)), - MB_CASE_TITLE + MB_CASE_TITLE, ); return [$basename, $name]; @@ -122,88 +146,111 @@ class LibraryProperties extends BaseProperties * `5.6 || >= 7.1` returns `5.6` * `>= 7.1 < 8` returns `7.1` * - * @param array $composerData + * @param ComposerData $composerData * @param string $key * - * @return string|null + * @return string */ - private static function extractPhpVersion(array $composerData, string $key = 'require'): ?string - { + protected static function extractPhpVersion( + array $composerData, + string $key = 'require' + ): string { + $nextKey = ($key === 'require') ? 'require-dev' : null; - $base = (array) ($composerData[$key] ?? []); - $requirement = $base['php'] ?? null; - $version = ($requirement && is_string($requirement)) + $base = $composerData[$key] ?? null; + $requirement = is_array($base) + ? ($base['php'] ?? '') + : ''; + $version = (($requirement !== '') && is_string($requirement)) ? trim($requirement) - : null; - if (!$version) { - return $nextKey + : ''; + if ($version === '') { + return ($nextKey !== null) ? static::extractPhpVersion($composerData, $nextKey) - : null; + : ''; } - static $matcher; - $matcher or $matcher = static function (string $version): ?string { - $version = trim($version); - if (!$version) { - return null; - } - - // versions range like `>= 7.2.4 < 8` - if (preg_match('{>=?([\s0-9\.]+)<}', $version, $matches)) { - return trim($matches[1], " \t\n\r\0\x0B."); - } - - // aliases like `dev-src#abcde as 7.4` - if (preg_match('{as\s*([\s0-9\.]+)}', $version, $matches)) { - return trim($matches[1], " \t\n\r\0\x0B."); - } - - // Basic requirements like 7.2, >=7.2, ^7.2, ~7.2 - if (preg_match('{^(?:[>=\s~\^]+)?([0-9\.]+)}', $version, $matches)) { - return trim($matches[1], " \t\n\r\0\x0B."); - } - - return null; - }; - // support for simpler requirements like `7.3`, `>=7.4` or alternative like `5.6 || >=7` $alternatives = explode('||', $version); + /** @var non-empty-string|null $found */ $found = null; foreach ($alternatives as $alternative) { - /** @var callable(string):?string $matcher */ - $itemFound = $matcher($alternative); - if ($itemFound && (!$found || version_compare($itemFound, $found, '<'))) { + $itemFound = static::parseVersion($alternative); + if ( + ($itemFound !== '') + && (($found === null) || version_compare($itemFound, $found, '<')) + ) { $found = $itemFound; } } - if ($found) { + if ($found !== null) { return $found; } - return $nextKey + return ($nextKey !== null) ? static::extractPhpVersion($composerData, $nextKey) - : null; + : ''; } /** - * @param string $url + * @param string $version * - * @return static - * - * @throws \Exception + * @return string */ - public function withBaseUrl(string $url): LibraryProperties + protected static function parseVersion(string $version): string { - if ($this->baseUrl !== null) { - throw new \Exception(sprintf('%s::$baseUrl property is not overridable.', __CLASS__)); + $version = trim($version); + if ($version === '') { + return ''; } - $this->baseUrl = trailingslashit($url); + // versions range like `>= 7.2.4 < 8` + if (preg_match('{>=?([\s0-9\.]+)<}', $version, $matches)) { + return trim($matches[1], " \t\n\r\0\x0B."); + } - return $this; + // aliases like `dev-src#abcde as 7.4` + if (preg_match('{as\s*([\s0-9\.]+)}', $version, $matches)) { + return trim($matches[1], " \t\n\r\0\x0B."); + } + + // Basic requirements like 7.2, >=7.2, ^7.2, ~7.2 + if (preg_match('{^(?:[>=\s~\^]+)?([0-9\.]+)}', $version, $matches)) { + return trim($matches[1], " \t\n\r\0\x0B."); + } + + return ''; + } + + /** + * @param string $composerJsonFile + * + * @return ComposerData + * @throws \Exception + */ + private static function readComposerJsonData(string $composerJsonFile): array + { + if (!\is_file($composerJsonFile) || !\is_readable($composerJsonFile)) { + throw new \Exception( + esc_html("File {$composerJsonFile} does not exist or is not readable."), + ); + } + + $content = (string) file_get_contents($composerJsonFile); + + /** @var ComposerData $composerJsonData */ + $composerJsonData = json_decode($content, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new \Exception( + esc_html("Error reading file {$composerJsonFile}: " . json_last_error_msg()), + ); + } + + return $composerJsonData; } } diff --git a/lib/packages/Inpsyde/Modularity/Properties/PluginProperties.php b/lib/packages/Inpsyde/Modularity/Properties/PluginProperties.php index f3d3f5485..2ae20c806 100644 --- a/lib/packages/Inpsyde/Modularity/Properties/PluginProperties.php +++ b/lib/packages/Inpsyde/Modularity/Properties/PluginProperties.php @@ -4,24 +4,14 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties; -/** - * Class PluginProperties - * - * @package WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties - * - * @psalm-suppress PossiblyFalseArgument, InvalidArgument - */ class PluginProperties extends BaseProperties { - /** - * Custom properties for Plugins. - */ + // Custom properties for Plugins public const PROP_NETWORK = 'network'; + public const PROP_REQUIRES_PLUGINS = 'requiresPlugins'; + /** - * Available methods of Properties::__call() - * from plugin headers. - * - * @link https://developer.wordpress.org/reference/functions/get_plugin_data/ + * @see https://developer.wordpress.org/reference/functions/get_plugin_data/ */ protected const HEADERS = [ self::PROP_AUTHOR => 'Author', @@ -37,36 +27,17 @@ class PluginProperties extends BaseProperties // additional headers self::PROP_NETWORK => 'Network', + self::PROP_REQUIRES_PLUGINS => 'RequiresPlugins', ]; - /** - * @var string - */ - private $pluginMainFile; - - /** - * @var string - */ - private $pluginBaseName; - - /** - * @var bool|null - */ - protected $isMu; - - /** - * @var bool|null - */ - protected $isActive; - - /** - * @var bool|null - */ - protected $isNetworkActive; + private string $pluginMainFile; + private string $pluginBaseName; + protected ?bool $isMu = null; + protected ?bool $isActive = null; + protected ?bool $isNetworkActive = null; /** * @param string $pluginMainFile - * * @return PluginProperties */ public static function new(string $pluginMainFile): PluginProperties @@ -75,8 +46,6 @@ class PluginProperties extends BaseProperties } /** - * PluginProperties constructor. - * * @param string $pluginMainFile */ protected function __construct(string $pluginMainFile) @@ -85,7 +54,11 @@ class PluginProperties extends BaseProperties require_once ABSPATH . 'wp-admin/includes/plugin.php'; } - $pluginData = get_plugin_data($pluginMainFile); + // $markup = false, to avoid an incorrect early wptexturize call. + // $translate = false, to avoid loading translations too early + // @see https://core.trac.wordpress.org/ticket/49965 + // @see https://core.trac.wordpress.org/ticket/34114 + $pluginData = (array) get_plugin_data($pluginMainFile, false, false); $properties = Properties::DEFAULT_PROPERTIES; // Map pluginData to internal structure. @@ -93,6 +66,7 @@ class PluginProperties extends BaseProperties $properties[$key] = $pluginData[$pluginDataKey] ?? ''; unset($pluginData[$pluginDataKey]); } + /** @var array $properties */ $properties = array_merge($properties, $pluginData); $this->pluginMainFile = wp_normalize_path($pluginMainFile); @@ -119,14 +93,22 @@ class PluginProperties extends BaseProperties /** * @return bool - * - * @psalm-suppress PossiblyFalseArgument */ public function network(): bool { return (bool) $this->get(self::PROP_NETWORK, false); } + /** + * @return string[] + */ + public function requiresPlugins(): array + { + $value = $this->get(self::PROP_REQUIRES_PLUGINS); + + return $value && is_string($value) ? explode(',', $value) : []; + } + /** * @return bool */ @@ -163,10 +145,6 @@ class PluginProperties extends BaseProperties public function isMuPlugin(): bool { if ($this->isMu === null) { - /** - * @psalm-suppress UndefinedConstant - * @psalm-suppress MixedArgument - */ $muPluginDir = wp_normalize_path(WPMU_PLUGIN_DIR); $this->isMu = strpos($this->pluginMainFile, $muPluginDir) === 0; } diff --git a/lib/packages/Inpsyde/Modularity/Properties/Properties.php b/lib/packages/Inpsyde/Modularity/Properties/Properties.php index 8663d6883..df44fe994 100644 --- a/lib/packages/Inpsyde/Modularity/Properties/Properties.php +++ b/lib/packages/Inpsyde/Modularity/Properties/Properties.php @@ -17,9 +17,7 @@ interface Properties public const PROP_REQUIRES_WP = 'requiresWp'; public const PROP_REQUIRES_PHP = 'requiresPhp'; public const PROP_TAGS = 'tags'; - /** - * @var array - */ + public const DEFAULT_PROPERTIES = [ self::PROP_AUTHOR => '', self::PROP_AUTHOR_URI => '', @@ -36,15 +34,13 @@ interface Properties /** * @param string $key - * @param null $default - * + * @param mixed $default * @return mixed */ public function get(string $key, $default = null); /** * @param string $key - * * @return bool */ public function has(string $key): bool; @@ -103,6 +99,7 @@ interface Properties /** * The home page of the plugin, theme or library. + * * @return string */ public function uri(): string; @@ -122,7 +119,7 @@ interface Properties /** * Optional. Specify the minimum required PHP version. * - * @return string + * @return string|null */ public function requiresPhp(): ?string; @@ -130,10 +127,10 @@ interface Properties * Optional. Currently, only available for Theme and Library. * Plugins do not have support for "tags"/"keywords" in header. * - * @link https://developer.wordpress.org/reference/classes/wp_theme/#properties - * @link https://getcomposer.org/doc/04-schema.md#keywords + * @return string[] * - * @return array + * @see https://developer.wordpress.org/reference/classes/wp_theme/#properties + * @see https://getcomposer.org/doc/04-schema.md#keywords */ public function tags(): array; } diff --git a/lib/packages/Inpsyde/Modularity/Properties/ThemeProperties.php b/lib/packages/Inpsyde/Modularity/Properties/ThemeProperties.php index 24a464caa..353e9ac9a 100644 --- a/lib/packages/Inpsyde/Modularity/Properties/ThemeProperties.php +++ b/lib/packages/Inpsyde/Modularity/Properties/ThemeProperties.php @@ -4,26 +4,12 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties; -/** - * Class ThemeProperties - * - * @package WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties - * - * @psalm-suppress PossiblyFalseArgument, InvalidArgument - */ class ThemeProperties extends BaseProperties { - /** - * Additional properties specific for themes. - */ public const PROP_STATUS = 'status'; public const PROP_TEMPLATE = 'template'; - /** - * Available methods of Properties::__call() - * from theme headers. - * - * @link https://developer.wordpress.org/reference/classes/wp_theme/ - */ + + /** @see https://developer.wordpress.org/reference/classes/wp_theme/ */ protected const HEADERS = [ self::PROP_AUTHOR => 'Author', self::PROP_AUTHOR_URI => 'AuthorURI', @@ -53,8 +39,6 @@ class ThemeProperties extends BaseProperties } /** - * ThemeProperties constructor. - * * @param string $themeDirectory */ protected function __construct(string $themeDirectory) @@ -67,13 +51,15 @@ class ThemeProperties extends BaseProperties $properties = Properties::DEFAULT_PROPERTIES; foreach (self::HEADERS as $key => $themeKey) { - /** @psalm-suppress DocblockTypeContradiction */ - $properties[$key] = $theme->get($themeKey) ?? ''; + $property = $theme->get($themeKey); + if (is_string($property) || is_array($property)) { + $properties[$key] = $property; + } } $baseName = $theme->get_stylesheet(); $basePath = $theme->get_stylesheet_directory(); - $baseUrl = (string) trailingslashit($theme->get_stylesheet_directory_uri()); + $baseUrl = trailingslashit($theme->get_stylesheet_directory_uri()); parent::__construct( $baseName, @@ -84,8 +70,6 @@ class ThemeProperties extends BaseProperties } /** - * If the theme is published. - * * @return string */ public function status(): string @@ -93,6 +77,9 @@ class ThemeProperties extends BaseProperties return (string) $this->get(self::PROP_STATUS); } + /** + * @return string + */ public function template(): string { return (string) $this->get(self::PROP_TEMPLATE); @@ -120,7 +107,7 @@ class ThemeProperties extends BaseProperties public function parentThemeProperties(): ?ThemeProperties { $template = $this->template(); - if (!$template) { + if ($template === '') { return null; } diff --git a/lib/packages/Psr/Container/ContainerExceptionInterface.php b/lib/packages/Psr/Container/ContainerExceptionInterface.php index 78ee05e4a..1215883b7 100644 --- a/lib/packages/Psr/Container/ContainerExceptionInterface.php +++ b/lib/packages/Psr/Container/ContainerExceptionInterface.php @@ -2,9 +2,11 @@ namespace WooCommerce\PayPalCommerce\Vendor\Psr\Container; +use Throwable; + /** * Base interface representing a generic exception in a container. */ -interface ContainerExceptionInterface +interface ContainerExceptionInterface extends Throwable { } diff --git a/src/services.php b/src/services.php index 562d448be..4d076fb78 100644 --- a/src/services.php +++ b/src/services.php @@ -10,10 +10,10 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce; use Dhii\Versions\StringVersionFactory; -use Inpsyde\Modularity\Properties\Properties; use WooCommerce\PayPalCommerce\Http\RedirectorInterface; use WooCommerce\PayPalCommerce\Http\WpRedirector; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Package; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties\Properties; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WpOop\WordPress\Plugin\PluginInterface; From 95239b51ae1ea41e8d02b7cdb685c8697154079a Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 4 Jul 2025 22:17:15 +0400 Subject: [PATCH 16/63] Fix Psalm --- src/services.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services.php b/src/services.php index 4d076fb78..e89a6bc39 100644 --- a/src/services.php +++ b/src/services.php @@ -42,6 +42,7 @@ return array( /** @var Properties $properties */ $properties = $container->get( Package::PROPERTIES ); + /** @psalm-suppress MissingClosureParamType */ return $properties->pluginMainFile(); }, ); From 579f9989c0433416a19b16574c9c0013f53d27ba Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 4 Jul 2025 22:19:39 +0400 Subject: [PATCH 17/63] Fix Psalm --- src/services.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services.php b/src/services.php index e89a6bc39..c25c4f7ed 100644 --- a/src/services.php +++ b/src/services.php @@ -42,7 +42,7 @@ return array( /** @var Properties $properties */ $properties = $container->get( Package::PROPERTIES ); - /** @psalm-suppress MissingClosureParamType */ + /** @psalm-suppress UndefinedInterfaceMethod */ return $properties->pluginMainFile(); }, ); From b0e9d8fb2c372765143eb22b3be6a7dd27a00f1c Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 7 Jul 2025 18:47:58 +0400 Subject: [PATCH 18/63] Update the lock file --- composer.lock | 473 ++++++++++++++++++++++++++------------------------ 1 file changed, 246 insertions(+), 227 deletions(-) diff --git a/composer.lock b/composer.lock index 908b9e94c..05ea6e974 100644 --- a/composer.lock +++ b/composer.lock @@ -438,26 +438,26 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.30.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -498,7 +498,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" }, "funding": [ { @@ -514,7 +514,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2025-01-02T08:10:11+00:00" }, { "name": "wikimedia/composer-merge-plugin", @@ -545,10 +545,10 @@ }, "type": "composer-plugin", "extra": { + "class": "Wikimedia\\Composer\\Merge\\V2\\MergePlugin", "branch-alias": { "dev-master": "2.x-dev" - }, - "class": "Wikimedia\\Composer\\Merge\\V2\\MergePlugin" + } }, "autoload": { "psr-4": { @@ -788,20 +788,20 @@ }, { "name": "antecedent/patchwork", - "version": "2.1.28", + "version": "2.2.1", "source": { "type": "git", "url": "https://github.com/antecedent/patchwork.git", - "reference": "6b30aff81ebadf0f2feb9268d3e08385cebcc08d" + "reference": "1bf183a3e1bd094f231a2128b9ecc5363c269245" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/antecedent/patchwork/zipball/6b30aff81ebadf0f2feb9268d3e08385cebcc08d", - "reference": "6b30aff81ebadf0f2feb9268d3e08385cebcc08d", + "url": "https://api.github.com/repos/antecedent/patchwork/zipball/1bf183a3e1bd094f231a2128b9ecc5363c269245", + "reference": "1bf183a3e1bd094f231a2128b9ecc5363c269245", "shasum": "" }, "require": { - "php": ">=5.4.0" + "php": ">=7.1.0" }, "require-dev": { "phpunit/phpunit": ">=4" @@ -830,22 +830,22 @@ ], "support": { "issues": "https://github.com/antecedent/patchwork/issues", - "source": "https://github.com/antecedent/patchwork/tree/2.1.28" + "source": "https://github.com/antecedent/patchwork/tree/2.2.1" }, - "time": "2024-02-06T09:26:11+00:00" + "time": "2024-12-11T10:19:54+00:00" }, { "name": "brain/monkey", - "version": "2.6.1", + "version": "2.6.2", "source": { "type": "git", "url": "https://github.com/Brain-WP/BrainMonkey.git", - "reference": "a31c84515bb0d49be9310f52ef1733980ea8ffbb" + "reference": "d95a9d895352c30f47604ad1b825ab8fa9d1a373" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Brain-WP/BrainMonkey/zipball/a31c84515bb0d49be9310f52ef1733980ea8ffbb", - "reference": "a31c84515bb0d49be9310f52ef1733980ea8ffbb", + "url": "https://api.github.com/repos/Brain-WP/BrainMonkey/zipball/d95a9d895352c30f47604ad1b825ab8fa9d1a373", + "reference": "d95a9d895352c30f47604ad1b825ab8fa9d1a373", "shasum": "" }, "require": { @@ -861,8 +861,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-version/1": "1.x-dev", - "dev-master": "2.0.x-dev" + "dev-master": "2.x-dev", + "dev-version/1": "1.x-dev" } }, "autoload": { @@ -902,7 +902,7 @@ "issues": "https://github.com/Brain-WP/BrainMonkey/issues", "source": "https://github.com/Brain-WP/BrainMonkey" }, - "time": "2021-11-11T15:53:55+00:00" + "time": "2024-08-29T20:15:04+00:00" }, { "name": "coenjacobs/mozart", @@ -1037,16 +1037,16 @@ }, { "name": "composer/pcre", - "version": "3.3.1", + "version": "3.3.2", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "63aaeac21d7e775ff9bc9d45021e1745c97521c4" + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/63aaeac21d7e775ff9bc9d45021e1745c97521c4", - "reference": "63aaeac21d7e775ff9bc9d45021e1745c97521c4", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", "shasum": "" }, "require": { @@ -1056,19 +1056,19 @@ "phpstan/phpstan": "<1.11.10" }, "require-dev": { - "phpstan/phpstan": "^1.11.10", - "phpstan/phpstan-strict-rules": "^1.1", + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", "phpunit/phpunit": "^8 || ^9" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - }, "phpstan": { "includes": [ "extension.neon" ] + }, + "branch-alias": { + "dev-main": "3.x-dev" } }, "autoload": { @@ -1096,7 +1096,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.3.1" + "source": "https://github.com/composer/pcre/tree/3.3.2" }, "funding": [ { @@ -1112,28 +1112,28 @@ "type": "tidelift" } ], - "time": "2024-08-27T18:44:43+00:00" + "time": "2024-11-12T16:29:46+00:00" }, { "name": "composer/semver", - "version": "3.4.2", + "version": "3.4.3", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6" + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/c51258e759afdb17f1fd1fe83bc12baaef6309d6", - "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^1.4", - "symfony/phpunit-bridge": "^4.2 || ^5" + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" }, "type": "library", "extra": { @@ -1177,7 +1177,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.2" + "source": "https://github.com/composer/semver/tree/3.4.3" }, "funding": [ { @@ -1193,7 +1193,7 @@ "type": "tidelift" } ], - "time": "2024-07-12T11:35:52+00:00" + "time": "2024-09-19T14:15:21+00:00" }, { "name": "composer/xdebug-handler", @@ -1490,29 +1490,30 @@ }, { "name": "doctrine/deprecations", - "version": "1.1.3", + "version": "1.1.5", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab" + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", - "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=13" + }, "require-dev": { - "doctrine/coding-standard": "^9", - "phpstan/phpstan": "1.4.10 || 1.10.15", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psalm/plugin-phpunit": "0.18.4", - "psr/log": "^1 || ^2 || ^3", - "vimeo/psalm": "4.30.0 || 5.12.0" + "doctrine/coding-standard": "^9 || ^12 || ^13", + "phpstan/phpstan": "1.4.10 || 2.1.11", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", + "psr/log": "^1 || ^2 || ^3" }, "suggest": { "psr/log": "Allows logging deprecations via PSR-3 logger implementation" @@ -1520,7 +1521,7 @@ "type": "library", "autoload": { "psr-4": { - "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + "Doctrine\\Deprecations\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1531,9 +1532,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.3" + "source": "https://github.com/doctrine/deprecations/tree/1.1.5" }, - "time": "2024-01-30T19:34:25+00:00" + "time": "2025-04-07T20:06:18+00:00" }, { "name": "doctrine/instantiator", @@ -1652,16 +1653,16 @@ }, { "name": "felixfbecker/language-server-protocol", - "version": "v1.5.2", + "version": "v1.5.3", "source": { "type": "git", "url": "https://github.com/felixfbecker/php-language-server-protocol.git", - "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842" + "reference": "a9e113dbc7d849e35b8776da39edaf4313b7b6c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/6e82196ffd7c62f7794d778ca52b69feec9f2842", - "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/a9e113dbc7d849e35b8776da39edaf4313b7b6c9", + "reference": "a9e113dbc7d849e35b8776da39edaf4313b7b6c9", "shasum": "" }, "require": { @@ -1702,9 +1703,9 @@ ], "support": { "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", - "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.2" + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.3" }, - "time": "2022-03-02T22:36:06+00:00" + "time": "2024-04-30T00:40:11+00:00" }, { "name": "graham-campbell/result-type", @@ -1770,20 +1771,20 @@ }, { "name": "hamcrest/hamcrest-php", - "version": "v2.0.1", + "version": "v2.1.1", "source": { "type": "git", "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", - "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", "shasum": "" }, "require": { - "php": "^5.3|^7.0|^8.0" + "php": "^7.4|^8.0" }, "replace": { "cordoval/hamcrest-php": "*", @@ -1791,8 +1792,8 @@ "kodova/hamcrest-php": "*" }, "require-dev": { - "phpunit/php-file-iterator": "^1.4 || ^2.0", - "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" + "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" }, "type": "library", "extra": { @@ -1815,9 +1816,9 @@ ], "support": { "issues": "https://github.com/hamcrest/hamcrest-php/issues", - "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" }, - "time": "2020-07-09T08:09:16+00:00" + "time": "2025-04-30T06:54:44+00:00" }, { "name": "inpsyde/composer-assets-compiler", @@ -1850,10 +1851,10 @@ "extra": { "class": "Inpsyde\\AssetsCompiler\\Composer\\Plugin", "branch-alias": { - "dev-master": "2.x-dev", "dev-v1.x": "1.x-dev", "dev-v2.x": "2.x-dev", - "dev-v3.x": "3.x-dev" + "dev-v3.x": "3.x-dev", + "dev-master": "2.x-dev" } }, "autoload": { @@ -2180,16 +2181,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.12.0", + "version": "1.13.3", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" + "reference": "faed855a7b5f4d4637717c2b3863e277116beb36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36", + "reference": "faed855a7b5f4d4637717c2b3863e277116beb36", "shasum": "" }, "require": { @@ -2228,7 +2229,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.3" }, "funding": [ { @@ -2236,20 +2237,20 @@ "type": "tidelift" } ], - "time": "2024-06-12T14:39:25+00:00" + "time": "2025-07-05T12:25:42+00:00" }, { "name": "netresearch/jsonmapper", - "version": "v4.4.1", + "version": "v4.5.0", "source": { "type": "git", "url": "https://github.com/cweiske/jsonmapper.git", - "reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0" + "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/132c75c7dd83e45353ebb9c6c9f591952995bbf0", - "reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8e76efb98ee8b6afc54687045e1b8dba55ac76e5", + "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5", "shasum": "" }, "require": { @@ -2285,22 +2286,22 @@ "support": { "email": "cweiske@cweiske.de", "issues": "https://github.com/cweiske/jsonmapper/issues", - "source": "https://github.com/cweiske/jsonmapper/tree/v4.4.1" + "source": "https://github.com/cweiske/jsonmapper/tree/v4.5.0" }, - "time": "2024-01-31T06:18:54+00:00" + "time": "2024-09-08T10:13:13+00:00" }, { "name": "nikic/php-parser", - "version": "v4.19.1", + "version": "v4.19.4", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b" + "reference": "715f4d25e225bc47b293a8b997fe6ce99bf987d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4e1b88d21c69391150ace211e9eaf05810858d0b", - "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/715f4d25e225bc47b293a8b997fe6ce99bf987d2", + "reference": "715f4d25e225bc47b293a8b997fe6ce99bf987d2", "shasum": "" }, "require": { @@ -2309,7 +2310,7 @@ }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, "bin": [ "bin/php-parse" @@ -2341,9 +2342,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.4" }, - "time": "2024-03-17T08:10:35+00:00" + "time": "2024-09-29T15:01:53+00:00" }, { "name": "openlss/lib-array2xml", @@ -2743,21 +2744,22 @@ }, { "name": "phpcompatibility/phpcompatibility-wp", - "version": "2.1.5", + "version": "2.1.7", "source": { "type": "git", "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git", - "reference": "01c1ff2704a58e46f0cb1ca9d06aee07b3589082" + "reference": "5bfbbfbabb3df2b9a83e601de9153e4a7111962c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/01c1ff2704a58e46f0cb1ca9d06aee07b3589082", - "reference": "01c1ff2704a58e46f0cb1ca9d06aee07b3589082", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/5bfbbfbabb3df2b9a83e601de9153e4a7111962c", + "reference": "5bfbbfbabb3df2b9a83e601de9153e4a7111962c", "shasum": "" }, "require": { "phpcompatibility/php-compatibility": "^9.0", - "phpcompatibility/phpcompatibility-paragonie": "^1.0" + "phpcompatibility/phpcompatibility-paragonie": "^1.0", + "squizlabs/php_codesniffer": "^3.3" }, "require-dev": { "dealerdirect/phpcodesniffer-composer-installer": "^1.0" @@ -2807,9 +2809,13 @@ { "url": "https://opencollective.com/php_codesniffer", "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcompatibility", + "type": "thanks_dev" } ], - "time": "2024-04-24T21:37:59+00:00" + "time": "2025-05-12T16:38:37+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -2866,16 +2872,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.4.1", + "version": "5.6.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c" + "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", - "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/92dde6a5919e34835c506ac8c523ef095a95ed62", + "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62", "shasum": "" }, "require": { @@ -2884,17 +2890,17 @@ "php": "^7.4 || ^8.0", "phpdocumentor/reflection-common": "^2.2", "phpdocumentor/type-resolver": "^1.7", - "phpstan/phpdoc-parser": "^1.7", + "phpstan/phpdoc-parser": "^1.7|^2.0", "webmozart/assert": "^1.9.1" }, "require-dev": { - "mockery/mockery": "~1.3.5", + "mockery/mockery": "~1.3.5 || ~1.6.0", "phpstan/extension-installer": "^1.1", "phpstan/phpstan": "^1.8", "phpstan/phpstan-mockery": "^1.1", "phpstan/phpstan-webmozart-assert": "^1.2", "phpunit/phpunit": "^9.5", - "vimeo/psalm": "^5.13" + "psalm/phar": "^5.26" }, "type": "library", "extra": { @@ -2924,29 +2930,29 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.4.1" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.2" }, - "time": "2024-05-21T05:55:05+00:00" + "time": "2025-04-13T19:20:35+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.8.2", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "153ae662783729388a584b4361f2545e4d841e3c" + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", - "reference": "153ae662783729388a584b4361f2545e4d841e3c", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", "shasum": "" }, "require": { "doctrine/deprecations": "^1.0", "php": "^7.3 || ^8.0", "phpdocumentor/reflection-common": "^2.0", - "phpstan/phpdoc-parser": "^1.13" + "phpstan/phpdoc-parser": "^1.18|^2.0" }, "require-dev": { "ext-tokenizer": "*", @@ -2982,9 +2988,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0" }, - "time": "2024-02-23T11:10:43+00:00" + "time": "2024-11-09T15:12:26+00:00" }, { "name": "phpoption/phpoption", @@ -3063,30 +3069,30 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.30.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "5ceb0e384997db59f38774bf79c2a6134252c08f" + "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/5ceb0e384997db59f38774bf79c2a6134252c08f", - "reference": "5ceb0e384997db59f38774bf79c2a6134252c08f", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", + "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": "^7.4 || ^8.0" }, "require-dev": { "doctrine/annotations": "^2.0", - "nikic/php-parser": "^4.15", + "nikic/php-parser": "^5.3.0", "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^1.5", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/phpunit": "^9.5", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", "symfony/process": "^5.2" }, "type": "library", @@ -3104,9 +3110,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.30.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.1.0" }, - "time": "2024-08-29T09:54:52+00:00" + "time": "2025-02-19T13:28:12+00:00" }, { "name": "phpunit/php-code-coverage", @@ -3429,16 +3435,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.20", + "version": "9.6.23", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "49d7820565836236411f5dc002d16dd689cde42f" + "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/49d7820565836236411f5dc002d16dd689cde42f", - "reference": "49d7820565836236411f5dc002d16dd689cde42f", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/43d2cb18d0675c38bd44982a5d1d88f6d53d8d95", + "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95", "shasum": "" }, "require": { @@ -3449,11 +3455,11 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.0", + "myclabs/deep-copy": "^1.13.1", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.31", + "phpunit/php-code-coverage": "^9.2.32", "phpunit/php-file-iterator": "^3.0.6", "phpunit/php-invoker": "^3.1.1", "phpunit/php-text-template": "^2.0.4", @@ -3512,7 +3518,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.20" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.23" }, "funding": [ { @@ -3523,12 +3529,20 @@ "url": "https://github.com/sebastianbergmann", "type": "github" }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, { "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", "type": "tidelift" } ], - "time": "2024-07-10T11:45:39+00:00" + "time": "2025-05-02T06:40:34+00:00" }, { "name": "sebastian/cli-parser", @@ -4495,16 +4509,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.10.2", + "version": "3.13.2", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017" + "reference": "5b5e3821314f947dd040c70f7992a64eac89025c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/86e5f5dd9a840c46810ebe5ff1885581c42a3017", - "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/5b5e3821314f947dd040c70f7992a64eac89025c", + "reference": "5b5e3821314f947dd040c70f7992a64eac89025c", "shasum": "" }, "require": { @@ -4569,22 +4583,26 @@ { "url": "https://opencollective.com/php_codesniffer", "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" } ], - "time": "2024-07-21T23:26:44+00:00" + "time": "2025-06-17T22:17:01+00:00" }, { "name": "symfony/console", - "version": "v5.4.43", + "version": "v5.4.47", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "e86f8554de667c16dde8aeb89a3990cfde924df9" + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/e86f8554de667c16dde8aeb89a3990cfde924df9", - "reference": "e86f8554de667c16dde8aeb89a3990cfde924df9", + "url": "https://api.github.com/repos/symfony/console/zipball/c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", "shasum": "" }, "require": { @@ -4654,7 +4672,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.43" + "source": "https://github.com/symfony/console/tree/v5.4.47" }, "funding": [ { @@ -4670,20 +4688,20 @@ "type": "tidelift" } ], - "time": "2024-08-13T16:31:56+00:00" + "time": "2024-11-06T11:30:55+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v2.5.3", + "version": "v2.5.4", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "80d075412b557d41002320b96a096ca65aa2c98d" + "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/80d075412b557d41002320b96a096ca65aa2c98d", - "reference": "80d075412b557d41002320b96a096ca65aa2c98d", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/605389f2a7e5625f273b53960dc46aeaf9c62918", + "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918", "shasum": "" }, "require": { @@ -4691,12 +4709,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -4721,7 +4739,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.3" + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.4" }, "funding": [ { @@ -4737,7 +4755,7 @@ "type": "tidelift" } ], - "time": "2023-01-24T14:02:46+00:00" + "time": "2024-09-25T14:11:13+00:00" }, { "name": "symfony/finder", @@ -4804,20 +4822,20 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.30.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -4828,8 +4846,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -4863,7 +4881,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" }, "funding": [ { @@ -4879,24 +4897,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.30.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -4904,8 +4922,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -4941,7 +4959,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" }, "funding": [ { @@ -4957,24 +4975,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.30.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -4982,8 +5000,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -5022,7 +5040,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" }, "funding": [ { @@ -5038,24 +5056,25 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.30.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { - "php": ">=7.1" + "ext-iconv": "*", + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -5066,8 +5085,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -5102,7 +5121,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" }, "funding": [ { @@ -5118,30 +5137,30 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.30.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "ec444d3f3f6505bb28d11afa41e75faadebc10a1" + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/ec444d3f3f6505bb28d11afa41e75faadebc10a1", - "reference": "ec444d3f3f6505bb28d11afa41e75faadebc10a1", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -5178,7 +5197,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.32.0" }, "funding": [ { @@ -5194,20 +5213,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/service-contracts", - "version": "v2.5.3", + "version": "v2.5.4", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "a2329596ddc8fd568900e3fc76cba42489ecc7f3" + "reference": "f37b419f7aea2e9abf10abd261832cace12e3300" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/a2329596ddc8fd568900e3fc76cba42489ecc7f3", - "reference": "a2329596ddc8fd568900e3fc76cba42489ecc7f3", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f37b419f7aea2e9abf10abd261832cace12e3300", + "reference": "f37b419f7aea2e9abf10abd261832cace12e3300", "shasum": "" }, "require": { @@ -5223,12 +5242,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -5261,7 +5280,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.3" + "source": "https://github.com/symfony/service-contracts/tree/v2.5.4" }, "funding": [ { @@ -5277,20 +5296,20 @@ "type": "tidelift" } ], - "time": "2023-04-21T15:04:16+00:00" + "time": "2024-09-25T14:11:13+00:00" }, { "name": "symfony/string", - "version": "v5.4.43", + "version": "v5.4.47", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "8be1d484951ff5ca995eaf8edcbcb8b9a5888450" + "reference": "136ca7d72f72b599f2631aca474a4f8e26719799" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/8be1d484951ff5ca995eaf8edcbcb8b9a5888450", - "reference": "8be1d484951ff5ca995eaf8edcbcb8b9a5888450", + "url": "https://api.github.com/repos/symfony/string/zipball/136ca7d72f72b599f2631aca474a4f8e26719799", + "reference": "136ca7d72f72b599f2631aca474a4f8e26719799", "shasum": "" }, "require": { @@ -5347,7 +5366,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.43" + "source": "https://github.com/symfony/string/tree/v5.4.47" }, "funding": [ { @@ -5363,7 +5382,7 @@ "type": "tidelift" } ], - "time": "2024-08-01T10:24:28+00:00" + "time": "2024-11-10T20:33:58+00:00" }, { "name": "theseer/tokenizer", @@ -5487,10 +5506,10 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.x-dev", - "dev-3.x": "3.x-dev", + "dev-1.x": "1.x-dev", "dev-2.x": "2.x-dev", - "dev-1.x": "1.x-dev" + "dev-3.x": "3.x-dev", + "dev-master": "4.x-dev" } }, "autoload": { @@ -5525,16 +5544,16 @@ }, { "name": "vlucas/phpdotenv", - "version": "v5.6.1", + "version": "v5.6.2", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2" + "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2", - "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/24ac4c74f91ee2c193fa1aaa5c249cb0822809af", + "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af", "shasum": "" }, "require": { @@ -5593,7 +5612,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.2" }, "funding": [ { @@ -5605,7 +5624,7 @@ "type": "tidelift" } ], - "time": "2024-07-20T21:52:34+00:00" + "time": "2025-04-30T23:37:27+00:00" }, { "name": "webmozart/assert", From 77443557518d26fb84fec5ea512aadcdd87fd645 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Fri, 11 Jul 2025 17:21:11 +0200 Subject: [PATCH 19/63] Add transaction info call when cart or checkout update event is triggered --- modules/ppcp-applepay/resources/js/ApplepayButton.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 155ab5d7b..cfb2d1d59 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -334,12 +334,22 @@ class ApplePayButton extends PaymentButton { this.checkEligibility(); } - reinit() { + async reinit() { // Missing (invalid) configuration indicates, that the first `init()` call did not happen yet. if ( ! this.validateConfiguration( true ) ) { return; } + // Ensures transaction info is updated in Apple Pay session when cart or checkout update events are triggered. + await this.contextHandler + .transactionInfo() + .then( ( transactionInfo ) => { + this.transactionInfo = transactionInfo; + } ) + .catch( ( error ) => { + console.error( 'Failed to get transaction info:', error ); + } ); + super.reinit(); this.init(); From fb35981e49dd1a39d28128805c435e372914bf47 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Fri, 11 Jul 2025 17:30:33 +0200 Subject: [PATCH 20/63] Add transaction info call when cart or checkout update event is triggered --- modules/ppcp-applepay/resources/js/ApplepayButton.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index cfb2d1d59..f0e5a8d7a 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -340,7 +340,7 @@ class ApplePayButton extends PaymentButton { return; } - // Ensures transaction info is updated in Apple Pay session when cart or checkout update events are triggered. + // Ensures transaction info is updated when cart or checkout update events are triggered. await this.contextHandler .transactionInfo() .then( ( transactionInfo ) => { From bd9343c893a7d26faf6c5de954ccc49ff539fea3 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Mon, 14 Jul 2025 19:31:01 +0200 Subject: [PATCH 21/63] =?UTF-8?q?=F0=9F=90=9B=20Fix=20empty=20description?= =?UTF-8?q?=20handling=20and=20logo=20display=20for=20PayPal=20gateway?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Override `get_description()` to properly handle saved empty descriptions using `array_key_exists()` instead of `get_option()` fallbacks - Fix issue where empty descriptions reverted to defaults - Utilize the `woocommerce_paypal_payments_paypal_gateway_icon` filter to handle PayPal logo display correctly --- .../src/Endpoint/CreateOrderEndpoint.php | 4 ++-- .../Data/Definition/PaymentMethodsDefinition.php | 4 ++-- modules/ppcp-settings/src/SettingsModule.php | 11 +++++++++++ .../ppcp-wc-gateway/src/Gateway/PayPalGateway.php | 15 +++++++++++++++ 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php index 8d7682be0..78ef86fd2 100644 --- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php @@ -254,8 +254,8 @@ class CreateOrderEndpoint implements EndpointInterface { $this->pay_now_contexts = $pay_now_contexts; $this->handle_shipping_in_paypal = $handle_shipping_in_paypal; $this->server_side_shipping_callback_enabled = $server_side_shipping_callback_enabled; - $this->funding_sources_without_redirect = $funding_sources_without_redirect; - $this->logger = $logger; + $this->funding_sources_without_redirect = $funding_sources_without_redirect; + $this->logger = $logger; } /** diff --git a/modules/ppcp-settings/src/Data/Definition/PaymentMethodsDefinition.php b/modules/ppcp-settings/src/Data/Definition/PaymentMethodsDefinition.php index 456317f45..0feba26af 100644 --- a/modules/ppcp-settings/src/Data/Definition/PaymentMethodsDefinition.php +++ b/modules/ppcp-settings/src/Data/Definition/PaymentMethodsDefinition.php @@ -136,7 +136,7 @@ class PaymentMethodsDefinition { $gateway = $this->wc_gateways[ $gateway_id ] ?? null; $gateway_title = $gateway ? $gateway->get_title() : $title; - $gateway_description = $gateway ? $gateway->get_description() : $description; + $gateway_description = $gateway->settings['description'] ?? $description; $enabled = $this->settings->is_method_enabled( $gateway_id ); $config = array( 'id' => $gateway_id, @@ -159,7 +159,7 @@ class PaymentMethodsDefinition { ), 'checkoutPageDescription' => array( 'type' => 'text', - 'default' => $gateway ? $gateway->get_description() : '', + 'default' => $gateway_description, 'label' => __( 'Checkout page description', 'woocommerce-paypal-payments' ), ), ), diff --git a/modules/ppcp-settings/src/SettingsModule.php b/modules/ppcp-settings/src/SettingsModule.php index 605b182f8..d144b6bef 100644 --- a/modules/ppcp-settings/src/SettingsModule.php +++ b/modules/ppcp-settings/src/SettingsModule.php @@ -527,6 +527,17 @@ class SettingsModule implements ServiceModule, ExecutableModule { 2 ); + add_filter( + 'woocommerce_paypal_payments_paypal_gateway_icon', + function ( string $icon_url ) use ( $container ) { + $payment_settings = $container->get( 'settings.data.payment' ); + assert( $payment_settings instanceof PaymentSettings ); + + // If "Show logo" is disabled, return an empty string to hide the icon. + return $payment_settings->get_paypal_show_logo() ? $icon_url : ''; + } + ); + add_filter( 'woocommerce_paypal_payments_card_button_gateway_should_register_gateway', '__return_true' ); add_filter( diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php index 60a350c8b..2c70c7baf 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php @@ -351,6 +351,21 @@ class PayPalGateway extends \WC_Payment_Gateway { return parent::get_title(); } + /** + * Return the gateway's description. + * + * @return string + */ + public function get_description() { + $gateway_settings = get_option( $this->get_option_key(), array() ); + + if ( array_key_exists( 'description', $gateway_settings ) ) { + return $gateway_settings['description']; + } + + return $this->description; + } + /** * Whether the Gateway needs to be setup. * From 3ba91e8f443b82261fde6fc134a4c48ed191a5f3 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Mon, 14 Jul 2025 20:30:17 +0200 Subject: [PATCH 22/63] =?UTF-8?q?=F0=9F=90=9B=20Fix=20the=20empty=20descri?= =?UTF-8?q?ption=20for=20the=20block=20checkout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-blocks/src/PayPalPaymentMethod.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-blocks/src/PayPalPaymentMethod.php b/modules/ppcp-blocks/src/PayPalPaymentMethod.php index e03bd57d3..b4c970524 100644 --- a/modules/ppcp-blocks/src/PayPalPaymentMethod.php +++ b/modules/ppcp-blocks/src/PayPalPaymentMethod.php @@ -260,7 +260,7 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType { 'src' => $this->gateway->icon, ), ), - 'description' => $this->gateway->description, + 'description' => $this->gateway->get_description(), 'smartButtonsEnabled' => $smart_buttons_enabled, 'placeOrderEnabled' => $place_order_enabled, 'fundingSource' => $this->session_handler->funding_source(), From fadd2f5b40f21f7f98ae5781ed87fa5436930c95 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Tue, 15 Jul 2025 19:09:22 +0400 Subject: [PATCH 23/63] Disable the card button if BCDC is disabled --- modules/ppcp-button/src/Helper/DisabledFundingSources.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-button/src/Helper/DisabledFundingSources.php b/modules/ppcp-button/src/Helper/DisabledFundingSources.php index 824e44abf..2d44cbd5c 100644 --- a/modules/ppcp-button/src/Helper/DisabledFundingSources.php +++ b/modules/ppcp-button/src/Helper/DisabledFundingSources.php @@ -159,7 +159,7 @@ class DisabledFundingSources { return $disable_funding; } - if ( ! is_checkout() || $this->dcc_configuration->use_acdc() ) { + if ( ! is_checkout() || $this->dcc_configuration->use_acdc() || ! $this->dcc_configuration->is_bcdc_enabled() ) { // Non-checkout pages, or ACDC capability: Don't load card button. $disable_funding[] = 'card'; } From 13f8a63e8ff44d9a7702ce3c4111ddbd4fdb6c10 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Tue, 15 Jul 2025 19:09:55 +0400 Subject: [PATCH 24/63] Check for Card button gateway enabled --- .../ppcp-wc-gateway/src/Helper/CardPaymentsConfiguration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/src/Helper/CardPaymentsConfiguration.php b/modules/ppcp-wc-gateway/src/Helper/CardPaymentsConfiguration.php index 278807597..ff829d442 100644 --- a/modules/ppcp-wc-gateway/src/Helper/CardPaymentsConfiguration.php +++ b/modules/ppcp-wc-gateway/src/Helper/CardPaymentsConfiguration.php @@ -330,7 +330,7 @@ class CardPaymentsConfiguration { * @return bool */ public function is_bcdc_enabled() : bool { - if ( 'MX' === $this->store_country ) { + if ( 'MX' === $this->store_country || ! $this->use_acdc() ) { $bcdc_setting = get_option( 'woocommerce_ppcp-card-button-gateway_settings' ); $enabled = $bcdc_setting['enabled'] ?? ''; From 0be7304b1159878784a1446d7dd48c18cad7975b Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Wed, 16 Jul 2025 12:36:53 +0200 Subject: [PATCH 25/63] =?UTF-8?q?=F0=9F=90=9B=20Make=20the=20state=20shipp?= =?UTF-8?q?ing=20address=20data=20optional=20in=20the=20Fastlane=20Block?= =?UTF-8?q?=20Checkout=20to=20fix=20non=20US-CA=20compatibility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-axo-block/resources/js/helpers/fieldHelpers.js | 2 +- .../resources/js/hooks/useShippingAddressChange.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-axo-block/resources/js/helpers/fieldHelpers.js b/modules/ppcp-axo-block/resources/js/helpers/fieldHelpers.js index c04b62a24..d7064e004 100644 --- a/modules/ppcp-axo-block/resources/js/helpers/fieldHelpers.js +++ b/modules/ppcp-axo-block/resources/js/helpers/fieldHelpers.js @@ -121,7 +121,7 @@ export const populateWooFields = ( address_1: address.addressLine1, address_2: address.addressLine2 || '', city: address.adminArea2, - state: address.adminArea1, + state: address.adminArea1 || '', postcode: address.postalCode, country: address.countryCode, phone: phoneNumber.nationalNumber, diff --git a/modules/ppcp-axo-block/resources/js/hooks/useShippingAddressChange.js b/modules/ppcp-axo-block/resources/js/hooks/useShippingAddressChange.js index 2c45b8930..d22904ab0 100644 --- a/modules/ppcp-axo-block/resources/js/hooks/useShippingAddressChange.js +++ b/modules/ppcp-axo-block/resources/js/hooks/useShippingAddressChange.js @@ -32,7 +32,7 @@ export const useShippingAddressChange = ( fastlaneSdk, setShippingAddress ) => { address_1: address.addressLine1, address_2: address.addressLine2 || '', city: address.adminArea2, - state: address.adminArea1, + state: address.adminArea1 || '', postcode: address.postalCode, country: address.countryCode, phone: phoneNumber.nationalNumber, From bb04e0ebe802058f1af6f8fd848e8d8366ece011 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Wed, 16 Jul 2025 12:45:59 +0200 Subject: [PATCH 26/63] =?UTF-8?q?=F0=9F=90=9B=20Make=20the=20state=20data?= =?UTF-8?q?=20optional=20in=20the=20Fastlane=20Classic=20Checkout=20to=20f?= =?UTF-8?q?ix=20non=20US-CA=20compatibility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-axo/resources/js/Views/ShippingView.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/modules/ppcp-axo/resources/js/Views/ShippingView.js b/modules/ppcp-axo/resources/js/Views/ShippingView.js index 4853659b7..1b3cbd94f 100644 --- a/modules/ppcp-axo/resources/js/Views/ShippingView.js +++ b/modules/ppcp-axo/resources/js/Views/ShippingView.js @@ -44,7 +44,7 @@ class ShippingView { ? this.states[ countryCode ][ stateCode ] : stateCode; - if ( this.hasEmptyValues( data, stateName ) ) { + if ( this.hasEmptyValues( data ) ) { return `
@@ -71,9 +71,9 @@ class ShippingView { ) }
${ data.value( 'street1' ) }
${ data.value( 'street2' ) }
-
${ data.value( - 'city' - ) }, ${ stateName } ${ data.value( 'postCode' ) }
+
${ data.value( 'city' ) }${ + stateName ? ', ' + stateName : '' + } ${ data.value( 'postCode' ) }
${ valueOfSelect( '#billing_country', countryCode @@ -158,14 +158,13 @@ class ShippingView { } ); } - hasEmptyValues( data, stateName ) { + hasEmptyValues( data ) { return ( ! data.value( 'email' ) || ! data.value( 'firstName' ) || ! data.value( 'lastName' ) || ! data.value( 'street1' ) || - ! data.value( 'city' ) || - ! stateName + ! data.value( 'city' ) ); } From 3001245c3b22337315dc22453f0fedfa8f4fb14d Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Wed, 16 Jul 2025 19:13:53 +0400 Subject: [PATCH 27/63] Update option if the migration is done. --- modules/ppcp-settings/src/Ajax/SwitchSettingsUiEndpoint.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/ppcp-settings/src/Ajax/SwitchSettingsUiEndpoint.php b/modules/ppcp-settings/src/Ajax/SwitchSettingsUiEndpoint.php index d5d5fb0da..a1bc218d0 100644 --- a/modules/ppcp-settings/src/Ajax/SwitchSettingsUiEndpoint.php +++ b/modules/ppcp-settings/src/Ajax/SwitchSettingsUiEndpoint.php @@ -24,6 +24,7 @@ class SwitchSettingsUiEndpoint { public const ENDPOINT = 'ppcp-settings-switch-ui'; public const OPTION_NAME_SHOULD_USE_OLD_UI = 'woocommerce_ppcp-settings-should-use-old-ui'; + public const OPTION_NAME_MIGRATION_IS_DONE = 'woocommerce_ppcp-settings-migration-is-done'; protected RequestData $request_data; protected LoggerInterface $logger; @@ -70,6 +71,8 @@ class SwitchSettingsUiEndpoint { $this->onboarding_profile->save(); $this->settings_data_migration->migrate(); + + update_option( self::OPTION_NAME_MIGRATION_IS_DONE, 'yes' ); wp_send_json_success(); } catch ( Exception $error ) { wp_send_json_error( array( 'message' => $error->getMessage() ), 500 ); From e3eee0970d1b799860d6dcdede52a17c7f70b1df Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Wed, 16 Jul 2025 19:14:11 +0400 Subject: [PATCH 28/63] Add the todo based on option --- .../src/Data/Definition/TodosDefinition.php | 69 ++++++++++++++++++- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-settings/src/Data/Definition/TodosDefinition.php b/modules/ppcp-settings/src/Data/Definition/TodosDefinition.php index 6d1a3be7a..a8018842c 100644 --- a/modules/ppcp-settings/src/Data/Definition/TodosDefinition.php +++ b/modules/ppcp-settings/src/Data/Definition/TodosDefinition.php @@ -9,6 +9,8 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Settings\Data\Definition; +use WooCommerce\PayPalCommerce\Settings\Ajax\SwitchSettingsUiEndpoint; +use WooCommerce\PayPalCommerce\Settings\Data\TodosModel; use WooCommerce\PayPalCommerce\Settings\Service\TodosEligibilityService; use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings; @@ -34,18 +36,23 @@ class TodosDefinition { */ protected GeneralSettings $settings; + protected TodosModel $todos; + /** * Constructor. * * @param TodosEligibilityService $eligibilities The todos eligibility service. * @param GeneralSettings $settings The general settings service. + * @param TodosModel $todos The todos model instance. */ public function __construct( TodosEligibilityService $eligibilities, - GeneralSettings $settings + GeneralSettings $settings, + TodosModel $todos ) { $this->eligibilities = $eligibilities; $this->settings = $settings; + $this->todos = $todos; } /** @@ -56,7 +63,7 @@ class TodosDefinition { public function get(): array { $eligibility_checks = $this->eligibilities->get_eligibility_checks(); - return array( + $todo_items = array( 'enable_fastlane' => array( 'title' => __( 'Enable Fastlane', 'woocommerce-paypal-payments' ), 'description' => __( 'Accelerate your guest checkout with Fastlane by PayPal', 'woocommerce-paypal-payments' ), @@ -228,5 +235,63 @@ class TodosDefinition { 'priority' => 13, ), ); + + $todo_items['check_settings_after_migration'] = array( + 'title' => __( "You're now using the new PayPal Payments interface!", 'woocommerce-paypal-payments' ), + 'description' => __( 'Complete the items below to ensure your payment configuration is optimized for your store.', 'woocommerce-paypal-payments' ), + 'isEligible' => fn(): bool => $this->is_settings_migration_done() && ! $this->are_all_todos_completed( $todo_items ), + 'action' => array( + 'type' => 'tab', + 'tab' => 'overview', + ), + 'priority' => 0, + ); + + return $todo_items; } + + /** + * Checks whether the settings migration to the new UI has been completed. + * + * @return bool True if the migration is marked as done, false otherwise. + */ + protected function is_settings_migration_done(): bool { + return 'yes' === get_option( SwitchSettingsUiEndpoint::OPTION_NAME_MIGRATION_IS_DONE ); + } + + /** + * Determines whether all todos have been completed or dismissed appropriately. + * + * A to-do is considered completed if: + * - It's eligible (based on the callable `isEligible`), AND + * - It is either: + * - A "completeOnClick" type and is present in the completed list, OR + * - Not a "completeOnClick" type and is present in the dismissed list. + * + * @param array $todos The array of to-do definitions. + * @return bool True if all to-dos are completed or dismissed as expected, false otherwise. + */ + protected function are_all_todos_completed( array $todos ): bool { + $dismissed = $this->todos->get_dismissed_todos(); + $completed = $this->todos->get_completed_onclick_todos(); + + foreach ( $todos as $id => $todo ) { + if ( ! is_callable( $todo['isEligible'] ) || ! call_user_func( $todo['isEligible'] ) ) { + continue; + } + + $is_click_to_complete = $todo['action']['completeOnClick'] ?? false; + + if ( $is_click_to_complete && ! in_array( $id, $completed, true ) ) { + return false; + } + + if ( ! $is_click_to_complete && ! in_array( $id, $dismissed, true ) ) { + return false; + } + } + + return true; + } + } From 4e93aca57aaa916f0a41e4743333944f808dc57d Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Wed, 16 Jul 2025 19:14:24 +0400 Subject: [PATCH 29/63] Add appropriate services --- modules/ppcp-settings/services.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-settings/services.php b/modules/ppcp-settings/services.php index 0a00db31e..2b2a838f4 100644 --- a/modules/ppcp-settings/services.php +++ b/modules/ppcp-settings/services.php @@ -417,7 +417,8 @@ $services = array( 'settings.data.definition.todos' => static function ( ContainerInterface $container ) : TodosDefinition { return new TodosDefinition( $container->get( 'settings.service.todos_eligibilities' ), - $container->get( 'settings.data.general' ) + $container->get( 'settings.data.general' ), + $container->get( 'settings.data.todos' ) ); }, 'settings.data.definition.methods' => static function ( ContainerInterface $container ) : PaymentMethodsDefinition { From 0fc54d1df5203f489b067be40048a71dee490aaa Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Wed, 16 Jul 2025 19:14:46 +0400 Subject: [PATCH 30/63] Remove the option on plugin uninstall --- modules/ppcp-uninstall/services.php | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/ppcp-uninstall/services.php b/modules/ppcp-uninstall/services.php index 629f1164b..3256b4672 100644 --- a/modules/ppcp-uninstall/services.php +++ b/modules/ppcp-uninstall/services.php @@ -36,6 +36,7 @@ return array( WebhookRegistrar::KEY, 'ppcp_payment_tokens_migration_initialized', SwitchSettingsUiEndpoint::OPTION_NAME_SHOULD_USE_OLD_UI, + SwitchSettingsUiEndpoint::OPTION_NAME_MIGRATION_IS_DONE, ); }, From 7973fd433ec82593eb70a407f2bddb3cdd2eece8 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 16 Jul 2025 17:40:53 +0200 Subject: [PATCH 31/63] Remove all sdk_client_token references in the code --- modules/ppcp-axo-block/src/AxoBlockModule.php | 49 ------------------- modules/ppcp-axo/src/AxoModule.php | 44 ----------------- .../js/modules/Helper/ConfigProcessor.js | 16 ++---- .../js/modules/Helper/PayPalScriptLoading.js | 8 --- .../js/modules/Helper/ScriptLoading.js | 22 --------- 5 files changed, 3 insertions(+), 136 deletions(-) diff --git a/modules/ppcp-axo-block/src/AxoBlockModule.php b/modules/ppcp-axo-block/src/AxoBlockModule.php index 307898cdc..e830784b6 100644 --- a/modules/ppcp-axo-block/src/AxoBlockModule.php +++ b/modules/ppcp-axo-block/src/AxoBlockModule.php @@ -67,24 +67,6 @@ class AxoBlockModule implements ServiceModule, ExtendingModule, ExecutableModule add_action( 'wp_loaded', function () use ( $c ) { - add_filter( - 'woocommerce_paypal_payments_localized_script_data', - function( array $localized_script_data ) use ( $c ) { - if ( ! $c->has( 'axo.available' ) || ! $c->get( 'axo.available' ) ) { - return $localized_script_data; - } - - $module = $this; - $api = $c->get( 'api.sdk-client-token' ); - assert( $api instanceof SdkClientToken ); - - $logger = $c->get( 'woocommerce.logger.woocommerce' ); - assert( $logger instanceof LoggerInterface ); - - return $module->add_sdk_client_token_to_script_data( $api, $logger, $localized_script_data ); - } - ); - /** * Param types removed to avoid third-party issues. * @@ -146,37 +128,6 @@ class AxoBlockModule implements ServiceModule, ExtendingModule, ExecutableModule return true; } - /** - * Adds id token to localized script data. - * - * @param SdkClientToken $api User id token api. - * @param LoggerInterface $logger The logger. - * @param array $localized_script_data The localized script data. - * @return array - */ - private function add_sdk_client_token_to_script_data( - SdkClientToken $api, - LoggerInterface $logger, - array $localized_script_data - ): array { - try { - $sdk_client_token = $api->sdk_client_token(); - $localized_script_data['axo'] = array( - 'sdk_client_token' => $sdk_client_token, - ); - - } catch ( RuntimeException $exception ) { - $error = $exception->getMessage(); - if ( is_a( $exception, PayPalApiException::class ) ) { - $error = $exception->get_details( $error ); - } - - $logger->error( $error ); - } - - return $localized_script_data; - } - /** * Enqueues PayPal Insights analytics script for the Checkout block. * diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index 1b9b6f59a..8bfe4f767 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -274,19 +274,6 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { } ); - add_filter( - 'woocommerce_paypal_payments_localized_script_data', - function( array $localized_script_data ) use ( $c ) { - $api = $c->get( 'api.sdk-client-token' ); - assert( $api instanceof SdkClientToken ); - - $logger = $c->get( 'woocommerce.logger.woocommerce' ); - assert( $logger instanceof LoggerInterface ); - - return $this->add_sdk_client_token_to_script_data( $api, $logger, $localized_script_data ); - } - ); - add_filter( 'ppcp_onboarding_dcc_table_rows', /** @@ -370,37 +357,6 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { return true; } - /** - * Adds id token to localized script data. - * - * @param SdkClientToken $api User id token api. - * @param LoggerInterface $logger The logger. - * @param array $localized_script_data The localized script data. - * @return array - */ - private function add_sdk_client_token_to_script_data( - SdkClientToken $api, - LoggerInterface $logger, - array $localized_script_data - ): array { - try { - $sdk_client_token = $api->sdk_client_token(); - $localized_script_data['axo'] = array( - 'sdk_client_token' => $sdk_client_token, - ); - - } catch ( RuntimeException $exception ) { - $error = $exception->getMessage(); - if ( is_a( $exception, PayPalApiException::class ) ) { - $error = $exception->get_details( $error ); - } - - $logger->error( $error ); - } - - return $localized_script_data; - } - /** * Condition to evaluate if Credit Card gateway should be hidden. * diff --git a/modules/ppcp-button/resources/js/modules/Helper/ConfigProcessor.js b/modules/ppcp-button/resources/js/modules/Helper/ConfigProcessor.js index b8736d0e1..4ecd99c56 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/ConfigProcessor.js +++ b/modules/ppcp-button/resources/js/modules/Helper/ConfigProcessor.js @@ -2,17 +2,6 @@ import merge from 'deepmerge'; import { v4 as uuidv4 } from 'uuid'; import { keysToCamelCase } from './Utils'; -const processAxoConfig = ( config ) => { - const scriptOptions = {}; - const sdkClientToken = config?.axo?.sdk_client_token; - const uuid = uuidv4().replace( /-/g, '' ); - if ( sdkClientToken && config?.user?.is_logged !== true ) { - scriptOptions[ 'data-sdk-client-token' ] = sdkClientToken; - scriptOptions[ 'data-client-metadata-id' ] = uuid; - } - return scriptOptions; -}; - const processUserIdToken = ( config ) => { const userIdToken = config?.save_payment_methods?.id_token; return userIdToken && config?.user?.is_logged === true @@ -25,7 +14,8 @@ export const processConfig = ( config ) => { if ( config.script_attributes ) { scriptOptions = merge( scriptOptions, config.script_attributes ); } - const axoOptions = processAxoConfig( config ); + const userIdTokenOptions = processUserIdToken( config ); - return merge.all( [ scriptOptions, axoOptions, userIdTokenOptions ] ); + + return merge.all( [ scriptOptions, userIdTokenOptions ] ); }; diff --git a/modules/ppcp-button/resources/js/modules/Helper/PayPalScriptLoading.js b/modules/ppcp-button/resources/js/modules/Helper/PayPalScriptLoading.js index 48134d2bc..de7bf4968 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/PayPalScriptLoading.js +++ b/modules/ppcp-button/resources/js/modules/Helper/PayPalScriptLoading.js @@ -48,14 +48,6 @@ export const loadPayPalScript = async ( namespace, config ) => { 'data-namespace': namespace, }; - const dataClientIdResult = await handleDataClientIdAttribute( - scriptOptions, - config - ); - if ( dataClientIdResult ) { - return dataClientIdResult; - } - const scriptPromise = new Promise( ( resolve, reject ) => { loadScript( scriptOptions ) .then( ( script ) => { diff --git a/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js b/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js index 00ae98a9c..3e24b8845 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js +++ b/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js @@ -72,28 +72,6 @@ export const loadPaypalScript = ( config, onLoaded, onError = null ) => { scriptOptions = merge( scriptOptions, config.script_attributes ); } - // Axo SDK options - const sdkClientToken = config?.axo?.sdk_client_token; - const uuid = uuidv4().replace( /-/g, '' ); - if ( sdkClientToken && config?.user?.is_logged !== true ) { - scriptOptions[ 'data-sdk-client-token' ] = sdkClientToken; - scriptOptions[ 'data-client-metadata-id' ] = uuid; - } - - // Load PayPal script for special case with data-client-token - if ( - config.data_client_id?.set_attribute && - config.vault_v3_enabled !== '1' - ) { - dataClientIdAttributeHandler( - scriptOptions, - config.data_client_id, - callback, - errorCallback - ); - return; - } - // Adds data-user-id-token to script options. const userIdToken = config?.save_payment_methods?.id_token; if ( userIdToken && config?.user?.is_logged === true ) { From c9795e8b4d5a4b4a1f0c058664d9c1adce44fff2 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 16 Jul 2025 17:56:11 +0200 Subject: [PATCH 32/63] Remove all sdk_client_token references in the code --- .../js/modules/Helper/PayPalScriptLoading.js | 21 ------------------- .../js/modules/Helper/ScriptLoading.js | 3 --- 2 files changed, 24 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/Helper/PayPalScriptLoading.js b/modules/ppcp-button/resources/js/modules/Helper/PayPalScriptLoading.js index de7bf4968..b90bad117 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/PayPalScriptLoading.js +++ b/modules/ppcp-button/resources/js/modules/Helper/PayPalScriptLoading.js @@ -1,31 +1,10 @@ import { loadScript } from '@paypal/paypal-js'; -import dataClientIdAttributeHandler from '../DataClientIdAttributeHandler'; import widgetBuilder from '../Renderer/WidgetBuilder'; import { processConfig } from './ConfigProcessor'; const loadedScripts = new Map(); const scriptPromises = new Map(); -const handleDataClientIdAttribute = async ( scriptOptions, config ) => { - if ( - config.data_client_id?.set_attribute && - config.vault_v3_enabled !== true - ) { - return new Promise( ( resolve, reject ) => { - dataClientIdAttributeHandler( - scriptOptions, - config.data_client_id, - ( paypal ) => { - widgetBuilder.setPaypal( paypal ); - resolve( paypal ); - }, - reject - ); - } ); - } - return null; -}; - export const loadPayPalScript = async ( namespace, config ) => { if ( ! namespace ) { throw new Error( 'Namespace is required' ); diff --git a/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js b/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js index 3e24b8845..9d143ad16 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js +++ b/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js @@ -1,10 +1,7 @@ -import dataClientIdAttributeHandler from '../DataClientIdAttributeHandler'; import { loadScript } from '@paypal/paypal-js'; import widgetBuilder from '../Renderer/WidgetBuilder'; import merge from 'deepmerge'; import { keysToCamelCase } from './Utils'; -import { getCurrentPaymentMethod } from './CheckoutMethodState'; -import { v4 as uuidv4 } from 'uuid'; // This component may be used by multiple modules. This assures that options are shared between all instances. const scriptOptionsMap = {}; From efed7b793a7340fa6db197c382f1478498401db0 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Thu, 17 Jul 2025 16:21:38 +0400 Subject: [PATCH 33/63] Fix the 'New UI active' status report. After making the new settings module always active, we need to rely on `SettingsModule::should_use_the_old_ui()` to determine whether the new UI is being used. --- modules/ppcp-status-report/src/StatusReportModule.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/modules/ppcp-status-report/src/StatusReportModule.php b/modules/ppcp-status-report/src/StatusReportModule.php index 7150b703b..e84f49ddf 100644 --- a/modules/ppcp-status-report/src/StatusReportModule.php +++ b/modules/ppcp-status-report/src/StatusReportModule.php @@ -15,6 +15,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Helper\ReferenceTransactionStatus; use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies; use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply; use WooCommerce\PayPalCommerce\Compat\PPEC\PPECHelper; +use WooCommerce\PayPalCommerce\Settings\SettingsModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; @@ -100,12 +101,7 @@ class StatusReportModule implements ServiceModule, ExtendingModule, ExecutableMo 'label' => esc_html__( 'New UI active', 'woocommerce-paypal-payments' ), 'exported_label' => 'New UI active', 'description' => esc_html__( 'Indicates whether the new Settings UI is enabled.', 'woocommerce-paypal-payments' ), - 'value' => $this->bool_to_html( - apply_filters( - 'woocommerce.feature-flags.woocommerce_paypal_payments.settings_enabled', - '1' === get_option( 'woocommerce-ppcp-is-new-merchant' ) || getenv( 'PCP_SETTINGS_ENABLED' ) === '1' - ) - ), + 'value' => $this->bool_to_html( ! SettingsModule::should_use_the_old_ui() ), ), array( 'label' => esc_html__( 'Shop country code', 'woocommerce-paypal-payments' ), From bea875a83bb4132e813ec61f1353e0ec00f553d4 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Thu, 17 Jul 2025 15:07:13 +0200 Subject: [PATCH 34/63] Add sdk client token when loading PayPal script (WIP) --- .../resources/js/hooks/usePayPalScript.js | 8 +++++++- modules/ppcp-axo/resources/js/boot.js | 12 ++++++++---- .../resources/js/modules/Helper/ConfigProcessor.js | 1 - 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/modules/ppcp-axo-block/resources/js/hooks/usePayPalScript.js b/modules/ppcp-axo-block/resources/js/hooks/usePayPalScript.js index 1d536344f..6818b843a 100644 --- a/modules/ppcp-axo-block/resources/js/hooks/usePayPalScript.js +++ b/modules/ppcp-axo-block/resources/js/hooks/usePayPalScript.js @@ -28,7 +28,13 @@ const usePayPalScript = ( namespace, ppcpConfig, isConfigLoaded ) => { const loadScript = async () => { if ( ! isPayPalLoaded && isConfigLoaded ) { try { - await loadPayPalScript( namespace, ppcpConfig ); + await loadPayPalScript( namespace, { + ...ppcpConfig, + script_attributes: { + ...ppcpConfig.script_attributes, + 'data-sdk-client-token': 'abc123', + }, + } ); setIsPayPalLoaded( true ); } catch ( error ) { log( diff --git a/modules/ppcp-axo/resources/js/boot.js b/modules/ppcp-axo/resources/js/boot.js index 1effce798..60eb1da61 100644 --- a/modules/ppcp-axo/resources/js/boot.js +++ b/modules/ppcp-axo/resources/js/boot.js @@ -2,7 +2,7 @@ import AxoManager from './AxoManager'; import { loadPayPalScript } from '../../../ppcp-button/resources/js/modules/Helper/PayPalScriptLoading'; import { log } from './Helper/Debug'; -( function ( { axoConfig, ppcpConfig, jQuery } ) { +( function ( { axoConfig, ppcpConfig } ) { const namespace = 'ppcpPaypalClassicAxo'; const bootstrap = () => { new AxoManager( namespace, axoConfig, ppcpConfig ); @@ -14,8 +14,13 @@ import { log } from './Helper/Debug'; return; } - // Load PayPal - loadPayPalScript( namespace, ppcpConfig ) + loadPayPalScript( namespace, { + ...ppcpConfig, + script_attributes: { + ...ppcpConfig.script_attributes, + 'data-sdk-client-token': 'abc123', + }, + } ) .then( () => { bootstrap(); } ) @@ -26,5 +31,4 @@ import { log } from './Helper/Debug'; } )( { axoConfig: window.wc_ppcp_axo, ppcpConfig: window.PayPalCommerceGateway, - jQuery: window.jQuery, } ); diff --git a/modules/ppcp-button/resources/js/modules/Helper/ConfigProcessor.js b/modules/ppcp-button/resources/js/modules/Helper/ConfigProcessor.js index 4ecd99c56..bc1e4fa7a 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/ConfigProcessor.js +++ b/modules/ppcp-button/resources/js/modules/Helper/ConfigProcessor.js @@ -1,5 +1,4 @@ import merge from 'deepmerge'; -import { v4 as uuidv4 } from 'uuid'; import { keysToCamelCase } from './Utils'; const processUserIdToken = ( config ) => { From 86696e91b5b56fd0d4d162a526cc2899c47149aa Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Thu, 17 Jul 2025 17:39:52 +0400 Subject: [PATCH 35/63] Add "Branded only" status report --- modules/ppcp-status-report/src/StatusReportModule.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/ppcp-status-report/src/StatusReportModule.php b/modules/ppcp-status-report/src/StatusReportModule.php index e84f49ddf..359c8859e 100644 --- a/modules/ppcp-status-report/src/StatusReportModule.php +++ b/modules/ppcp-status-report/src/StatusReportModule.php @@ -15,6 +15,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Helper\ReferenceTransactionStatus; use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies; use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply; use WooCommerce\PayPalCommerce\Compat\PPEC\PPECHelper; +use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings; use WooCommerce\PayPalCommerce\Settings\SettingsModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; @@ -86,6 +87,9 @@ class StatusReportModule implements ServiceModule, ExtendingModule, ExecutableMo $subscription_mode_options = $c->get( 'wcgateway.settings.fields.subscriptions_mode_options' ); + /* @var GeneralSettings $general_settings General plugin settings. */ + $general_settings = $c->get( 'settings.data.general' ); + // Feature flag convention. // phpcs:disable WordPress.NamingConventions.ValidHookName.UseUnderscores $items = array( @@ -97,6 +101,12 @@ class StatusReportModule implements ServiceModule, ExtendingModule, ExecutableMo $this->onboarded( $bearer, $is_connected ) ), ), + array( + 'label' => esc_html__( 'Branded only', 'woocommerce-paypal-payments' ), + 'exported_label' => 'Branded only', + 'description' => esc_html__( 'Whether the plugin is in Branded only mode or not.', 'woocommerce-paypal-payments' ), + 'value' => $this->bool_to_html( $general_settings->own_brand_only() ), + ), array( 'label' => esc_html__( 'New UI active', 'woocommerce-paypal-payments' ), 'exported_label' => 'New UI active', From fff4f5bb84f733eb111940ef7e9de02e94b5da54 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Thu, 17 Jul 2025 16:04:31 +0200 Subject: [PATCH 36/63] Add sdk client token when loading PayPal script (WIP) --- .../{FrontendLoggerEndpoint.php => Endpoint/FrontendLogger.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename modules/ppcp-axo/src/{FrontendLoggerEndpoint.php => Endpoint/FrontendLogger.php} (100%) diff --git a/modules/ppcp-axo/src/FrontendLoggerEndpoint.php b/modules/ppcp-axo/src/Endpoint/FrontendLogger.php similarity index 100% rename from modules/ppcp-axo/src/FrontendLoggerEndpoint.php rename to modules/ppcp-axo/src/Endpoint/FrontendLogger.php From 450e4eb6aacdc78da177683bbfa0d494926e3d93 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Thu, 17 Jul 2025 16:44:25 +0200 Subject: [PATCH 37/63] Add axo script attributes boilerplate to classic checkout --- .../src/AxoBlockPaymentMethod.php | 6 +-- modules/ppcp-axo/resources/js/boot.js | 21 ++++++++++- modules/ppcp-axo/services.php | 13 ++++++- modules/ppcp-axo/src/Assets/AxoManager.php | 11 ++++-- modules/ppcp-axo/src/AxoModule.php | 20 +++++++--- .../src/Endpoint/AxoScriptAttributes.php | 37 +++++++++++++++++++ .../ppcp-axo/src/Endpoint/FrontendLogger.php | 4 +- 7 files changed, 94 insertions(+), 18 deletions(-) create mode 100644 modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php diff --git a/modules/ppcp-axo-block/src/AxoBlockPaymentMethod.php b/modules/ppcp-axo-block/src/AxoBlockPaymentMethod.php index 92a76e101..ee2f6bd12 100644 --- a/modules/ppcp-axo-block/src/AxoBlockPaymentMethod.php +++ b/modules/ppcp-axo-block/src/AxoBlockPaymentMethod.php @@ -11,7 +11,7 @@ namespace WooCommerce\PayPalCommerce\AxoBlock; use WC_Payment_Gateway; use Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType; -use WooCommerce\PayPalCommerce\Axo\FrontendLoggerEndpoint; +use WooCommerce\PayPalCommerce\Axo\Endpoint\FrontendLogger; use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment; use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway; @@ -258,8 +258,8 @@ class AxoBlockPaymentMethod extends AbstractPaymentMethodType { 'module_url' => untrailingslashit( $this->module_url ), 'ajax' => array( 'frontend_logger' => array( - 'endpoint' => \WC_AJAX::get_endpoint( FrontendLoggerEndpoint::ENDPOINT ), - 'nonce' => wp_create_nonce( FrontendLoggerEndpoint::nonce() ), + 'endpoint' => \WC_AJAX::get_endpoint( FrontendLogger::ENDPOINT ), + 'nonce' => wp_create_nonce( FrontendLogger::nonce() ), ), ), 'logging_enabled' => $this->settings->has( 'logging_enabled' ) ? $this->settings->get( 'logging_enabled' ) : '', diff --git a/modules/ppcp-axo/resources/js/boot.js b/modules/ppcp-axo/resources/js/boot.js index 60eb1da61..9e5a2b389 100644 --- a/modules/ppcp-axo/resources/js/boot.js +++ b/modules/ppcp-axo/resources/js/boot.js @@ -8,17 +8,34 @@ import { log } from './Helper/Debug'; new AxoManager( namespace, axoConfig, ppcpConfig ); }; - document.addEventListener( 'DOMContentLoaded', () => { + document.addEventListener( 'DOMContentLoaded', async () => { if ( typeof PayPalCommerceGateway === 'undefined' ) { console.error( 'AXO could not be configured.' ); return; } + const res = await fetch( + axoConfig.ajax.axo_script_attributes.endpoint, + { + method: 'POST', + credentials: 'same-origin', + body: JSON.stringify( { + nonce: axoConfig.ajax.axo_script_attributes.nonce, + } ), + } + ); + + const json = await res.json(); + if ( ! json.success ) { + throw new Error( json.data.message ); + } + loadPayPalScript( namespace, { ...ppcpConfig, script_attributes: { ...ppcpConfig.script_attributes, - 'data-sdk-client-token': 'abc123', + 'data-sdk-client-token': json.data.sdk_client_token, + 'data-client-metadata-id': json.data.client_metadata_id, }, } ) .then( () => { diff --git a/modules/ppcp-axo/services.php b/modules/ppcp-axo/services.php index 2d35fd136..05a46c4b9 100644 --- a/modules/ppcp-axo/services.php +++ b/modules/ppcp-axo/services.php @@ -10,6 +10,8 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Axo; use WooCommerce\PayPalCommerce\Axo\Assets\AxoManager; +use WooCommerce\PayPalCommerce\Axo\Endpoint\AxoScriptAttributes; +use WooCommerce\PayPalCommerce\Axo\Endpoint\FrontendLogger; use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway; use WooCommerce\PayPalCommerce\Axo\Helper\ApmApplies; use WooCommerce\PayPalCommerce\Axo\Helper\CompatibilityChecker; @@ -280,8 +282,15 @@ return array( return '

' . $notice_content . '

'; }, - 'axo.endpoint.frontend-logger' => static function ( ContainerInterface $container ): FrontendLoggerEndpoint { - return new FrontendLoggerEndpoint( + 'axo.endpoint.frontend-logger' => static function ( ContainerInterface $container ): FrontendLogger { + return new FrontendLogger( + $container->get( 'button.request-data' ), + $container->get( 'woocommerce.logger.woocommerce' ) + ); + }, + + 'axo.endpoint.script-attributes' => static function ( ContainerInterface $container ): AxoScriptAttributes { + return new AxoScriptAttributes( $container->get( 'button.request-data' ), $container->get( 'woocommerce.logger.woocommerce' ) ); diff --git a/modules/ppcp-axo/src/Assets/AxoManager.php b/modules/ppcp-axo/src/Assets/AxoManager.php index 0263adb3c..2a932eae6 100644 --- a/modules/ppcp-axo/src/Assets/AxoManager.php +++ b/modules/ppcp-axo/src/Assets/AxoManager.php @@ -11,7 +11,8 @@ namespace WooCommerce\PayPalCommerce\Axo\Assets; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\ApiClient\Helper\CurrencyGetter; -use WooCommerce\PayPalCommerce\Axo\FrontendLoggerEndpoint; +use WooCommerce\PayPalCommerce\Axo\Endpoint\AxoScriptAttributes; +use WooCommerce\PayPalCommerce\Axo\Endpoint\FrontendLogger; use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment; use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus; @@ -225,8 +226,12 @@ class AxoManager { 'module_url' => untrailingslashit( $this->module_url ), 'ajax' => array( 'frontend_logger' => array( - 'endpoint' => \WC_AJAX::get_endpoint( FrontendLoggerEndpoint::ENDPOINT ), - 'nonce' => wp_create_nonce( FrontendLoggerEndpoint::nonce() ), + 'endpoint' => \WC_AJAX::get_endpoint( FrontendLogger::ENDPOINT ), + 'nonce' => wp_create_nonce( FrontendLogger::nonce() ), + ), + 'axo_script_attributes' => array( + 'endpoint' => \WC_AJAX::get_endpoint( AxoScriptAttributes::ENDPOINT ), + 'nonce' => wp_create_nonce( AxoScriptAttributes::nonce() ), ), ), 'logging_enabled' => $this->settings->has( 'logging_enabled' ) ? $this->settings->get( 'logging_enabled' ) : '', diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index 8bfe4f767..2ffa8d7c1 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -9,11 +9,9 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Axo; -use Psr\Log\LoggerInterface; -use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken; -use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; -use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\Axo\Assets\AxoManager; +use WooCommerce\PayPalCommerce\Axo\Endpoint\AxoScriptAttributes; +use WooCommerce\PayPalCommerce\Axo\Endpoint\FrontendLogger; use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway; use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait; @@ -317,10 +315,10 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { ); add_action( - 'wc_ajax_' . FrontendLoggerEndpoint::ENDPOINT, + 'wc_ajax_' . FrontendLogger::ENDPOINT, static function () use ( $c ) { $endpoint = $c->get( 'axo.endpoint.frontend-logger' ); - assert( $endpoint instanceof FrontendLoggerEndpoint ); + assert( $endpoint instanceof FrontendLogger ); $endpoint->handle_request(); } @@ -354,6 +352,16 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { } ); + add_action( + 'wc_ajax_' . AxoScriptAttributes::ENDPOINT, + static function () use ( $c ) { + $endpoint = $c->get( 'axo.endpoint.script-attributes' ); + assert( $endpoint instanceof AxoScriptAttributes ); + + $endpoint->handle_request(); + } + ); + return true; } diff --git a/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php b/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php new file mode 100644 index 000000000..c2c2114bd --- /dev/null +++ b/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php @@ -0,0 +1,37 @@ +request_data = $request_data; + $this->logger = $logger; + } + + public static function nonce(): string { + return self::ENDPOINT; + } + + public function handle_request(): bool { + $this->request_data->read_request( $this->nonce() ); + + wp_send_json_success( + array( + 'sdk_client_token' => 'abc123', + 'client_metadata_id' => 'xyz789', + ) + ); + + return true; + } +} diff --git a/modules/ppcp-axo/src/Endpoint/FrontendLogger.php b/modules/ppcp-axo/src/Endpoint/FrontendLogger.php index e17a819e4..0c9342b4e 100644 --- a/modules/ppcp-axo/src/Endpoint/FrontendLogger.php +++ b/modules/ppcp-axo/src/Endpoint/FrontendLogger.php @@ -7,7 +7,7 @@ declare(strict_types=1); -namespace WooCommerce\PayPalCommerce\Axo; +namespace WooCommerce\PayPalCommerce\Axo\Endpoint; use Exception; use Psr\Log\LoggerInterface; @@ -17,7 +17,7 @@ use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData; /** * Class FrontendLoggerEndpoint */ -class FrontendLoggerEndpoint implements EndpointInterface { +class FrontendLogger implements EndpointInterface { const ENDPOINT = 'ppc-frontend-logger'; From 57e36ed954cd2b95412e591686c604188cbec4a0 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Thu, 17 Jul 2025 17:36:06 +0200 Subject: [PATCH 38/63] Get sdk token from paypal --- modules/ppcp-axo/services.php | 3 ++- .../src/Endpoint/AxoScriptAttributes.php | 22 ++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-axo/services.php b/modules/ppcp-axo/services.php index 05a46c4b9..17e4bfcd6 100644 --- a/modules/ppcp-axo/services.php +++ b/modules/ppcp-axo/services.php @@ -292,7 +292,8 @@ return array( 'axo.endpoint.script-attributes' => static function ( ContainerInterface $container ): AxoScriptAttributes { return new AxoScriptAttributes( $container->get( 'button.request-data' ), - $container->get( 'woocommerce.logger.woocommerce' ) + $container->get( 'woocommerce.logger.woocommerce' ), + $container->get('api.sdk-client-token') ); }, diff --git a/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php b/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php index c2c2114bd..af44665df 100644 --- a/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php +++ b/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php @@ -3,6 +3,8 @@ namespace WooCommerce\PayPalCommerce\Axo\Endpoint; use Psr\Log\LoggerInterface; +use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken; +use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\Button\Endpoint\EndpointInterface; use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData; @@ -12,10 +14,16 @@ class AxoScriptAttributes implements EndpointInterface { private RequestData $request_data; private LoggerInterface $logger; + private SdkClientToken $sdk_client_token; - public function __construct( RequestData $request_data, LoggerInterface $logger ) { + public function __construct( + RequestData $request_data, + LoggerInterface $logger, + SdkClientToken $sdk_client_token + ) { $this->request_data = $request_data; $this->logger = $logger; + $this->sdk_client_token = $sdk_client_token; } public static function nonce(): string { @@ -25,10 +33,18 @@ class AxoScriptAttributes implements EndpointInterface { public function handle_request(): bool { $this->request_data->read_request( $this->nonce() ); + try { + $token = $this->sdk_client_token->sdk_client_token(); + } catch (PayPalApiException $exception) { + $this->logger->error($exception->getMessage()); + wp_send_json_error($exception->getMessage()); + return false; + } + wp_send_json_success( array( - 'sdk_client_token' => 'abc123', - 'client_metadata_id' => 'xyz789', + 'sdk_client_token' => $token, + 'client_metadata_id' => str_replace('-', '', wp_generate_uuid4()), ) ); From 06c000b42b4a02cc70ac3022052293aeb483cde4 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Fri, 18 Jul 2025 11:09:23 +0200 Subject: [PATCH 39/63] Add axo script attributes to blocks checkout --- .../resources/js/hooks/usePayPalScript.js | 26 ++++++++++++++++++- .../src/AxoBlockPaymentMethod.php | 5 ++++ modules/ppcp-axo/resources/js/boot.js | 1 - .../src/Endpoint/AxoScriptAttributes.php | 1 - 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-axo-block/resources/js/hooks/usePayPalScript.js b/modules/ppcp-axo-block/resources/js/hooks/usePayPalScript.js index 6818b843a..fccfddb92 100644 --- a/modules/ppcp-axo-block/resources/js/hooks/usePayPalScript.js +++ b/modules/ppcp-axo-block/resources/js/hooks/usePayPalScript.js @@ -27,12 +27,36 @@ const usePayPalScript = ( namespace, ppcpConfig, isConfigLoaded ) => { useEffect( () => { const loadScript = async () => { if ( ! isPayPalLoaded && isConfigLoaded ) { + const axoConfig = window.wc_ppcp_axo; + try { + const res = await fetch( + axoConfig.ajax.axo_script_attributes.endpoint, + { + method: 'POST', + credentials: 'same-origin', + body: JSON.stringify( { + nonce: axoConfig.ajax.axo_script_attributes + .nonce, + } ), + } + ); + + const json = await res.json(); + if ( ! json.success ) { + log( + `Failed to load axo script attributes: ${ json.data.message }`, + 'error' + ); + + return; + } + await loadPayPalScript( namespace, { ...ppcpConfig, script_attributes: { ...ppcpConfig.script_attributes, - 'data-sdk-client-token': 'abc123', + 'data-sdk-client-token': json.data.sdk_client_token, }, } ); setIsPayPalLoaded( true ); diff --git a/modules/ppcp-axo-block/src/AxoBlockPaymentMethod.php b/modules/ppcp-axo-block/src/AxoBlockPaymentMethod.php index ee2f6bd12..2add989f0 100644 --- a/modules/ppcp-axo-block/src/AxoBlockPaymentMethod.php +++ b/modules/ppcp-axo-block/src/AxoBlockPaymentMethod.php @@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\AxoBlock; use WC_Payment_Gateway; use Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType; +use WooCommerce\PayPalCommerce\Axo\Endpoint\AxoScriptAttributes; use WooCommerce\PayPalCommerce\Axo\Endpoint\FrontendLogger; use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment; @@ -261,6 +262,10 @@ class AxoBlockPaymentMethod extends AbstractPaymentMethodType { 'endpoint' => \WC_AJAX::get_endpoint( FrontendLogger::ENDPOINT ), 'nonce' => wp_create_nonce( FrontendLogger::nonce() ), ), + 'axo_script_attributes' => array( + 'endpoint' => \WC_AJAX::get_endpoint( AxoScriptAttributes::ENDPOINT ), + 'nonce' => wp_create_nonce( AxoScriptAttributes::nonce() ), + ), ), 'logging_enabled' => $this->settings->has( 'logging_enabled' ) ? $this->settings->get( 'logging_enabled' ) : '', 'wp_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG, diff --git a/modules/ppcp-axo/resources/js/boot.js b/modules/ppcp-axo/resources/js/boot.js index 9e5a2b389..9af2c625d 100644 --- a/modules/ppcp-axo/resources/js/boot.js +++ b/modules/ppcp-axo/resources/js/boot.js @@ -35,7 +35,6 @@ import { log } from './Helper/Debug'; script_attributes: { ...ppcpConfig.script_attributes, 'data-sdk-client-token': json.data.sdk_client_token, - 'data-client-metadata-id': json.data.client_metadata_id, }, } ) .then( () => { diff --git a/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php b/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php index af44665df..143c883b6 100644 --- a/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php +++ b/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php @@ -44,7 +44,6 @@ class AxoScriptAttributes implements EndpointInterface { wp_send_json_success( array( 'sdk_client_token' => $token, - 'client_metadata_id' => str_replace('-', '', wp_generate_uuid4()), ) ); From fbe13cb7ddac7011cf0f5ae4e7f7958c35400a1f Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Fri, 18 Jul 2025 11:29:12 +0200 Subject: [PATCH 40/63] Fix phpcs --- .../ppcp-axo-block/src/AxoBlockPaymentMethod.php | 2 +- modules/ppcp-axo/services.php | 2 +- modules/ppcp-axo/src/Assets/AxoManager.php | 2 +- .../ppcp-axo/src/Endpoint/AxoScriptAttributes.php | 15 +++++++++------ .../PayUponInvoice/PayUponInvoiceGateway.php | 2 +- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/modules/ppcp-axo-block/src/AxoBlockPaymentMethod.php b/modules/ppcp-axo-block/src/AxoBlockPaymentMethod.php index 2add989f0..fb23730b2 100644 --- a/modules/ppcp-axo-block/src/AxoBlockPaymentMethod.php +++ b/modules/ppcp-axo-block/src/AxoBlockPaymentMethod.php @@ -258,7 +258,7 @@ class AxoBlockPaymentMethod extends AbstractPaymentMethodType { 'icons_directory' => esc_url( $this->wcgateway_module_url ) . 'assets/images/axo/', 'module_url' => untrailingslashit( $this->module_url ), 'ajax' => array( - 'frontend_logger' => array( + 'frontend_logger' => array( 'endpoint' => \WC_AJAX::get_endpoint( FrontendLogger::ENDPOINT ), 'nonce' => wp_create_nonce( FrontendLogger::nonce() ), ), diff --git a/modules/ppcp-axo/services.php b/modules/ppcp-axo/services.php index 17e4bfcd6..12dd4aebb 100644 --- a/modules/ppcp-axo/services.php +++ b/modules/ppcp-axo/services.php @@ -293,7 +293,7 @@ return array( return new AxoScriptAttributes( $container->get( 'button.request-data' ), $container->get( 'woocommerce.logger.woocommerce' ), - $container->get('api.sdk-client-token') + $container->get( 'api.sdk-client-token' ) ); }, diff --git a/modules/ppcp-axo/src/Assets/AxoManager.php b/modules/ppcp-axo/src/Assets/AxoManager.php index 2a932eae6..d577033cc 100644 --- a/modules/ppcp-axo/src/Assets/AxoManager.php +++ b/modules/ppcp-axo/src/Assets/AxoManager.php @@ -225,7 +225,7 @@ class AxoManager { 'icons_directory' => esc_url( $this->wcgateway_module_url ) . 'assets/images/axo/', 'module_url' => untrailingslashit( $this->module_url ), 'ajax' => array( - 'frontend_logger' => array( + 'frontend_logger' => array( 'endpoint' => \WC_AJAX::get_endpoint( FrontendLogger::ENDPOINT ), 'nonce' => wp_create_nonce( FrontendLogger::nonce() ), ), diff --git a/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php b/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php index 143c883b6..15e8fc609 100644 --- a/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php +++ b/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php @@ -8,6 +8,9 @@ use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\Button\Endpoint\EndpointInterface; use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData; +/** + * Handles the request for the PayPal Axo script attributes. + */ class AxoScriptAttributes implements EndpointInterface { const ENDPOINT = 'ppc-axo-script-attributes'; @@ -21,8 +24,8 @@ class AxoScriptAttributes implements EndpointInterface { LoggerInterface $logger, SdkClientToken $sdk_client_token ) { - $this->request_data = $request_data; - $this->logger = $logger; + $this->request_data = $request_data; + $this->logger = $logger; $this->sdk_client_token = $sdk_client_token; } @@ -35,15 +38,15 @@ class AxoScriptAttributes implements EndpointInterface { try { $token = $this->sdk_client_token->sdk_client_token(); - } catch (PayPalApiException $exception) { - $this->logger->error($exception->getMessage()); - wp_send_json_error($exception->getMessage()); + } catch ( PayPalApiException $exception ) { + $this->logger->error( $exception->getMessage() ); + wp_send_json_error( $exception->getMessage() ); return false; } wp_send_json_success( array( - 'sdk_client_token' => $token, + 'sdk_client_token' => $token, ) ); diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php index 952df2c8a..0661e3273 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php @@ -248,7 +248,7 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway { public function process_payment( $order_id ) { $wc_order = wc_get_order( $order_id ); // phpcs:disable WordPress.Security.NonceVerification - $birth_date = wc_clean( wp_unslash( $_POST['billing_birth_date'] ?? '' ) ); + $birth_date = wc_clean( wp_unslash( $_POST['billing_birth_date'] ?? '' ) ); $pay_for_order = wc_clean( wp_unslash( $_GET['pay_for_order'] ?? '' ) ); if ( 'true' === $pay_for_order ) { if ( ! $this->checkout_helper->validate_birth_date( $birth_date ) ) { From 0d265f5aa152db572464a58161c5c10fc98e8f0c Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Fri, 18 Jul 2025 13:56:37 +0200 Subject: [PATCH 41/63] Add validation to axo script attributes endpoint --- modules/ppcp-axo/services.php | 18 +++++--- modules/ppcp-axo/src/AxoModule.php | 31 ++----------- .../src/Endpoint/AxoScriptAttributes.php | 20 +++++++- .../ApmApplies.php => Service/AxoApplies.php} | 46 ++++++++++++++++--- 4 files changed, 75 insertions(+), 40 deletions(-) rename modules/ppcp-axo/src/{Helper/ApmApplies.php => Service/AxoApplies.php} (57%) diff --git a/modules/ppcp-axo/services.php b/modules/ppcp-axo/services.php index 12dd4aebb..b466e50a7 100644 --- a/modules/ppcp-axo/services.php +++ b/modules/ppcp-axo/services.php @@ -13,7 +13,7 @@ use WooCommerce\PayPalCommerce\Axo\Assets\AxoManager; use WooCommerce\PayPalCommerce\Axo\Endpoint\AxoScriptAttributes; use WooCommerce\PayPalCommerce\Axo\Endpoint\FrontendLogger; use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway; -use WooCommerce\PayPalCommerce\Axo\Helper\ApmApplies; +use WooCommerce\PayPalCommerce\Axo\Service\AxoApplies; use WooCommerce\PayPalCommerce\Axo\Helper\CompatibilityChecker; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; @@ -31,18 +31,20 @@ return array( return $eligibility_check(); }, 'axo.eligibility.check' => static function ( ContainerInterface $container ): callable { - $apm_applies = $container->get( 'axo.helpers.apm-applies' ); - assert( $apm_applies instanceof ApmApplies ); + $apm_applies = $container->get( 'axo.helpers.axo-applies' ); + assert( $apm_applies instanceof AxoApplies ); return static function () use ( $apm_applies ) : bool { return $apm_applies->for_country_currency() && $apm_applies->for_merchant(); }; }, - 'axo.helpers.apm-applies' => static function ( ContainerInterface $container ) : ApmApplies { - return new ApmApplies( + 'axo.helpers.axo-applies' => static function ( ContainerInterface $container ) : AxoApplies { + return new AxoApplies( $container->get( 'axo.supported-country-currency-matrix' ), $container->get( 'api.shop.currency.getter' ), - $container->get( 'api.shop.country' ) + $container->get( 'api.shop.country' ), + $container->get( 'wcgateway.configuration.card-configuration' ), + $container->get( 'wc-subscriptions.helper' ) ); }, @@ -293,7 +295,9 @@ return array( return new AxoScriptAttributes( $container->get( 'button.request-data' ), $container->get( 'woocommerce.logger.woocommerce' ), - $container->get( 'api.sdk-client-token' ) + $container->get( 'api.sdk-client-token' ), + $container->get( 'axo.helpers.axo-applies' ), + $container->get( 'axo.eligible' ) ); }, diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index 2ffa8d7c1..2d52e2bbc 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -13,6 +13,7 @@ use WooCommerce\PayPalCommerce\Axo\Assets\AxoManager; use WooCommerce\PayPalCommerce\Axo\Endpoint\AxoScriptAttributes; use WooCommerce\PayPalCommerce\Axo\Endpoint\FrontendLogger; use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway; +use WooCommerce\PayPalCommerce\Axo\Service\AxoApplies; use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait; use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingOptionsRenderer; @@ -24,7 +25,6 @@ use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; -use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector; use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsListener; use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; use WC_Payment_Gateways; @@ -209,7 +209,10 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { $smart_button = $c->get( 'button.smart-button' ); assert( $smart_button instanceof SmartButtonInterface ); - if ( $this->should_render_fastlane( $c ) && $smart_button->should_load_ppcp_script() ) { + $axo_applies = $c->get( 'axo.applies' ); + assert( $axo_applies instanceof AxoApplies ); + + if ( $axo_applies->should_render_fastlane() && $smart_button->should_load_ppcp_script() ) { $manager->enqueue(); } } @@ -376,30 +379,6 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { return $this->should_render_fastlane( $c ) && isset( $methods[ CreditCardGateway::ID ] ); } - /** - * Condition to evaluate if Fastlane should be rendered. - * - * Fastlane should only render on the classic checkout, when Fastlane is enabled in the settings and also only for guest customers. - * - * @param ContainerInterface $c The container. - * @return bool - */ - private function should_render_fastlane( ContainerInterface $c ): bool { - - $dcc_configuration = $c->get( 'wcgateway.configuration.card-configuration' ); - assert( $dcc_configuration instanceof CardPaymentsConfiguration ); - - $subscription_helper = $c->get( 'wc-subscriptions.helper' ); - assert( $subscription_helper instanceof SubscriptionHelper ); - - return ! is_user_logged_in() - && CartCheckoutDetector::has_classic_checkout() - && $dcc_configuration->use_fastlane() - && ! $this->is_excluded_endpoint() - && is_checkout() - && ! $subscription_helper->cart_contains_subscription(); - } - /** * Adds the markup necessary for displaying overlays and loaders for Axo on the checkout page. * diff --git a/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php b/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php index 15e8fc609..99cef98ef 100644 --- a/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php +++ b/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php @@ -5,28 +5,37 @@ namespace WooCommerce\PayPalCommerce\Axo\Endpoint; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; +use WooCommerce\PayPalCommerce\Axo\Service\AxoApplies; use WooCommerce\PayPalCommerce\Button\Endpoint\EndpointInterface; use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData; +use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait; /** * Handles the request for the PayPal Axo script attributes. */ class AxoScriptAttributes implements EndpointInterface { + use ContextTrait; const ENDPOINT = 'ppc-axo-script-attributes'; private RequestData $request_data; private LoggerInterface $logger; private SdkClientToken $sdk_client_token; + private AxoApplies $axo_applies; + private bool $axo_eligible; public function __construct( RequestData $request_data, LoggerInterface $logger, - SdkClientToken $sdk_client_token + SdkClientToken $sdk_client_token, + AxoApplies $axo_applies, + bool $axo_eligible ) { $this->request_data = $request_data; $this->logger = $logger; $this->sdk_client_token = $sdk_client_token; + $this->axo_applies = $axo_applies; + $this->axo_eligible = $axo_eligible; } public static function nonce(): string { @@ -36,6 +45,15 @@ class AxoScriptAttributes implements EndpointInterface { public function handle_request(): bool { $this->request_data->read_request( $this->nonce() ); + if ( + ! $this->axo_eligible + || ! $this->axo_applies->should_render_fastlane() + || $this->is_paypal_continuation() + ) { + wp_send_json_error( 'Failed to load axo script attributes.' ); + return false; + } + try { $token = $this->sdk_client_token->sdk_client_token(); } catch ( PayPalApiException $exception ) { diff --git a/modules/ppcp-axo/src/Helper/ApmApplies.php b/modules/ppcp-axo/src/Service/AxoApplies.php similarity index 57% rename from modules/ppcp-axo/src/Helper/ApmApplies.php rename to modules/ppcp-axo/src/Service/AxoApplies.php index cdbffd30a..59dfdf8b7 100644 --- a/modules/ppcp-axo/src/Helper/ApmApplies.php +++ b/modules/ppcp-axo/src/Service/AxoApplies.php @@ -1,21 +1,24 @@ allowed_country_currency_matrix = $allowed_country_currency_matrix; $this->currency = $currency; $this->country = $country; + $this->dcc_configuration = $dcc_configuration; + $this->subscription_helper = $subscription_helper; } /** @@ -79,4 +89,28 @@ class ApmApplies { true ); } + + /** + * Checks if Fastlane should be rendered. + * + * @return bool + */ + public function should_render_fastlane(): bool { + return ! is_user_logged_in() + && CartCheckoutDetector::has_classic_checkout() + && $this->dcc_configuration->use_fastlane() + && ! $this->is_excluded_endpoint() + && is_checkout() + && ! $this->subscription_helper->cart_contains_subscription(); + } + + /** + * Condition to evaluate if the current endpoint is excluded. + * + * @return bool + */ + private function is_excluded_endpoint(): bool { + // Exclude the Order Pay and Order Received endpoints. + return is_wc_endpoint_url( 'order-pay' ) || is_wc_endpoint_url( 'order-received' ); + } } From 5a2b079f80e677a67957de626a0bd71812317359 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Fri, 18 Jul 2025 15:18:39 +0200 Subject: [PATCH 42/63] Rename service --- modules/ppcp-axo/services.php | 11 +++++----- modules/ppcp-axo/src/AxoModule.php | 20 ++++++++++++++----- .../src/Endpoint/AxoScriptAttributes.php | 5 ----- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/modules/ppcp-axo/services.php b/modules/ppcp-axo/services.php index b466e50a7..13afcb32e 100644 --- a/modules/ppcp-axo/services.php +++ b/modules/ppcp-axo/services.php @@ -31,14 +31,14 @@ return array( return $eligibility_check(); }, 'axo.eligibility.check' => static function ( ContainerInterface $container ): callable { - $apm_applies = $container->get( 'axo.helpers.axo-applies' ); - assert( $apm_applies instanceof AxoApplies ); + $axo_applies = $container->get( 'axo.service.axo-applies' ); + assert( $axo_applies instanceof AxoApplies ); - return static function () use ( $apm_applies ) : bool { - return $apm_applies->for_country_currency() && $apm_applies->for_merchant(); + return static function () use ( $axo_applies ) : bool { + return $axo_applies->for_country_currency() && $axo_applies->for_merchant(); }; }, - 'axo.helpers.axo-applies' => static function ( ContainerInterface $container ) : AxoApplies { + 'axo.service.axo-applies' => static function ( ContainerInterface $container ) : AxoApplies { return new AxoApplies( $container->get( 'axo.supported-country-currency-matrix' ), $container->get( 'api.shop.currency.getter' ), @@ -296,7 +296,6 @@ return array( $container->get( 'button.request-data' ), $container->get( 'woocommerce.logger.woocommerce' ), $container->get( 'api.sdk-client-token' ), - $container->get( 'axo.helpers.axo-applies' ), $container->get( 'axo.eligible' ) ); }, diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index 2d52e2bbc..f2a8ab769 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -209,7 +209,7 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { $smart_button = $c->get( 'button.smart-button' ); assert( $smart_button instanceof SmartButtonInterface ); - $axo_applies = $c->get( 'axo.applies' ); + $axo_applies = $c->get( 'axo.service.axo-applies' ); assert( $axo_applies instanceof AxoApplies ); if ( $axo_applies->should_render_fastlane() && $smart_button->should_load_ppcp_script() ) { @@ -222,7 +222,10 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { add_action( $manager->checkout_button_renderer_hook(), function () use ( $c, $manager ) { - if ( $this->should_render_fastlane( $c ) ) { + $axo_applies = $c->get( 'axo.service.axo-applies' ); + assert( $axo_applies instanceof AxoApplies ); + + if ( $axo_applies->should_render_fastlane( $c ) ) { $manager->render_checkout_button(); } } @@ -304,8 +307,10 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { add_action( 'template_redirect', function () use ( $c ) { + $axo_applies = $c->get( 'axo.service.axo-applies' ); + assert( $axo_applies instanceof AxoApplies ); - if ( $this->should_render_fastlane( $c ) ) { + if ( $axo_applies->should_render_fastlane() ) { WC()->session->set( 'chosen_payment_method', AxoGateway::ID ); } } @@ -376,7 +381,10 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { * @return bool */ private function hide_credit_card_when_using_fastlane( array $methods, ContainerInterface $c ): bool { - return $this->should_render_fastlane( $c ) && isset( $methods[ CreditCardGateway::ID ] ); + $axo_applies = $c->get( 'axo.service.axo-applies' ); + assert( $axo_applies instanceof AxoApplies ); + + return $axo_applies->should_render_fastlane() && isset( $methods[ CreditCardGateway::ID ] ); } /** @@ -386,8 +394,10 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { * @return void */ private function add_checkout_loader_markup( ContainerInterface $c ): void { + $axo_applies = $c->get( 'axo.service.axo-applies' ); + assert( $axo_applies instanceof AxoApplies ); - if ( $this->should_render_fastlane( $c ) ) { + if ( $axo_applies->should_render_fastlane() ) { add_action( 'woocommerce_checkout_before_customer_details', function () { diff --git a/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php b/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php index 99cef98ef..7f9fe5349 100644 --- a/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php +++ b/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php @@ -21,20 +21,17 @@ class AxoScriptAttributes implements EndpointInterface { private RequestData $request_data; private LoggerInterface $logger; private SdkClientToken $sdk_client_token; - private AxoApplies $axo_applies; private bool $axo_eligible; public function __construct( RequestData $request_data, LoggerInterface $logger, SdkClientToken $sdk_client_token, - AxoApplies $axo_applies, bool $axo_eligible ) { $this->request_data = $request_data; $this->logger = $logger; $this->sdk_client_token = $sdk_client_token; - $this->axo_applies = $axo_applies; $this->axo_eligible = $axo_eligible; } @@ -47,8 +44,6 @@ class AxoScriptAttributes implements EndpointInterface { if ( ! $this->axo_eligible - || ! $this->axo_applies->should_render_fastlane() - || $this->is_paypal_continuation() ) { wp_send_json_error( 'Failed to load axo script attributes.' ); return false; From 749ffe0f76ebd85efa97d6774124445ea4f2be5b Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Fri, 18 Jul 2025 17:01:35 +0200 Subject: [PATCH 43/63] Add check for user logged in and is paypal continuation --- modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php b/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php index 7f9fe5349..6fed655e2 100644 --- a/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php +++ b/modules/ppcp-axo/src/Endpoint/AxoScriptAttributes.php @@ -5,7 +5,6 @@ namespace WooCommerce\PayPalCommerce\Axo\Endpoint; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; -use WooCommerce\PayPalCommerce\Axo\Service\AxoApplies; use WooCommerce\PayPalCommerce\Button\Endpoint\EndpointInterface; use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData; use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait; @@ -44,6 +43,8 @@ class AxoScriptAttributes implements EndpointInterface { if ( ! $this->axo_eligible + || is_user_logged_in() + || $this->is_paypal_continuation() ) { wp_send_json_error( 'Failed to load axo script attributes.' ); return false; From 6cccb0c97324cb3689365d528c523b2e9f7b52ff Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Fri, 18 Jul 2025 17:05:09 +0200 Subject: [PATCH 44/63] Fix psalm --- modules/ppcp-axo/src/AxoModule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index f2a8ab769..09fba065b 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -225,7 +225,7 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { $axo_applies = $c->get( 'axo.service.axo-applies' ); assert( $axo_applies instanceof AxoApplies ); - if ( $axo_applies->should_render_fastlane( $c ) ) { + if ( $axo_applies->should_render_fastlane() ) { $manager->render_checkout_button(); } } From ac2bfa13af2b62d19adf322d51830acd84583c10 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 21 Jul 2025 09:34:40 +0200 Subject: [PATCH 45/63] Remove comments --- modules/ppcp-axo/src/AxoModule.php | 1 - modules/ppcp-axo/src/Service/AxoApplies.php | 4 ---- modules/ppcp-button/src/Endpoint/CartScriptParamsEndpoint.php | 3 +-- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index 09fba065b..8a031df9b 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -436,7 +436,6 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { * @return bool */ private function is_excluded_endpoint(): bool { - // Exclude the Order Pay and Order Received endpoints. return is_wc_endpoint_url( 'order-pay' ) || is_wc_endpoint_url( 'order-received' ); } diff --git a/modules/ppcp-axo/src/Service/AxoApplies.php b/modules/ppcp-axo/src/Service/AxoApplies.php index 59dfdf8b7..83996c1f6 100644 --- a/modules/ppcp-axo/src/Service/AxoApplies.php +++ b/modules/ppcp-axo/src/Service/AxoApplies.php @@ -15,9 +15,6 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration; use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector; use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; -/** - * Class AxoApplies - */ class AxoApplies { /** @@ -110,7 +107,6 @@ class AxoApplies { * @return bool */ private function is_excluded_endpoint(): bool { - // Exclude the Order Pay and Order Received endpoints. return is_wc_endpoint_url( 'order-pay' ) || is_wc_endpoint_url( 'order-received' ); } } diff --git a/modules/ppcp-button/src/Endpoint/CartScriptParamsEndpoint.php b/modules/ppcp-button/src/Endpoint/CartScriptParamsEndpoint.php index 2f6bc375d..aa5b60e8e 100644 --- a/modules/ppcp-button/src/Endpoint/CartScriptParamsEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CartScriptParamsEndpoint.php @@ -20,8 +20,7 @@ use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; */ class CartScriptParamsEndpoint implements EndpointInterface { - - const ENDPOINT = 'ppc-cart-script-params'; + public const ENDPOINT = 'ppc-cart-script-params'; /** * The SmartButton. From 576b70c3c484039336e043032f5771fc35b3cac8 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 21 Jul 2025 09:49:06 +0200 Subject: [PATCH 46/63] Exclude phpcs rule --- phpcs.xml.dist | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/phpcs.xml.dist b/phpcs.xml.dist index b8ae0871d..cda6e95a7 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -51,11 +51,13 @@ + + + - api src From 038ee9821bcf47dd8098b128cd20603f8fce7688 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 21 Jul 2025 14:04:23 +0200 Subject: [PATCH 47/63] Ensure billing agreement is used as payment token for ppec orders --- modules/ppcp-compat/src/CompatModule.php | 25 ++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/modules/ppcp-compat/src/CompatModule.php b/modules/ppcp-compat/src/CompatModule.php index aec79f6a3..35a3de160 100644 --- a/modules/ppcp-compat/src/CompatModule.php +++ b/modules/ppcp-compat/src/CompatModule.php @@ -10,9 +10,12 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Compat; use Exception; +use stdClass; use WC_Cart; use WC_Order; use WC_Order_Item_Product; +use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken; +use WooCommerce\PayPalCommerce\Compat\PPEC\PPECHelper; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; @@ -91,6 +94,28 @@ class CompatModule implements ServiceModule, ExtendingModule, ExecutableModule { $this->legacy_ui_card_payment_mapping( $c ); + // Short-circuit RenewalHandler::get_token_for_customer() to use a Billing Agreement ID for PPEC orders. + add_filter('woocommerce_paypal_payments_subscriptions_get_token_for_customer', function ($token, $customer, $order ) { + if ( PPECHelper::PPEC_GATEWAY_ID === $order->get_payment_method() && wcs_order_contains_renewal( $order ) ) { + $subscriptions = wcs_get_subscriptions_for_renewal_order($order); + + if (!empty($subscriptions)) { + $subscription = reset($subscriptions); // Get first subscription. + $parent_order = $subscription->get_parent(); + + if ($parent_order) { + $billing_agreement_id = $parent_order->get_meta('_ppec_billing_agreement_id', true); + + if ($billing_agreement_id) { + $token = new PaymentToken($billing_agreement_id, new stdClass(), 'BILLING_AGREEMENT'); + } + } + } + } + + return $token; + }, 10, 3); + return true; } From 521099b16d2c8d5a4bb9aba809e54240ef5bd5a9 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 21 Jul 2025 15:27:26 +0200 Subject: [PATCH 48/63] Add billing agreements fallback to existing method instead of new filter --- modules/ppcp-compat/src/CompatModule.php | 22 ------------------- .../src/PPEC/SubscriptionsHandler.php | 17 ++++++++++++-- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/modules/ppcp-compat/src/CompatModule.php b/modules/ppcp-compat/src/CompatModule.php index 35a3de160..c0c3db330 100644 --- a/modules/ppcp-compat/src/CompatModule.php +++ b/modules/ppcp-compat/src/CompatModule.php @@ -94,28 +94,6 @@ class CompatModule implements ServiceModule, ExtendingModule, ExecutableModule { $this->legacy_ui_card_payment_mapping( $c ); - // Short-circuit RenewalHandler::get_token_for_customer() to use a Billing Agreement ID for PPEC orders. - add_filter('woocommerce_paypal_payments_subscriptions_get_token_for_customer', function ($token, $customer, $order ) { - if ( PPECHelper::PPEC_GATEWAY_ID === $order->get_payment_method() && wcs_order_contains_renewal( $order ) ) { - $subscriptions = wcs_get_subscriptions_for_renewal_order($order); - - if (!empty($subscriptions)) { - $subscription = reset($subscriptions); // Get first subscription. - $parent_order = $subscription->get_parent(); - - if ($parent_order) { - $billing_agreement_id = $parent_order->get_meta('_ppec_billing_agreement_id', true); - - if ($billing_agreement_id) { - $token = new PaymentToken($billing_agreement_id, new stdClass(), 'BILLING_AGREEMENT'); - } - } - } - } - - return $token; - }, 10, 3); - return true; } diff --git a/modules/ppcp-compat/src/PPEC/SubscriptionsHandler.php b/modules/ppcp-compat/src/PPEC/SubscriptionsHandler.php index 640ab607f..c93293cdd 100644 --- a/modules/ppcp-compat/src/PPEC/SubscriptionsHandler.php +++ b/modules/ppcp-compat/src/PPEC/SubscriptionsHandler.php @@ -125,9 +125,22 @@ class SubscriptionsHandler { public function use_billing_agreement_as_token( $token, $customer, $order ) { if ( PPECHelper::PPEC_GATEWAY_ID === $order->get_payment_method() && wcs_order_contains_renewal( $order ) ) { $billing_agreement_id = $order->get_meta( '_ppec_billing_agreement_id', true ); - if ( $billing_agreement_id ) { - $token = new PaymentToken( $billing_agreement_id, new stdClass(), 'BILLING_AGREEMENT' ); + return new PaymentToken( $billing_agreement_id, new stdClass(), 'BILLING_AGREEMENT' ); + } + + $subscriptions = wcs_get_subscriptions_for_renewal_order( $order ); + if ( ! empty( $subscriptions ) ) { + $subscription = reset( $subscriptions ); // Get first subscription. + $parent_order = $subscription->get_parent(); + + if ( $parent_order ) { + $billing_agreement_id = $parent_order->get_meta( '_ppec_billing_agreement_id', true ); + + if ( $billing_agreement_id ) { + return new PaymentToken( $billing_agreement_id, new stdClass(), 'BILLING_AGREEMENT' ); + } + } } } From 086fe17983dc196bc4ea02137052d0fbff755a3f Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 21 Jul 2025 18:37:53 +0400 Subject: [PATCH 49/63] Add check for `purchase_units` --- modules/ppcp-blocks/resources/js/Helper/Address.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-blocks/resources/js/Helper/Address.js b/modules/ppcp-blocks/resources/js/Helper/Address.js index 6e6338f3c..e82e551db 100644 --- a/modules/ppcp-blocks/resources/js/Helper/Address.js +++ b/modules/ppcp-blocks/resources/js/Helper/Address.js @@ -115,7 +115,7 @@ export const paypalSubscriberToWc = ( subscriber ) => { * @return {Object} */ export const paypalOrderToWcShippingAddress = ( order ) => { - const shipping = order.purchase_units?.[ 0 ]?.shipping; + const shipping = order?.purchase_units?.[ 0 ]?.shipping; if ( ! shipping ) { return {}; } From 8fccfd637f698374095f59e487b3089788e7b073 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 21 Jul 2025 18:38:03 +0400 Subject: [PATCH 50/63] Add check for `payer` --- modules/ppcp-blocks/resources/js/Helper/Address.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-blocks/resources/js/Helper/Address.js b/modules/ppcp-blocks/resources/js/Helper/Address.js index e82e551db..aa76c3e5a 100644 --- a/modules/ppcp-blocks/resources/js/Helper/Address.js +++ b/modules/ppcp-blocks/resources/js/Helper/Address.js @@ -145,7 +145,7 @@ export const paypalOrderToWcShippingAddress = ( order ) => { export const paypalOrderToWcAddresses = ( order ) => { const shippingAddress = paypalOrderToWcShippingAddress( order ); let billingAddress = shippingAddress; - if ( order.payer ) { + if ( order?.payer ) { billingAddress = paypalPayerToWc( order.payer ); // no billing address, such as if billing address retrieval is not allowed in the merchant account if ( ! billingAddress.address_line_1 ) { From c4db1ac53aa3e428dae76bf631677e65fd96b0f6 Mon Sep 17 00:00:00 2001 From: Himad M Date: Mon, 21 Jul 2025 15:58:52 -0400 Subject: [PATCH 51/63] Remove redundant authorize call in subscription renewal handler --- modules/ppcp-wc-subscriptions/src/RenewalHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-wc-subscriptions/src/RenewalHandler.php b/modules/ppcp-wc-subscriptions/src/RenewalHandler.php index 986d0271a..6c331571b 100644 --- a/modules/ppcp-wc-subscriptions/src/RenewalHandler.php +++ b/modules/ppcp-wc-subscriptions/src/RenewalHandler.php @@ -516,7 +516,7 @@ class RenewalHandler { $this->add_paypal_meta( $wc_order, $order, $this->environment ); if ( $order->intent() === 'AUTHORIZE' ) { - $order = $this->order_endpoint->authorize( $order ); + // No authorize call needed - vault tokens auto-authorize during order creation. $wc_order->update_meta_data( AuthorizedPaymentsProcessor::CAPTURED_META_KEY, 'false' ); } From ecb46a9ec7937d431cc921debfa05f05d37ef9eb Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Tue, 22 Jul 2025 18:24:17 +0400 Subject: [PATCH 52/63] Skip "Payment Methods" step for branded-only + BCDC and casual sellers. --- .../Screens/Onboarding/Steps/index.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Steps/index.js b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Steps/index.js index 5d0790cf6..aafadedc3 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Steps/index.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Steps/index.js @@ -64,10 +64,19 @@ export const getSteps = ( flags ) => { // Casual selling: Unlock the "Personal Account" choice. ( step ) => flags.canUseCasualSelling || step.id !== 'business', // Skip payment methods screen. - ( step ) => - step.id !== 'methods' || - ( ! flags.shouldSkipPaymentMethods && - ! ( ownBrandOnly && isCasualSeller ) ), + ( step ) => { + if ( step.id !== 'methods' ) { + return true; + } + + const isBrandedBCDC = ownBrandOnly && ! flags.canUseCardPayments; + const shouldSkip = + flags.shouldSkipPaymentMethods || + isCasualSeller || + isBrandedBCDC; + + return ! shouldSkip; + }, ] ); const totalStepsCount = steps.length; From 546a775c497b5bfe447ccbdf988fe41cce7ef0c9 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 23 Jul 2025 12:42:44 +0200 Subject: [PATCH 53/63] Remove should_mock_ppec_gateway logic --- modules/ppcp-compat/src/CompatModule.php | 3 - .../src/PPEC/SubscriptionsHandler.php | 77 +------------------ 2 files changed, 1 insertion(+), 79 deletions(-) diff --git a/modules/ppcp-compat/src/CompatModule.php b/modules/ppcp-compat/src/CompatModule.php index c0c3db330..aec79f6a3 100644 --- a/modules/ppcp-compat/src/CompatModule.php +++ b/modules/ppcp-compat/src/CompatModule.php @@ -10,12 +10,9 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Compat; use Exception; -use stdClass; use WC_Cart; use WC_Order; use WC_Order_Item_Product; -use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken; -use WooCommerce\PayPalCommerce\Compat\PPEC\PPECHelper; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; diff --git a/modules/ppcp-compat/src/PPEC/SubscriptionsHandler.php b/modules/ppcp-compat/src/PPEC/SubscriptionsHandler.php index c93293cdd..de3ea2cb4 100644 --- a/modules/ppcp-compat/src/PPEC/SubscriptionsHandler.php +++ b/modules/ppcp-compat/src/PPEC/SubscriptionsHandler.php @@ -76,7 +76,7 @@ class SubscriptionsHandler { * @return array */ public function add_mock_ppec_gateway( $gateways ) { - if ( ! isset( $gateways[ PPECHelper::PPEC_GATEWAY_ID ] ) && $this->should_mock_ppec_gateway() ) { + if ( ! isset( $gateways[ PPECHelper::PPEC_GATEWAY_ID ] ) ) { $gateways[ PPECHelper::PPEC_GATEWAY_ID ] = $this->mock_gateway; } @@ -146,79 +146,4 @@ class SubscriptionsHandler { return $token; } - - /** - * Checks whether the mock PPEC gateway should be used or not. - * - * @return bool - */ - private function should_mock_ppec_gateway() { - // Are we processing a renewal? - if ( doing_action( 'woocommerce_scheduled_subscription_payment' ) ) { - return true; - } - - // My Account > Subscriptions. - if ( is_wc_endpoint_url( 'subscriptions' ) ) { - return true; - } - - // phpcs:disable WordPress.Security.NonceVerification - - // Checks that require Subscriptions. - if ( class_exists( \WC_Subscriptions::class ) ) { - // My Account > Subscriptions > (Subscription). - if ( wcs_is_view_subscription_page() ) { - $subscription = wcs_get_subscription( absint( get_query_var( 'view-subscription' ) ) ); - - return ( $subscription && PPECHelper::PPEC_GATEWAY_ID === $subscription->get_payment_method() ); - } - - // Changing payment method? - if ( is_wc_endpoint_url( 'order-pay' ) && isset( $_GET['change_payment_method'] ) ) { - $subscription = wcs_get_subscription( absint( get_query_var( 'order-pay' ) ) ); - - return ( $subscription && PPECHelper::PPEC_GATEWAY_ID === $subscription->get_payment_method() ); - } - - // Early renew (via modal). - if ( isset( $_GET['process_early_renewal'], $_GET['subscription_id'] ) ) { - $subscription = wcs_get_subscription( absint( $_GET['subscription_id'] ) ); - - return ( $subscription && PPECHelper::PPEC_GATEWAY_ID === $subscription->get_payment_method() ); - } - } - - // Admin-only from here onwards. - if ( ! is_admin() ) { - return false; - } - - // Are we saving metadata for a subscription? - if ( doing_action( 'woocommerce_process_shop_order_meta' ) ) { - return true; - } - - // Are we editing an order or subscription tied to PPEC? - $order_id = wc_clean( wp_unslash( $_GET['id'] ?? $_GET['post'] ?? $_POST['post_ID'] ?? '' ) ); - if ( $order_id ) { - $order = wc_get_order( $order_id ); - return ( $order && PPECHelper::PPEC_GATEWAY_ID === $order->get_payment_method() ); - } - - // Are we on the WC > Subscriptions screen? - /** - * Class exist in WooCommerce. - * - * @psalm-suppress UndefinedClass - */ - $post_type_or_page = class_exists( OrderUtil::class ) && OrderUtil::custom_orders_table_usage_is_enabled() - ? wc_clean( wp_unslash( $_GET['page'] ?? '' ) ) - : wc_clean( wp_unslash( $_GET['post_type'] ?? $_POST['post_type'] ?? '' ) ); - if ( $post_type_or_page === 'shop_subscription' || $post_type_or_page === 'wc-orders--shop_subscription' ) { - return true; - } - - return false; - } } From 33b27e5ee423394932948231c951ef64e1dc7e4b Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 23 Jul 2025 14:45:00 +0200 Subject: [PATCH 54/63] Add condition to display button if apple session and can make payments --- modules/ppcp-applepay/resources/js/boot-block.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-applepay/resources/js/boot-block.js b/modules/ppcp-applepay/resources/js/boot-block.js index eff026251..e75a9f550 100644 --- a/modules/ppcp-applepay/resources/js/boot-block.js +++ b/modules/ppcp-applepay/resources/js/boot-block.js @@ -105,7 +105,8 @@ registerExpressPaymentMethod( { content: , edit: , ariaLabel: buttonData.title, - canMakePayment: () => buttonData.enabled, + canMakePayment: () => + buttonData.enabled && window.ApplePaySession?.canMakePayments(), supports: { features, style: [ 'height', 'borderRadius' ], From 0385aad3017da0d6de48645aadd62d945fc12e62 Mon Sep 17 00:00:00 2001 From: Himad M Date: Wed, 23 Jul 2025 16:32:22 -0400 Subject: [PATCH 55/63] Add optional chaining to address properties --- modules/ppcp-blocks/resources/js/Helper/Address.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-blocks/resources/js/Helper/Address.js b/modules/ppcp-blocks/resources/js/Helper/Address.js index 6e6338f3c..a4a6fa99d 100644 --- a/modules/ppcp-blocks/resources/js/Helper/Address.js +++ b/modules/ppcp-blocks/resources/js/Helper/Address.js @@ -27,7 +27,7 @@ export const paypalAddressToWc = ( address ) => { admin_area_2: 'city', postal_code: 'postcode', }; - if ( address.city ) { + if ( address?.city ) { // address not from API, such as onShippingChange map = { country_code: 'country', @@ -38,7 +38,7 @@ export const paypalAddressToWc = ( address ) => { } const result = {}; Object.entries( map ).forEach( ( [ paypalKey, wcKey ] ) => { - if ( address[ paypalKey ] ) { + if ( address?.[ paypalKey ] ) { result[ wcKey ] = address[ paypalKey ]; } } ); From 344726ca95439243fa77b8f02e52d4b6e2ccfa68 Mon Sep 17 00:00:00 2001 From: Diego Curbelo Date: Mon, 28 Jul 2025 10:25:10 -0300 Subject: [PATCH 56/63] Update changelog with release date --- changelog.txt | 4 ++-- readme.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/changelog.txt b/changelog.txt index d951a2276..6a44ae2af 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,6 @@ *** Changelog *** -= 3.0.8 - XXXX-XX-XX = += 3.0.8 - 2025-07-28 = * Enhancement - Migration from Legacy Settings to New Settings as opt-in via banner & button #3491 * Enhancement - Replace call to `billing-agreements/agreement-tokens` with checking the capabilities for Reference Transactions #3495 * Enhancement - Add Fastlane 3D Secure support #3493 @@ -16,7 +16,7 @@ = 3.0.7 - 2025-07-01 = * Enhancement - Remove `application_context` in favor of `experience_context` object #3431 - **NOTE**: If you were modifying the `application_context` object programmatically, you may need to update your code to utilize `experience_context` for your customizations. + **NOTE**: If you were modifying the `application_context` object programmatically, you may need to update your code to utilize `experience_context` for your customizations. * Enhancement - Add Contact Module feature * Enhancement - Add WooCommerce Tracks integration * Enhancement - Onboarding notification for Firefox browser #3433 diff --git a/readme.txt b/readme.txt index f9a6da400..7d005e851 100644 --- a/readme.txt +++ b/readme.txt @@ -156,7 +156,7 @@ If you encounter issues with the PayPal buttons not appearing after an update, p == Changelog == -= 3.0.8 - XXXX-XX-XX = += 3.0.8 - 2025-07-28 = * Enhancement - Migration from Legacy Settings to New Settings as opt-in via banner & button #3491 * Enhancement - Replace call to `billing-agreements/agreement-tokens` with checking the capabilities for Reference Transactions #3495 * Enhancement - Add Fastlane 3D Secure support #3493 @@ -172,7 +172,7 @@ If you encounter issues with the PayPal buttons not appearing after an update, p = 3.0.7 - 2025-07-01 = * Enhancement - Remove `application_context` in favor of `experience_context` object #3431 - **NOTE**: If you were modifying the `application_context` object programmatically, you may need to update your code to utilize `experience_context` for your customizations. + **NOTE**: If you were modifying the `application_context` object programmatically, you may need to update your code to utilize `experience_context` for your customizations. * Enhancement - Add Contact Module feature * Enhancement - Add WooCommerce Tracks integration * Enhancement - Onboarding notification for Firefox browser #3433 From 9cfd827e27ca1fe86b9764b8b45fff7ac080a981 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Tue, 29 Jul 2025 01:16:48 +0200 Subject: [PATCH 57/63] =?UTF-8?q?=E2=9C=A8=20Add=20buttons=20to=20copy=20m?= =?UTF-8?q?erchant=20credentials=20in=20the=20Settings=20tab?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../screens/settings/_controls.scss | 45 +++++++- .../Controls/ControlStaticValue.js | 32 +++++- .../ReusableComponents/Elements/CopyButton.js | 106 ++++++++++++++++++ .../Components/Settings/ConnectionStatus.js | 12 +- .../resources/js/hooks/useCopyToClipboard.js | 38 +++++++ 5 files changed, 223 insertions(+), 10 deletions(-) create mode 100644 modules/ppcp-settings/resources/js/Components/ReusableComponents/Elements/CopyButton.js create mode 100644 modules/ppcp-settings/resources/js/hooks/useCopyToClipboard.js diff --git a/modules/ppcp-settings/resources/css/components/screens/settings/_controls.scss b/modules/ppcp-settings/resources/css/components/screens/settings/_controls.scss index 8bc05b2ef..5de76e1b8 100644 --- a/modules/ppcp-settings/resources/css/components/screens/settings/_controls.scss +++ b/modules/ppcp-settings/resources/css/components/screens/settings/_controls.scss @@ -1,9 +1,50 @@ .ppcp--static-value { @include font(13, 26, 400); - white-space: nowrap; - overflow: hidden; text-overflow: ellipsis; + + &.ppcp--static-value-with-copy { + display: inline-flex; + align-items: flex-start; + gap: 6px; + overflow: visible; + + .ppcp--static-value-text { + flex: 1; + min-width: 0; + white-space: normal; + word-break: break-all; + overflow-wrap: break-word; + max-width: 37ch; + } + } + + &:not(.ppcp--static-value-with-copy) { + overflow: hidden; + } +} + +.ppcp-copy-button { + display: flex; + border: none; + background: transparent; + color: $color-gray-700; + cursor: pointer; + transition: color 0.2s ease; + flex-shrink: 0; + + &:hover:not(:disabled) { + color: $color-blueberry; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + svg { + fill: currentColor; + } } // Fix the checkbox layout (add gap between checkbox and label). diff --git a/modules/ppcp-settings/resources/js/Components/ReusableComponents/Controls/ControlStaticValue.js b/modules/ppcp-settings/resources/js/Components/ReusableComponents/Controls/ControlStaticValue.js index 7f92afc4b..19297c34d 100644 --- a/modules/ppcp-settings/resources/js/Components/ReusableComponents/Controls/ControlStaticValue.js +++ b/modules/ppcp-settings/resources/js/Components/ReusableComponents/Controls/ControlStaticValue.js @@ -1,9 +1,31 @@ import { Action } from '../Elements'; +import classNames from 'classnames'; +import CopyButton from '../Elements/CopyButton'; -const ControlStaticValue = ( { value } ) => ( - -
{ value }
-
-); +const ControlStaticValue = ( { + value, + showCopy = false, + copyButtonProps = {}, + className, + ...props +} ) => { + const wrapperClass = classNames( 'ppcp--static-value', { + 'ppcp--static-value-with-copy': showCopy, + 'ppcp--has-copy': showCopy, + } ); + + return ( + + { showCopy ? ( +
+
{ value }
+ +
+ ) : ( +
{ value }
+ ) } +
+ ); +}; export default ControlStaticValue; diff --git a/modules/ppcp-settings/resources/js/Components/ReusableComponents/Elements/CopyButton.js b/modules/ppcp-settings/resources/js/Components/ReusableComponents/Elements/CopyButton.js new file mode 100644 index 000000000..7646bf48a --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/ReusableComponents/Elements/CopyButton.js @@ -0,0 +1,106 @@ +import { __ } from '@wordpress/i18n'; +import { speak } from '@wordpress/a11y'; +import { Tooltip } from '@wordpress/components'; +import { SVG, Path } from '@wordpress/primitives'; +import classNames from 'classnames'; +import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard'; + +const COPY_CONFIRMATION_DURATION = 1000; + +/** + * Copy button component with tooltip and icon transition + * @param {Object} props - Component props + * @param {string} props.value - The text value to copy to clipboard + * @param {string} [props.className] - Additional CSS class names + * @param {string} [props.ariaLabel] - Custom aria-label for the button + */ +const CopyButton = ( { value, className, ariaLabel, ...props } ) => { + const { copy, copied, error } = useCopyToClipboard( { + successDuration: COPY_CONFIRMATION_DURATION, + } ); + + const buttonClass = classNames( 'ppcp-copy-button', className ); + + const getTooltipText = () => { + if ( copied ) { + return __( 'Copied!', 'woocommerce-paypal-payments' ); + } + if ( error ) { + return __( 'Failed to copy', 'woocommerce-paypal-payments' ); + } + return __( 'Copy to clipboard', 'woocommerce-paypal-payments' ); + }; + + const handleCopy = async () => { + if ( ! value ) { + return; + } + await copy( value ); + + if ( copied ) { + speak( + __( 'Copied to clipboard', 'woocommerce-paypal-payments' ), + 'assertive' + ); + return; + } + + if ( error ) { + speak( + __( + 'Failed to copy to clipboard', + 'woocommerce-paypal-payments' + ), + 'assertive' + ); + } + }; + + return ( + + + + ); +}; + +const CopyIcon = () => ( + + + +); + +const CheckIcon = () => ( + + + +); + +export default CopyButton; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Settings/ConnectionStatus.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Settings/ConnectionStatus.js index 172e1cd40..d7a38e3a4 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Settings/ConnectionStatus.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Settings/ConnectionStatus.js @@ -37,17 +37,23 @@ const ConnectionStatus = () => { title={ __( 'Merchant ID', 'woocommerce-paypal-payments' ) } className="ppcp--no-gap" > - + - + - + ); diff --git a/modules/ppcp-settings/resources/js/hooks/useCopyToClipboard.js b/modules/ppcp-settings/resources/js/hooks/useCopyToClipboard.js new file mode 100644 index 000000000..f76f8d039 --- /dev/null +++ b/modules/ppcp-settings/resources/js/hooks/useCopyToClipboard.js @@ -0,0 +1,38 @@ +import { useState, useRef } from '@wordpress/element'; + +/** + * Custom hook for handling copy to clipboard functionality + * + * @param {Object} options - Configuration options + * @param {number} options.successDuration - How long to show success state (ms) + * @return {Object} Copy functionality and state + */ +export const useCopyToClipboard = ( options = {} ) => { + const { successDuration = 1000 } = options; + const [ copied, setCopied ] = useState( false ); + const [ error, setError ] = useState( false ); + const timerRef = useRef( null ); + + const copy = async ( text ) => { + try { + await navigator.clipboard.writeText( text ); + + clearTimeout( timerRef.current ); + setCopied( true ); + setError( false ); + + timerRef.current = setTimeout( + () => setCopied( false ), + successDuration + ); + } catch ( err ) { + console.error( 'Copy failed:', err ); + setError( true ); + setCopied( false ); + } + }; + + return { copy, copied, error }; +}; + +export default useCopyToClipboard; From bdaa16f3b81b58af4ad23d1da83aedf45284faf4 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Tue, 29 Jul 2025 19:21:27 +0400 Subject: [PATCH 58/63] Fix: Prevent early `is_enabled()` check for Google Pay button in new UI In the new UI, the Google Pay button availability is determined by gateway enablement and location selection, not a global setting. The `is_enabled()` check depends on context functions (like `is_cart()`), which return `false` too early during block registration. This commit skips the `is_enabled()` check when the new UI is active to avoid incorrect context evaluation and ensure the Google Pay button is properly registered. --- modules/ppcp-googlepay/src/GooglepayModule.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-googlepay/src/GooglepayModule.php b/modules/ppcp-googlepay/src/GooglepayModule.php index 166a42884..345fb21eb 100644 --- a/modules/ppcp-googlepay/src/GooglepayModule.php +++ b/modules/ppcp-googlepay/src/GooglepayModule.php @@ -17,6 +17,7 @@ use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; use WooCommerce\PayPalCommerce\Googlepay\Endpoint\UpdatePaymentDataEndpoint; use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmProductStatus; use WooCommerce\PayPalCommerce\Googlepay\Helper\AvailabilityNotice; +use WooCommerce\PayPalCommerce\Settings\SettingsModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; @@ -149,9 +150,11 @@ class GooglepayModule implements ServiceModule, ExtendingModule, ExecutableModul add_action( 'woocommerce_blocks_payment_method_type_registration', function( PaymentMethodRegistry $payment_method_registry ) use ( $c, $button ): void { - if ( $button->is_enabled() ) { - $payment_method_registry->register( $c->get( 'googlepay.blocks-payment-method' ) ); + if ( SettingsModule::should_use_the_old_ui() && ! $button->is_enabled() ) { + return; } + + $payment_method_registry->register( $c->get( 'googlepay.blocks-payment-method' ) ); } ); From 0b75c7fab859cc01410a89926cc3ca3956121a37 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Thu, 31 Jul 2025 10:27:06 +0200 Subject: [PATCH 59/63] =?UTF-8?q?=F0=9F=90=9B=20Fix=20endless=20redirect?= =?UTF-8?q?=20bug=20for=20the=20PayPal=20button?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-wc-gateway/src/Endpoint/ReturnUrlEndpoint.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/ppcp-wc-gateway/src/Endpoint/ReturnUrlEndpoint.php b/modules/ppcp-wc-gateway/src/Endpoint/ReturnUrlEndpoint.php index 39b69ba5d..b53bc5c5d 100644 --- a/modules/ppcp-wc-gateway/src/Endpoint/ReturnUrlEndpoint.php +++ b/modules/ppcp-wc-gateway/src/Endpoint/ReturnUrlEndpoint.php @@ -109,6 +109,13 @@ class ReturnUrlEndpoint { } } + // Replace session order for approved/completed orders. + if ( $order->status()->is( OrderStatus::APPROVED ) + || $order->status()->is( OrderStatus::COMPLETED ) + ) { + $this->session_handler->replace_order( $order ); + } + $wc_order_id = (int) $order->purchase_units()[0]->custom_id(); if ( ! $wc_order_id ) { // We cannot finish processing here without WC order, but at least go into the continuation mode. From d8dbe206d2cd7e55e58a604f7eb240db17c2b210 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Thu, 31 Jul 2025 16:17:40 +0200 Subject: [PATCH 60/63] =?UTF-8?q?=F0=9F=93=A6=20Prepare=203.0.9-rc1=20rele?= =?UTF-8?q?ase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog.txt | 3 +++ package.json | 2 +- readme.txt | 5 ++++- woocommerce-paypal-payments.php | 4 ++-- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/changelog.txt b/changelog.txt index 6a44ae2af..c80a05d89 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,8 @@ *** Changelog *** += 3.0.9 - XXXX-XX-XX = +* Fix - Payment via "Proceed to PayPal" may result in a redirect loop #3570 + = 3.0.8 - 2025-07-28 = * Enhancement - Migration from Legacy Settings to New Settings as opt-in via banner & button #3491 * Enhancement - Replace call to `billing-agreements/agreement-tokens` with checking the capabilities for Reference Transactions #3495 diff --git a/package.json b/package.json index 092b50055..5878f5671 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "woocommerce-paypal-payments", - "version": "3.0.8", + "version": "3.0.9", "description": "WooCommerce PayPal Payments", "repository": "https://github.com/woocommerce/woocommerce-paypal-payments", "license": "GPL-2.0", diff --git a/readme.txt b/readme.txt index 7d005e851..8b06b14ef 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: woocommerce, paypal, payments, ecommerce, credit card Requires at least: 6.5 Tested up to: 6.8 Requires PHP: 7.4 -Stable tag: 3.0.8 +Stable tag: 3.0.9 License: GPLv2 License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -156,6 +156,9 @@ If you encounter issues with the PayPal buttons not appearing after an update, p == Changelog == += 3.0.9 - XXXX-XX-XX = +* Fix - Payment via "Proceed to PayPal" may result in a redirect loop #3570 + = 3.0.8 - 2025-07-28 = * Enhancement - Migration from Legacy Settings to New Settings as opt-in via banner & button #3491 * Enhancement - Replace call to `billing-agreements/agreement-tokens` with checking the capabilities for Reference Transactions #3495 diff --git a/woocommerce-paypal-payments.php b/woocommerce-paypal-payments.php index d23173970..edc0afa40 100644 --- a/woocommerce-paypal-payments.php +++ b/woocommerce-paypal-payments.php @@ -3,7 +3,7 @@ * Plugin Name: WooCommerce PayPal Payments * Plugin URI: https://woocommerce.com/products/woocommerce-paypal-payments/ * Description: PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage. - * Version: 3.0.8 + * Version: 3.0.9 * Author: PayPal * Author URI: https://paypal.com/ * License: GPL-2.0 @@ -27,7 +27,7 @@ define( 'PAYPAL_API_URL', 'https://api-m.paypal.com' ); define( 'PAYPAL_URL', 'https://www.paypal.com' ); define( 'PAYPAL_SANDBOX_API_URL', 'https://api-m.sandbox.paypal.com' ); define( 'PAYPAL_SANDBOX_URL', 'https://www.sandbox.paypal.com' ); -define( 'PAYPAL_INTEGRATION_DATE', '2025-07-21' ); +define( 'PAYPAL_INTEGRATION_DATE', '2025-07-31' ); define( 'PPCP_PAYPAL_BN_CODE', 'Woo_PPCP' ); ! defined( 'CONNECT_WOO_CLIENT_ID' ) && define( 'CONNECT_WOO_CLIENT_ID', 'AcCAsWta_JTL__OfpjspNyH7c1GGHH332fLwonA5CwX4Y10mhybRZmHLA0GdRbwKwjQIhpDQy0pluX_P' ); From 09ef2befd772a30b4bb205735c279be0c9f22b5a Mon Sep 17 00:00:00 2001 From: Diego Curbelo Date: Thu, 31 Jul 2025 13:19:09 -0300 Subject: [PATCH 61/63] Update changelog --- changelog.txt | 2 +- readme.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index c80a05d89..4f41e7cab 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,6 @@ *** Changelog *** -= 3.0.9 - XXXX-XX-XX = += 3.0.9 - 2025-07-31 = * Fix - Payment via "Proceed to PayPal" may result in a redirect loop #3570 = 3.0.8 - 2025-07-28 = diff --git a/readme.txt b/readme.txt index 8b06b14ef..05ab691f4 100644 --- a/readme.txt +++ b/readme.txt @@ -156,7 +156,7 @@ If you encounter issues with the PayPal buttons not appearing after an update, p == Changelog == -= 3.0.9 - XXXX-XX-XX = += 3.0.9 - 2025-07-31 = * Fix - Payment via "Proceed to PayPal" may result in a redirect loop #3570 = 3.0.8 - 2025-07-28 = From b631692bc60f3d30cd337e0a3fc2f17d9bf4416d Mon Sep 17 00:00:00 2001 From: Himad M Date: Fri, 1 Aug 2025 15:31:46 -0400 Subject: [PATCH 62/63] Add polling mechanism for renderer wrapper --- .../ContextBootstrap/MessagesBootstrap.js | 23 +++++++------ .../js/modules/Helper/WaitForElement.js | 32 +++++++++++++++++++ 2 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 modules/ppcp-button/resources/js/modules/Helper/WaitForElement.js diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/MessagesBootstrap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/MessagesBootstrap.js index be9b654bb..f74a86f9c 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/MessagesBootstrap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/MessagesBootstrap.js @@ -1,5 +1,6 @@ import { setVisible } from '../Helper/Hiding'; import MessageRenderer from '../Renderer/MessageRenderer'; +import { waitForElement } from '../Helper/WaitForElement'; class MessagesBootstrap { constructor( gateway, messageRenderer ) { @@ -93,17 +94,21 @@ class MessagesBootstrap { render() { this.renderers.forEach( ( renderer ) => { - const shouldShow = this.shouldShow( renderer ); - setVisible( renderer.config.wrapper, shouldShow ); - if ( ! shouldShow ) { - return; - } + waitForElement( renderer.config.wrapper ) + .then( () => { + const shouldShow = this.shouldShow( renderer ); + setVisible( renderer.config.wrapper, shouldShow ); + if ( ! shouldShow ) { + return; + } - if ( ! renderer.shouldRender() ) { - return; - } + if ( ! renderer.shouldRender() ) { + return; + } - renderer.renderWithAmount( this.lastAmount ); + renderer.renderWithAmount( this.lastAmount ); + } ) + .catch( ( err ) => console.error( err ) ); } ); } } diff --git a/modules/ppcp-button/resources/js/modules/Helper/WaitForElement.js b/modules/ppcp-button/resources/js/modules/Helper/WaitForElement.js new file mode 100644 index 000000000..3c6fab6a0 --- /dev/null +++ b/modules/ppcp-button/resources/js/modules/Helper/WaitForElement.js @@ -0,0 +1,32 @@ +/** + * Waits for a DOM element using setTimeout polling + * @param {string} selector - CSS selector for the element + * @param {number} timeout - Maximum time to wait in milliseconds (default: 3000) + * @param {number} interval - Polling interval in milliseconds (default: 100) + * @return {Promise} - Resolves with the element or rejects if timeout + */ +export function waitForElement( selector, timeout = 3000, interval = 100 ) { + return new Promise( ( resolve, reject ) => { + const timeoutId = setTimeout( () => { + clearInterval( intervalId ); + reject( `Element "${ selector }" not found within ${ timeout }ms` ); + }, timeout ); + + const element = document.querySelector( selector ); + if ( element ) { + clearTimeout( timeoutId ); + resolve( element ); + return; + } + + const intervalId = setInterval( () => { + const el = document.querySelector( selector ); + + if ( el ) { + clearTimeout( timeoutId ); + clearInterval( intervalId ); + resolve( el ); + } + }, interval ); + } ); +} From 0eb932a73ed7311ab40ca48c71504bb4528faa6c Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Tue, 5 Aug 2025 11:45:23 +0400 Subject: [PATCH 63/63] use boolean as option value --- modules/ppcp-settings/src/Ajax/SwitchSettingsUiEndpoint.php | 2 +- modules/ppcp-settings/src/Data/Definition/TodosDefinition.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-settings/src/Ajax/SwitchSettingsUiEndpoint.php b/modules/ppcp-settings/src/Ajax/SwitchSettingsUiEndpoint.php index a1bc218d0..62f2a5eaf 100644 --- a/modules/ppcp-settings/src/Ajax/SwitchSettingsUiEndpoint.php +++ b/modules/ppcp-settings/src/Ajax/SwitchSettingsUiEndpoint.php @@ -72,7 +72,7 @@ class SwitchSettingsUiEndpoint { $this->settings_data_migration->migrate(); - update_option( self::OPTION_NAME_MIGRATION_IS_DONE, 'yes' ); + update_option( self::OPTION_NAME_MIGRATION_IS_DONE, true ); wp_send_json_success(); } catch ( Exception $error ) { wp_send_json_error( array( 'message' => $error->getMessage() ), 500 ); diff --git a/modules/ppcp-settings/src/Data/Definition/TodosDefinition.php b/modules/ppcp-settings/src/Data/Definition/TodosDefinition.php index a8018842c..6c5bc4b0e 100644 --- a/modules/ppcp-settings/src/Data/Definition/TodosDefinition.php +++ b/modules/ppcp-settings/src/Data/Definition/TodosDefinition.php @@ -256,7 +256,7 @@ class TodosDefinition { * @return bool True if the migration is marked as done, false otherwise. */ protected function is_settings_migration_done(): bool { - return 'yes' === get_option( SwitchSettingsUiEndpoint::OPTION_NAME_MIGRATION_IS_DONE ); + return '1' === get_option( SwitchSettingsUiEndpoint::OPTION_NAME_MIGRATION_IS_DONE ); } /**