Fetch Marketplace promotions - ensure callback is always registered for `woocommerce_marketplace_fetch_promotions` action (#47262)

* Marketplace promotions - ensure action is always there

We started getting errors 'Scheduled action for woocommerce_marketplace_fetch_promotions will not be executed as no callbacks are registered.' on some environments.

In this commit we ensure that WC_Admin_Marketplace_Promotions::fetch_marketplace_promotions() is always added as a calback to our action hook.

* Marketplace promotions - switch from Action Scheduler to transients

We're switching fetching marketplace promotions from using Action Scheduler
every 12 hours, to a 1 day transient. This makes it easier to ensure that
the requests are triggered only for admins, and that they don't impact frontend
users of the stores.

* Changelog

* Clear action from Action Scheduler

Action woocommerce_marketplace_fetch_promotions is no longer used, so we're clearing it when it's triggered.
Since we cannot self-clear the action, we're triggering a new single action when woocommerce_marketplace_fetch_promotions is run.
This new action's sole responsibility is to clear the original action.
This commit is contained in:
Michal Iwanow 2024-05-16 09:56:59 +02:00 committed by GitHub
parent a7a1264fec
commit 5cd46f48ad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 93 additions and 46 deletions

View File

@ -0,0 +1,4 @@
Significance: patch
Type: fix
Switch marketplace promotions from Action Scheduler to transient.

View File

@ -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;

View File

@ -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' );
}
}
}

View File

@ -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' );