🔀 Merge branch 'trunk'

This commit is contained in:
Philipp Stracker 2025-03-03 15:23:05 +01:00
commit 748c7f4d10
No known key found for this signature in database
9 changed files with 213 additions and 50 deletions

View file

@ -94,6 +94,45 @@ function as_schedule_single_action( $timestamp, $hook, $args = array(), $group =
return 0;
}
/**
* Retrieves the number of times a filter has been applied during the current request.
*
* @since 6.1.0
*
* @global int[] $wp_filters Stores the number of times each filter was triggered.
*
* @param string $hook_name The name of the filter hook.
* @return int The number of times the filter hook has been applied.
*/
function did_filter( $hook_name ) {
return 0;
}
/**
* Returns whether or not a filter hook is currently being processed.
*
* The function current_filter() only returns the most recent filter being executed.
* did_filter() returns the number of times a filter has been applied during
* the current request.
*
* This function allows detection for any filter currently being executed
* (regardless of whether it's the most recent filter to fire, in the case of
* hooks called from hook callbacks) to be verified.
*
* @since 3.9.0
*
* @see current_filter()
* @see did_filter()
* @global string[] $wp_current_filter Current filter.
*
* @param string|null $hook_name Optional. Filter hook to check. Defaults to null,
* which checks if any filter is currently being run.
* @return bool Whether the filter is currently in the stack.
*/
function doing_filter( $hook_name = null ) {
return false;
}
/**
* HTML API: WP_HTML_Tag_Processor class
*/

View file

@ -91,9 +91,12 @@ return function ( string $root_dir ): iterable {
$modules[] = ( require "$modules_dir/ppcp-axo-block/module.php" )();
}
$show_new_ux = '1' === get_option( 'woocommerce-ppcp-is-new-merchant' );
$preview_new_ux = '1' === getenv( 'PCP_SETTINGS_ENABLED' );
if ( apply_filters(
'woocommerce.feature-flags.woocommerce_paypal_payments.settings_enabled',
getenv( 'PCP_SETTINGS_ENABLED' ) === '1'
$show_new_ux || $preview_new_ux
) ) {
$modules[] = ( require "$modules_dir/ppcp-settings/module.php" )();
}

View file

@ -172,6 +172,16 @@ return array(
$container->get( 'settings.data.settings' ),
$subscription_map_helper->map()
),
/**
* We need to pass the PaymentSettings model instance to use it in some helpers.
* Once the new settings module is permanently enabled,
* this model can be passed as a dependency to the appropriate helper classes.
* For now, we must pass it this way to avoid errors when the new settings module is disabled.
*/
new SettingsMap(
$container->get( 'settings.data.payment' ),
array()
),
);
},
'compat.settings.settings_map_helper' => static function( ContainerInterface $container ) : SettingsMapHelper {
@ -180,7 +190,8 @@ return array(
$container->get( 'compat.settings.styling_map_helper' ),
$container->get( 'compat.settings.settings_tab_map_helper' ),
$container->get( 'compat.settings.subscription_map_helper' ),
$container->get( 'compat.settings.general_map_helper' )
$container->get( 'compat.settings.general_map_helper' ),
$container->get( 'wcgateway.settings.admin-settings-enabled' )
);
},
'compat.settings.styling_map_helper' => static function() : StylingSettingsMapHelper {

View file

@ -10,7 +10,9 @@ declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\Compat\Settings;
use RuntimeException;
use WooCommerce\PayPalCommerce\Settings\Data\AbstractDataModel;
use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings;
use WooCommerce\PayPalCommerce\Settings\Data\PaymentSettings;
use WooCommerce\PayPalCommerce\Settings\Data\SettingsModel;
use WooCommerce\PayPalCommerce\Settings\Data\StylingSettings;
@ -72,6 +74,13 @@ class SettingsMapHelper {
*/
protected GeneralSettingsMapHelper $general_settings_map_helper;
/**
* Whether the new settings module is enabled.
*
* @var bool
*/
protected bool $new_settings_module_enabled;
/**
* Constructor.
*
@ -80,6 +89,7 @@ class SettingsMapHelper {
* @param SettingsTabMapHelper $settings_tab_map_helper A helper for mapping the old/new settings tab settings.
* @param SubscriptionSettingsMapHelper $subscription_map_helper A helper for mapping old and new subscription settings.
* @param GeneralSettingsMapHelper $general_settings_map_helper A helper for mapping old and new general settings.
* @param bool $new_settings_module_enabled Whether the new settings module is enabled.
* @throws RuntimeException When an old key has multiple mappings.
*/
public function __construct(
@ -87,7 +97,8 @@ class SettingsMapHelper {
StylingSettingsMapHelper $styling_settings_map_helper,
SettingsTabMapHelper $settings_tab_map_helper,
SubscriptionSettingsMapHelper $subscription_map_helper,
GeneralSettingsMapHelper $general_settings_map_helper
GeneralSettingsMapHelper $general_settings_map_helper,
bool $new_settings_module_enabled
) {
$this->validate_settings_map( $settings_map );
$this->settings_map = $settings_map;
@ -95,6 +106,7 @@ class SettingsMapHelper {
$this->settings_tab_map_helper = $settings_tab_map_helper;
$this->subscription_map_helper = $subscription_map_helper;
$this->general_settings_map_helper = $general_settings_map_helper;
$this->new_settings_module_enabled = $new_settings_module_enabled;
}
/**
@ -124,6 +136,10 @@ class SettingsMapHelper {
* @return mixed|null The value of the mapped setting, or null if not found.
*/
public function mapped_value( string $old_key ) {
if ( ! $this->new_settings_module_enabled ) {
return null;
}
$this->ensure_map_initialized();
if ( ! isset( $this->key_to_model[ $old_key ] ) ) {
return null;
@ -147,6 +163,10 @@ class SettingsMapHelper {
* @return bool True if the key exists in the new settings, false otherwise.
*/
public function has_mapped_key( string $old_key ) : bool {
if ( ! $this->new_settings_module_enabled ) {
return false;
}
$this->ensure_map_initialized();
return isset( $this->key_to_model[ $old_key ] );
@ -169,7 +189,11 @@ class SettingsMapHelper {
switch ( true ) {
case $model instanceof StylingSettings:
return $this->styling_settings_map_helper->mapped_value( $old_key, $this->model_cache[ $model_id ] );
return $this->styling_settings_map_helper->mapped_value(
$old_key,
$this->model_cache[ $model_id ],
$this->get_payment_settings_model()
);
case $model instanceof GeneralSettings:
return $this->general_settings_map_helper->mapped_value( $old_key, $this->model_cache[ $model_id ] );
@ -217,4 +241,23 @@ class SettingsMapHelper {
}
}
}
/**
* Retrieves the PaymentSettings model instance.
*
* Once the new settings module is permanently enabled,
* this model can be passed as a dependency to the appropriate helper classes.
* For now, we must pass it this way to avoid errors when the new settings module is disabled.
*
* @return AbstractDataModel|null
*/
protected function get_payment_settings_model() : ?AbstractDataModel {
foreach ( $this->settings_map as $settings_map_instance ) {
if ( $settings_map_instance->get_model() instanceof PaymentSettings ) {
return $settings_map_instance->get_model();
}
}
return null;
}
}

View file

@ -10,7 +10,11 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Compat\Settings;
use RuntimeException;
use WooCommerce\PayPalCommerce\Applepay\ApplePayGateway;
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
use WooCommerce\PayPalCommerce\Googlepay\GooglePayGateway;
use WooCommerce\PayPalCommerce\Settings\Data\AbstractDataModel;
use WooCommerce\PayPalCommerce\Settings\Data\PaymentSettings;
use WooCommerce\PayPalCommerce\Settings\DTO\LocationStylingDTO;
/**
@ -23,7 +27,7 @@ class StylingSettingsMapHelper {
use ContextTrait;
protected const BUTTON_NAMES = array( 'ppcp-googlepay', 'ppcp-applepay', 'pay-later' );
protected const BUTTON_NAMES = array( GooglePayGateway::ID, ApplePayGateway::ID, 'pay-later' );
/**
* Maps old setting keys to new setting style names.
@ -64,12 +68,13 @@ class StylingSettingsMapHelper {
/**
* Retrieves the value of a mapped key from the new settings.
*
* @param string $old_key The key from the legacy settings.
* @param LocationStylingDTO[] $styling_models The list of location styling models.
* @param string $old_key The key from the legacy settings.
* @param LocationStylingDTO[] $styling_models The list of location styling models.
* @param AbstractDataModel|null $payment_settings The payment settings model.
*
* @return mixed The value of the mapped setting, (null if not found).
*/
public function mapped_value( string $old_key, array $styling_models ) {
public function mapped_value( string $old_key, array $styling_models, ?AbstractDataModel $payment_settings ) {
switch ( $old_key ) {
case 'smart_button_locations':
return $this->mapped_smart_button_locations_value( $styling_models );
@ -81,16 +86,16 @@ class StylingSettingsMapHelper {
return $this->mapped_pay_later_button_locations_value( $styling_models );
case 'disable_funding':
return $this->mapped_disabled_funding_value( $styling_models );
return $this->mapped_disabled_funding_value( $styling_models, $payment_settings );
case 'googlepay_button_enabled':
return $this->mapped_button_enabled_value( $styling_models, 'ppcp-googlepay' );
return $this->mapped_button_enabled_value( $styling_models, GooglePayGateway::ID, $payment_settings );
case 'applepay_button_enabled':
return $this->mapped_button_enabled_value( $styling_models, 'ppcp-applepay' );
return $this->mapped_button_enabled_value( $styling_models, ApplePayGateway::ID, $payment_settings );
case 'pay_later_button_enabled':
return $this->mapped_button_enabled_value( $styling_models, 'pay-later' );
return $this->mapped_button_enabled_value( $styling_models, 'pay-later', $payment_settings );
default:
foreach ( $this->locations_map() as $old_location_name => $new_location_name ) {
@ -224,52 +229,80 @@ class StylingSettingsMapHelper {
/**
* Retrieves the mapped disabled funding value from the new settings.
*
* @param LocationStylingDTO[] $styling_models The list of location styling models.
* @param LocationStylingDTO[] $styling_models The list of location styling models.
* @param AbstractDataModel|null $payment_settings The payment settings model.
* @return array|null The list of disabled funding, or null if none are disabled.
*/
protected function mapped_disabled_funding_value( array $styling_models ): ?array {
protected function mapped_disabled_funding_value( array $styling_models, ?AbstractDataModel $payment_settings ): ?array {
if ( is_null( $payment_settings ) ) {
return null;
}
$disabled_funding = array();
$locations_to_context_map = $this->current_context_to_new_button_location_map();
$current_context = $locations_to_context_map[ $this->context() ] ?? '';
assert( $payment_settings instanceof PaymentSettings );
foreach ( $styling_models as $model ) {
if ( $model->location !== $current_context || in_array( 'venmo', $model->methods, true ) ) {
continue;
if ( $model->location === $current_context ) {
if ( ! in_array( 'venmo', $model->methods, true ) || ! $payment_settings->get_venmo_enabled() ) {
$disabled_funding[] = 'venmo';
}
}
$disabled_funding[] = 'venmo';
return $disabled_funding;
}
return null;
return $disabled_funding;
}
/**
* Retrieves the mapped enabled or disabled button value from the new settings.
*
* @param LocationStylingDTO[] $styling_models The list of location styling models.
* @param string $button_name The button name (see {@link self::BUTTON_NAMES}).
* @param LocationStylingDTO[] $styling_models The list of location styling models.
* @param string $button_name The button name (see {@link self::BUTTON_NAMES}).
* @param AbstractDataModel|null $payment_settings The payment settings model.
* @return int The enabled (1) or disabled (0) state.
* @throws RuntimeException If an invalid button name is provided.
*/
protected function mapped_button_enabled_value( array $styling_models, string $button_name ): ?int {
protected function mapped_button_enabled_value( array $styling_models, string $button_name, ?AbstractDataModel $payment_settings ): ?int {
if ( is_null( $payment_settings ) ) {
return null;
}
if ( ! in_array( $button_name, self::BUTTON_NAMES, true ) ) {
throw new RuntimeException( 'Wrong button name is provided.' );
}
$locations_to_context_map = $this->current_context_to_new_button_location_map();
$current_context = $locations_to_context_map[ $this->context() ] ?? '';
assert( $payment_settings instanceof PaymentSettings );
foreach ( $styling_models as $model ) {
if ( ! $model->enabled
|| $model->location !== $current_context
|| ! in_array( $button_name, $model->methods, true )
) {
continue;
if ( $model->enabled && $model->location === $current_context ) {
if ( in_array( $button_name, $model->methods, true ) && $payment_settings->is_method_enabled( $button_name ) ) {
return 1;
}
}
}
return 1;
if ( $current_context === 'classic_checkout' ) {
/**
* Outputs an inline CSS style that hides the Google Pay gateway (on Classic Checkout)
* In case if the button is disabled from the styling settings but the gateway itself is enabled.
*
* @return void
*/
add_action(
'woocommerce_paypal_payments_checkout_button_render',
static function (): void {
?>
<style data-hide-gateway='<?php echo esc_attr( GooglePayGateway::ID ); ?>'>
.wc_payment_method.payment_method_ppcp-googlepay {
display: none;
}
</style>
<?php
}
);
}
return 0;

View file

@ -81,22 +81,23 @@ const GooglePayComponent = ( { isEditing, buttonAttributes } ) => {
};
const features = [ 'products' ];
registerExpressPaymentMethod( {
name: buttonData.id,
title: `PayPal - ${ buttonData.title }`,
description: __(
'Eligible users will see the PayPal button.',
'woocommerce-paypal-payments'
),
gatewayId: 'ppcp-gateway',
label: <div dangerouslySetInnerHTML={ { __html: buttonData.title } } />,
content: <GooglePayComponent isEditing={ false } />,
edit: <GooglePayComponent isEditing={ true } />,
ariaLabel: buttonData.title,
canMakePayment: () => buttonData.enabled,
supports: {
features,
style: [ 'height', 'borderRadius' ],
},
} );
if ( buttonConfig?.is_enabled ) {
registerExpressPaymentMethod( {
name: buttonData.id,
title: `PayPal - ${ buttonData.title }`,
description: __(
'Eligible users will see the PayPal button.',
'woocommerce-paypal-payments'
),
gatewayId: 'ppcp-gateway',
label: <div dangerouslySetInnerHTML={ { __html: buttonData.title } } />,
content: <GooglePayComponent isEditing={ false } />,
edit: <GooglePayComponent isEditing={ true } />,
ariaLabel: buttonData.title,
canMakePayment: () => buttonData.enabled,
supports: {
features,
style: [ 'height', 'borderRadius' ],
},
} );
}

View file

@ -105,6 +105,12 @@ class PaymentSettings extends AbstractDataModel {
return $this->get_paylater_enabled();
default:
if (
! did_filter( 'woocommerce_payment_gateways' )
|| doing_filter( 'woocommerce_payment_gateways' )
) {
return true;
}
$gateway = $this->get_gateway( $method_id );
if ( $gateway ) {

View file

@ -53,9 +53,19 @@ class SettingsModule implements ServiceModule, ExecutableModule {
* Returns whether the old settings UI should be loaded.
*/
public static function should_use_the_old_ui() : bool {
// New merchants should never see the legacy UI.
$show_new_ux = '1' === get_option( 'woocommerce-ppcp-is-new-merchant' );
if ( $show_new_ux ) {
return false;
}
// Existing merchants can opt-in to see the new UI.
$opt_out_choice = 'yes' === get_option( SwitchSettingsUiEndpoint::OPTION_NAME_SHOULD_USE_OLD_UI );
return apply_filters(
'woocommerce_paypal_payments_should_use_the_old_ui',
get_option( SwitchSettingsUiEndpoint::OPTION_NAME_SHOULD_USE_OLD_UI ) === 'yes'
$opt_out_choice
);
}

View file

@ -229,4 +229,21 @@ define( 'PPCP_PAYPAL_BN_CODE', 'Woo_PPCP' );
return class_exists( 'woocommerce' );
}
add_action(
'woocommerce_paypal_payments_gateway_migrate',
/**
* Set new merchant flag on plugin install.
*
* When installing the plugin for the first time, we direct the user to
* the new UI without a data migration, and fully hide the legacy UI.
*
* @param string|false $version String with previous installed plugin version.
* Boolean false on first installation on a new site.
*/
static function ( $version ) {
if ( ! $version ) {
update_option( 'woocommerce-ppcp-is-new-merchant', '1' );
}
}
);
} )();