From 5eaecdf3830ccf682c466afb7c256a2916c513cb Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Mon, 28 Oct 2024 11:46:03 +0800 Subject: [PATCH] Prevent synchronous WCCOM API calls in admin pages (#52281) * Remove Internal/Admin/Onboarding/OnboardingThemes.php * Only init WCPayPromotion for payment setting page * Use cached specs * Add changelog * Update onboarding.php * Move isWooPayEligible settings to init * Fix $is_payments_task check * Update is_woopay_eligible * Fix isWooPayEligible logic --- ...-prevent-synchronous-api-calls-admin-pages | 4 + .../src/Admin/API/OnboardingThemes.php | 2 - .../src/Admin/Features/Onboarding.php | 16 +- .../Internal/Admin/Onboarding/Onboarding.php | 1 - .../Admin/Onboarding/OnboardingThemes.php | 239 ------------------ .../src/Internal/Admin/Settings.php | 2 - .../Internal/Admin/WCPayPromotion/Init.php | 51 +++- .../features/class-wc-tests-onboarding.php | 40 --- 8 files changed, 56 insertions(+), 299 deletions(-) create mode 100644 plugins/woocommerce/changelog/update-prevent-synchronous-api-calls-admin-pages delete mode 100644 plugins/woocommerce/src/Internal/Admin/Onboarding/OnboardingThemes.php delete mode 100644 plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/class-wc-tests-onboarding.php diff --git a/plugins/woocommerce/changelog/update-prevent-synchronous-api-calls-admin-pages b/plugins/woocommerce/changelog/update-prevent-synchronous-api-calls-admin-pages new file mode 100644 index 00000000000..bcc3abedeaa --- /dev/null +++ b/plugins/woocommerce/changelog/update-prevent-synchronous-api-calls-admin-pages @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Remove OnboardingThemes.php and prevent synchronous WCPayPromotion api call diff --git a/plugins/woocommerce/src/Admin/API/OnboardingThemes.php b/plugins/woocommerce/src/Admin/API/OnboardingThemes.php index bf0a9edfa8d..543ee242d3d 100644 --- a/plugins/woocommerce/src/Admin/API/OnboardingThemes.php +++ b/plugins/woocommerce/src/Admin/API/OnboardingThemes.php @@ -7,8 +7,6 @@ namespace Automattic\WooCommerce\Admin\API; -use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingThemes as Themes; - defined( 'ABSPATH' ) || exit; /** diff --git a/plugins/woocommerce/src/Admin/Features/Onboarding.php b/plugins/woocommerce/src/Admin/Features/Onboarding.php index 99a3738b6e4..ac5c02ba545 100644 --- a/plugins/woocommerce/src/Admin/Features/Onboarding.php +++ b/plugins/woocommerce/src/Admin/Features/Onboarding.php @@ -62,8 +62,8 @@ class Onboarding extends DeprecatedClassFacade { * @return array */ public static function get_themes() { - wc_deprecated_function( 'get_themes', '6.3', '\Automattic\WooCommerce\Internal\Admin\OnboardingThemes::get_themes()' ); - return \Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingThemes::get_themes(); + wc_deprecated_function( 'get_themes', '6.3' ); + return array(); } /** @@ -74,8 +74,8 @@ class Onboarding extends DeprecatedClassFacade { * @return array */ public static function get_theme_data( $theme ) { - wc_deprecated_function( 'get_theme_data', '6.3', '\Automattic\WooCommerce\Internal\Admin\OnboardingThemes::get_theme_data()' ); - return \Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingThemes::get_theme_data(); + wc_deprecated_function( 'get_theme_data', '6.3' ); + return array(); } /** @@ -85,8 +85,8 @@ class Onboarding extends DeprecatedClassFacade { * @return array */ public static function get_allowed_themes() { - wc_deprecated_function( 'get_allowed_themes', '6.3', '\Automattic\WooCommerce\Internal\Admin\OnboardingThemes::get_allowed_themes()' ); - return \Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingThemes::get_allowed_themes(); + wc_deprecated_function( 'get_allowed_themes', '6.3' ); + return array(); } /** @@ -97,7 +97,7 @@ class Onboarding extends DeprecatedClassFacade { * @return array */ public static function get_product_data( $product_types ) { - wc_deprecated_function( 'get_product_data', '6.3', '\Automattic\WooCommerce\Internal\Admin\OnboardingProducts::get_product_data()' ); - return \Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProducts::get_product_data(); + wc_deprecated_function( 'get_product_data', '6.3' ); + return array(); } } diff --git a/plugins/woocommerce/src/Internal/Admin/Onboarding/Onboarding.php b/plugins/woocommerce/src/Internal/Admin/Onboarding/Onboarding.php index 69f66759460..5ed62b2395b 100644 --- a/plugins/woocommerce/src/Internal/Admin/Onboarding/Onboarding.php +++ b/plugins/woocommerce/src/Internal/Admin/Onboarding/Onboarding.php @@ -22,7 +22,6 @@ class Onboarding { OnboardingProfile::init(); OnboardingSetupWizard::instance()->init(); OnboardingSync::instance()->init(); - OnboardingThemes::init(); OnboardingFonts::init(); } } diff --git a/plugins/woocommerce/src/Internal/Admin/Onboarding/OnboardingThemes.php b/plugins/woocommerce/src/Internal/Admin/Onboarding/OnboardingThemes.php deleted file mode 100644 index e6f71d90601..00000000000 --- a/plugins/woocommerce/src/Internal/Admin/Onboarding/OnboardingThemes.php +++ /dev/null @@ -1,239 +0,0 @@ - 0 ) { - return (float) $price_match[0]; - } - return null; - } - - /** - * Get puchasable theme by slug. - * - * @param string $slug from theme. - * @return array|null - */ - public static function get_paid_theme_by_slug( $slug ) { - $themes = self::get_themes(); - $theme_key = array_search( $slug, array_column( $themes, 'slug' ), true ); - $theme = false !== $theme_key ? $themes[ $theme_key ] : null; - if ( $theme && isset( $theme['id'] ) && isset( $theme['price'] ) ) { - $price = self::get_price_from_string( $theme['price'] ); - if ( $price && $price > 0 ) { - return $themes[ $theme_key ]; - } - } - return null; - } - - /** - * Sort themes returned from WooCommerce.com - * - * @param array $themes Array of themes from WooCommerce.com. - * @return array - */ - public static function sort_woocommerce_themes( $themes ) { - usort( - $themes, - function ( $product_1, $product_2 ) { - if ( ! is_object( $product_1 ) || ! property_exists( $product_1, 'id' ) || ! property_exists( $product_1, 'slug' ) ) { - return 1; - } - if ( ! is_object( $product_2 ) || ! property_exists( $product_2, 'id' ) || ! property_exists( $product_2, 'slug' ) ) { - return 1; - } - if ( in_array( 'Storefront', array( $product_1->slug, $product_2->slug ), true ) ) { - return 'Storefront' === $product_1->slug ? -1 : 1; - } - return $product_1->id < $product_2->id ? 1 : -1; - } - ); - return $themes; - } - - /** - * Get a list of themes for the onboarding wizard. - * - * @return array - */ - public static function get_themes() { - $themes = get_transient( self::THEMES_TRANSIENT ); - if ( false === $themes ) { - $theme_data = wp_remote_get( - 'https://woocommerce.com/wp-json/wccom-extensions/1.0/search?category=themes', - array( - 'user-agent' => 'WooCommerce/' . WC()->version . '; ' . get_bloginfo( 'url' ), - ) - ); - $themes = array(); - - if ( ! is_wp_error( $theme_data ) ) { - $theme_data = json_decode( $theme_data['body'] ); - - if ( $theme_data ) { - $woo_themes = property_exists( $theme_data, 'products' ) ? $theme_data->products : array(); - $sorted_themes = self::sort_woocommerce_themes( $woo_themes ); - - foreach ( $sorted_themes as $theme ) { - if ( ! isset( $theme->slug ) ) { - continue; - } - $slug = sanitize_title_with_dashes( $theme->slug ); - $themes[ $slug ] = (array) $theme; - $themes[ $slug ]['is_installed'] = false; - $themes[ $slug ]['has_woocommerce_support'] = true; - $themes[ $slug ]['slug'] = $slug; - } - } - } - - $installed_themes = wp_get_themes(); - foreach ( $installed_themes as $slug => $theme ) { - $theme_data = self::get_theme_data( $theme ); - if ( isset( $themes[ $slug ] ) ) { - $themes[ $slug ]['is_installed'] = true; - $themes[ $slug ]['image'] = $theme_data['image']; - } else { - $themes[ $slug ] = $theme_data; - } - } - - $active_theme = get_option( 'stylesheet' ); - - /** - * The active theme may no be set if active_theme is not compatible with current version of WordPress. - * In this case, we should not add active theme to onboarding themes. - */ - if ( isset( $themes[ $active_theme ] ) ) { - // Add the WooCommerce support tag for default themes that don't explicitly declare support. - if ( function_exists( 'wc_is_wp_default_theme_active' ) && wc_is_wp_default_theme_active() ) { - $themes[ $active_theme ]['has_woocommerce_support'] = true; - } - - $themes = array( $active_theme => $themes[ $active_theme ] ) + $themes; - } - - set_transient( self::THEMES_TRANSIENT, $themes, DAY_IN_SECONDS ); - } - - $themes = apply_filters( 'woocommerce_admin_onboarding_themes', $themes ); - return array_values( $themes ); - } - - /** - * Get theme data used in onboarding theme browser. - * - * @param WP_Theme $theme Theme to gather data from. - * @return array - */ - public static function get_theme_data( $theme ) { - return array( - 'slug' => sanitize_text_field( $theme->stylesheet ), - 'title' => $theme->get( 'Name' ), - 'price' => '0.00', - 'is_installed' => true, - 'image' => $theme->get_screenshot(), - 'has_woocommerce_support' => true, - ); - } - - /** - * Add theme data to response from themes controller. - * - * @param WP_REST_Response $response Rest response. - * @return WP_REST_Response - */ - public static function add_uploaded_theme_data( $response ) { - if ( ! isset( $response->data['theme'] ) ) { - return $response; - } - - $theme = wp_get_theme( $response->data['theme'] ); - $response->data['theme_data'] = self::get_theme_data( $theme ); - - return $response; - } - - /** - * Delete the stored themes transient. - */ - public static function delete_themes_transient() { - delete_transient( self::THEMES_TRANSIENT ); - } - - /** - * Add preloaded data to onboarding. - * - * @param array $settings Component settings. - * - * @return array - */ - public static function preload_data( $settings ) { - $settings['onboarding']['activeTheme'] = get_option( 'stylesheet' ); - $settings['onboarding']['themes'] = self::get_themes(); - return $settings; - } - - /** - * Gets an array of themes that can be installed & activated via the onboarding wizard. - * - * @return array - */ - public static function get_allowed_themes() { - $allowed_themes = array(); - $themes = self::get_themes(); - - foreach ( $themes as $theme ) { - $price = preg_replace( '/&#?[a-z0-9]+;/i', '', $theme['price'] ); - - if ( $theme['is_installed'] || '0.00' === $price ) { - $allowed_themes[] = $theme['slug']; - } - } - - return apply_filters( 'woocommerce_admin_onboarding_themes_whitelist', $allowed_themes ); - } - -} diff --git a/plugins/woocommerce/src/Internal/Admin/Settings.php b/plugins/woocommerce/src/Internal/Admin/Settings.php index a72ae43f098..3d5f24c3b65 100644 --- a/plugins/woocommerce/src/Internal/Admin/Settings.php +++ b/plugins/woocommerce/src/Internal/Admin/Settings.php @@ -238,8 +238,6 @@ class Settings { $settings['features'] = $this->get_features(); - $settings['isWooPayEligible'] = WCPayPromotionInit::is_woopay_eligible(); - $has_gutenberg = is_plugin_active( 'gutenberg/gutenberg.php' ); $gutenberg_version = ''; if ( $has_gutenberg ) { diff --git a/plugins/woocommerce/src/Internal/Admin/WCPayPromotion/Init.php b/plugins/woocommerce/src/Internal/Admin/WCPayPromotion/Init.php index 5fbf035600a..5769593f96c 100644 --- a/plugins/woocommerce/src/Internal/Admin/WCPayPromotion/Init.php +++ b/plugins/woocommerce/src/Internal/Admin/WCPayPromotion/Init.php @@ -19,8 +19,15 @@ class Init extends RemoteSpecsEngine { * Constructor. */ public function __construct() { - $is_payments_page = isset( $_GET['page'] ) && $_GET['page'] === 'wc-settings' && isset( $_GET['tab'] ) && $_GET['tab'] === 'checkout'; // phpcs:ignore WordPress.Security.NonceVerification - if ( ! wp_is_json_request() && ! $is_payments_page ) { + /* phpcs:disable WordPress.Security.NonceVerification */ + $is_payments_setting_page = isset( $_GET['page'] ) && 'wc-settings' === $_GET['page'] && isset( $_GET['tab'] ) && 'checkout' === $_GET['tab']; + $is_wc_admin_page = isset( $_GET['page'] ) && 'wc-admin' === $_GET['page']; + + if ( $is_payments_setting_page || $is_wc_admin_page ) { + add_filter( 'woocommerce_admin_shared_settings', array( $this, 'add_component_settings' ) ); + } + + if ( ! wp_is_json_request() && ! $is_payments_setting_page ) { return; } @@ -87,14 +94,16 @@ class Init extends RemoteSpecsEngine { /** * Get WooPayments promotion spec. * + * @param boolean $fetch_from_remote Whether to fetch the spec from remote or not. + * * @return object|false WooPayments promotion spec or false if there isn't one. */ - public static function get_wc_pay_promotion_spec() { - $promotions = self::get_promotions(); + public static function get_wc_pay_promotion_spec( $fetch_from_remote = true ) { + $promotions = $fetch_from_remote ? self::get_promotions() : self::get_cached_or_default_promotions(); $wc_pay_promotion_spec = array_values( array_filter( $promotions, - function( $promotion ) { + function ( $promotion ) { return isset( $promotion->plugins ) && in_array( 'woocommerce-payments', $promotion->plugins, true ); } ) @@ -136,13 +145,30 @@ class Init extends RemoteSpecsEngine { return $specs_to_return; } + /** + * Gets either cached or default promotions. + * + * @return array + */ + public static function get_cached_or_default_promotions() { + $specs = 'no' === get_option( 'woocommerce_show_marketplace_suggestions', 'yes' ) + ? DefaultPromotions::get_all() + : WCPayPromotionDataSourcePoller::get_instance()->get_cached_specs(); + + if ( ! is_array( $specs ) || 0 === count( $specs ) ) { + $specs = DefaultPromotions::get_all(); + } + $results = EvaluateSuggestion::evaluate_specs( $specs, array( 'source' => 'wc-wcpay-promotions' ) ); + return $results['suggestions']; + } + /** * Get merchant WooPay eligibility. * * @return boolean If merchant is eligible for WooPay. */ public static function is_woopay_eligible() { - $wcpay_promotion = self::get_wc_pay_promotion_spec(); + $wcpay_promotion = self::get_wc_pay_promotion_spec( false ); return $wcpay_promotion && 'woocommerce_payments:woopay' === $wcpay_promotion->id; } @@ -173,6 +199,18 @@ class Init extends RemoteSpecsEngine { return $specs; } + /** + * Add component settings. + * + * @param array $settings Component settings. + * + * @return array + */ + public function add_component_settings( $settings ) { + $settings['isWooPayEligible'] = self::is_woopay_eligible(); + return $settings; + } + /** * Loads the payment method promotions scripts and styles. */ @@ -181,4 +219,3 @@ class Init extends RemoteSpecsEngine { WCAdminAssets::register_script( 'wp-admin-scripts', 'payment-method-promotions', true ); } } - diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/class-wc-tests-onboarding.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/class-wc-tests-onboarding.php deleted file mode 100644 index 0bb0340d6b5..00000000000 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/class-wc-tests-onboarding.php +++ /dev/null @@ -1,40 +0,0 @@ - 1, - 'slug' => 'ribs', - ); - $theme2 = (object) array( - 'id' => 2, - 'slug' => 'chicken', - ); - $theme3 = (object) array( - 'id' => 3, - 'slug' => 'Storefront', - ); - $theme4 = (object) array( - 'id' => 4, - 'slug' => 'poutine', - ); - $some_themes = array( $theme1, $theme2, $theme3, $theme4 ); - $sorted_themes = OnboardingThemes::sort_woocommerce_themes( $some_themes ); - $this->assertEquals( 'Storefront', $sorted_themes[0]->slug ); - } - -}