diff --git a/plugins/woocommerce/changelog/fix-woocommerce_marketplace_fetch_promotions-broken-with-cron b/plugins/woocommerce/changelog/fix-woocommerce_marketplace_fetch_promotions-broken-with-cron new file mode 100644 index 00000000000..1e47ce7a93e --- /dev/null +++ b/plugins/woocommerce/changelog/fix-woocommerce_marketplace_fetch_promotions-broken-with-cron @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Switch marketplace promotions from Action Scheduler to transient. diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-assets.php b/plugins/woocommerce/includes/admin/class-wc-admin-assets.php index fc2d933c93e..e24cdb09933 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-assets.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-assets.php @@ -558,7 +558,7 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) : // Marketplace promotions. if ( in_array( $screen_id, array( 'woocommerce_page_wc-admin' ), true ) ) { - $promotions = get_transient( WC_Admin_Marketplace_Promotions::TRANSIENT_NAME ); + $promotions = WC_Admin_Marketplace_Promotions::get_active_promotions(); if ( false === $promotions ) { return; diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-marketplace-promotions.php b/plugins/woocommerce/includes/admin/class-wc-admin-marketplace-promotions.php index 756f4cdc8fc..d1e6f667932 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-marketplace-promotions.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-marketplace-promotions.php @@ -15,10 +15,9 @@ if ( ! defined( 'ABSPATH' ) ) { */ class WC_Admin_Marketplace_Promotions { - const TRANSIENT_NAME = 'woocommerce_marketplace_promotions'; - const SCHEDULED_ACTION_HOOK = 'woocommerce_marketplace_fetch_promotions'; - const PROMOTIONS_API_URL = 'https://woocommerce.com/wp-json/wccom-extensions/3.0/promotions'; - const SCHEDULED_ACTION_INTERVAL = 12 * HOUR_IN_SECONDS; + const TRANSIENT_NAME = 'woocommerce_marketplace_promotions_v2'; + const TRANSIENT_LIFE_SPAN = DAY_IN_SECONDS; + const PROMOTIONS_API_URL = 'https://woocommerce.com/wp-json/wccom-extensions/3.0/promotions'; /** * The user's locale, for example en_US. @@ -28,7 +27,7 @@ class WC_Admin_Marketplace_Promotions { public static string $locale; /** - * On all admin pages, schedule an action to fetch promotions data. + * On all admin pages, try go get Marketplace promotions every day. * Shows notice and adds menu badge to WooCommerce Extensions item * if the promotions API requests them. * @@ -38,25 +37,9 @@ class WC_Admin_Marketplace_Promotions { * @return void */ public static function init() { - /** - * Filter to suppress the requests for and showing of marketplace promotions. - * - * @since 8.8 - */ - if ( apply_filters( 'woocommerce_marketplace_suppress_promotions', false ) ) { - return; - } - - register_deactivation_hook( WC_PLUGIN_FILE, array( __CLASS__, 'clear_scheduled_event' ) ); - - // Add the callback for our scheduled action. - if ( ! has_action( self::SCHEDULED_ACTION_HOOK, array( __CLASS__, 'fetch_marketplace_promotions' ) ) ) { - add_action( self::SCHEDULED_ACTION_HOOK, array( __CLASS__, 'fetch_marketplace_promotions' ) ); - } - - if ( is_admin() ) { - add_action( 'init', array( __CLASS__, 'schedule_promotion_fetch' ), 12 ); - } + // A legacy hook that can be triggered by action scheduler. + add_action( 'woocommerce_marketplace_fetch_promotions', array( __CLASS__, 'clear_deprecated_action' ) ); + add_action( 'woocommerce_marketplace_fetch_promotions_clear', array( __CLASS__, 'clear_scheduled_event' ) ); if ( defined( 'DOING_AJAX' ) && DOING_AJAX @@ -66,31 +49,73 @@ class WC_Admin_Marketplace_Promotions { return; } + if ( ! is_admin() ) { + return; + } + + self::maybe_update_promotions(); + self::$locale = ( self::$locale ?? get_user_locale() ) ?? 'en_US'; self::maybe_show_bubble_promotions(); } /** - * Schedule the action to fetch promotions data. + * Fetch promotions from the API and store them in a transient. + * Fetching can be suppressed by the `woocommerce_marketplace_suppress_promotions` filter. + * + * @return void */ - public static function schedule_promotion_fetch() { - // Schedule the action twice a day using Action Scheduler. - if ( - function_exists( 'as_has_scheduled_action' ) - && function_exists( 'as_schedule_recurring_action' ) - && false === as_has_scheduled_action( self::SCHEDULED_ACTION_HOOK ) - ) { - as_schedule_recurring_action( time(), self::SCHEDULED_ACTION_INTERVAL, self::SCHEDULED_ACTION_HOOK ); + private static function maybe_update_promotions() { + // Fetch promotions if they're not in the transient. + if ( false !== get_transient( self::TRANSIENT_NAME ) ) { + return; } + + // Fetch promotions from the API. + $promotions = self::fetch_marketplace_promotions(); + set_transient( self::TRANSIENT_NAME, $promotions, self::TRANSIENT_LIFE_SPAN ); + } + + /** + * Get active Marketplace promotions from the transient. + * Use `woocommerce_marketplace_suppress_promotions` filter to suppress promotions. + * + * @since 9.0 + */ + public static function get_active_promotions() { + /** + * Filter to suppress the requests for and showing of marketplace promotions. + * + * @since 8.8 + */ + if ( apply_filters( 'woocommerce_marketplace_suppress_promotions', false ) ) { + return array(); + } + + $promotions = get_transient( self::TRANSIENT_NAME ); + if ( ! $promotions ) { + return array(); + } + + return self::filter_out_inactive_promotions( $promotions ); } /** * Get promotions to show in the Woo in-app marketplace and load them into a transient * with a 12-hour life. Run as a recurring scheduled action. * - * @return void + * @return array */ - public static function fetch_marketplace_promotions() { + private static function fetch_marketplace_promotions() { + /** + * Filter to suppress the requests for and showing of marketplace promotions. + * + * @since 8.8 + */ + if ( apply_filters( 'woocommerce_marketplace_suppress_promotions', false ) ) { + return array(); + } + // Fetch promotions from the API. $fetch_options = array( 'auth' => true, @@ -131,9 +156,7 @@ class WC_Admin_Marketplace_Promotions { } // phpcs:enable WordPress.NamingConventions.ValidHookName.UseUnderscores - // Filter out any expired promotions. - $active_promotions = self::get_active_promotions( $promotions ); - set_transient( self::TRANSIENT_NAME, $active_promotions, 12 * HOUR_IN_SECONDS ); + return $promotions; } /** @@ -141,10 +164,21 @@ class WC_Admin_Marketplace_Promotions { * add a filter to show a bubble on the Extensions item in the * WooCommerce menu. * + * Use `woocommerce_marketplace_suppress_promotions` filter to suppress the bubble. + * * @return void * @throws Exception If we are unable to create a DateTime from the date_to_gmt. */ private static function maybe_show_bubble_promotions() { + /** + * Filter to suppress the requests for and showing of marketplace promotions. + * + * @since 8.8 + */ + if ( apply_filters( 'woocommerce_marketplace_suppress_promotions', false ) ) { + return; + } + $promotions = get_transient( self::TRANSIENT_NAME ); if ( ! $promotions ) { return; @@ -211,7 +245,7 @@ class WC_Admin_Marketplace_Promotions { * * @return array */ - private static function get_active_promotions( $promotions = array() ) { + private static function filter_out_inactive_promotions( $promotions = array() ) { $now_date_time = new DateTime( 'now', new DateTimeZone( 'UTC' ) ); $active_promotions = array(); @@ -292,13 +326,25 @@ class WC_Admin_Marketplace_Promotions { } /** - * When WooCommerce is deactivated, clear the scheduled action. + * Clear the scheduled action that was used to fetch promotions in WooCommerce 8.8. + * It's no longer needed as a transient is used to store the data. * * @return void */ public static function clear_scheduled_event() { if ( function_exists( 'as_unschedule_all_actions' ) ) { - as_unschedule_all_actions( self::SCHEDULED_ACTION_HOOK ); + as_unschedule_all_actions( 'woocommerce_marketplace_fetch_promotions' ); + } + } + + /** + * We can't clear deprecated action from AS when it's running, + * so we schedule a new single action to clear the deprecated + * `woocommerce_marketplace_fetch_promotions` action. + */ + public static function clear_deprecated_action() { + if ( function_exists( 'as_schedule_single_action' ) ) { + as_schedule_single_action( time(), 'woocommerce_marketplace_fetch_promotions_clear' ); } } } diff --git a/plugins/woocommerce/includes/class-woocommerce.php b/plugins/woocommerce/includes/class-woocommerce.php index b4cc5dc57f9..c87ab152179 100644 --- a/plugins/woocommerce/includes/class-woocommerce.php +++ b/plugins/woocommerce/includes/class-woocommerce.php @@ -585,6 +585,7 @@ final class WooCommerce { include_once WC_ABSPATH . 'includes/queue/class-wc-action-queue.php'; include_once WC_ABSPATH . 'includes/queue/class-wc-queue.php'; include_once WC_ABSPATH . 'includes/admin/marketplace-suggestions/class-wc-marketplace-updater.php'; + include_once WC_ABSPATH . 'includes/admin/class-wc-admin-marketplace-promotions.php'; include_once WC_ABSPATH . 'includes/blocks/class-wc-blocks-utils.php'; /** @@ -652,10 +653,6 @@ final class WooCommerce { include_once WC_ABSPATH . 'includes/admin/class-wc-admin.php'; } - if ( $this->is_request( 'admin' ) || $this->is_request( 'cron' ) ) { - include_once WC_ABSPATH . 'includes/admin/class-wc-admin-marketplace-promotions.php'; - } - // We load frontend includes in the post editor, because they may be invoked via pre-loading of blocks. $in_post_editor = doing_action( 'load-post.php' ) || doing_action( 'load-post-new.php' );