diff --git a/plugins/woocommerce-admin/client/marketplace/components/content/content.tsx b/plugins/woocommerce-admin/client/marketplace/components/content/content.tsx
index 73142721037..1a4679dc4c1 100644
--- a/plugins/woocommerce-admin/client/marketplace/components/content/content.tsx
+++ b/plugins/woocommerce-admin/client/marketplace/components/content/content.tsx
@@ -197,6 +197,9 @@ export default function Content(): JSX.Element {
{ selectedTab !== 'business-services' && (
) }
+ { selectedTab !== 'business-services' && (
+
+ ) }
{ renderContent() }
diff --git a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/subscriptions-expired-expiring-notice.tsx b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/subscriptions-expired-expiring-notice.tsx
index 6d76baedca7..f0140071e92 100644
--- a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/subscriptions-expired-expiring-notice.tsx
+++ b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/subscriptions-expired-expiring-notice.tsx
@@ -39,6 +39,12 @@ export default function SubscriptionsExpiredExpiringNotice(
dismissed:
'woo_subscription_expiring_notice_in_marketplace_dismissed',
},
+ 'woo-subscription-missing-notice': {
+ shown: 'woo_subscription_missing_notice_in_marketplace_shown',
+ clicked: 'woo_subscription_missing_notice_in_marketplace_clicked',
+ dismissed:
+ 'woo_subscription_missing_notice_in_marketplace_dismissed',
+ },
};
let notice = null;
@@ -51,6 +57,9 @@ export default function SubscriptionsExpiredExpiringNotice(
} else if ( type === 'expiring' ) {
notice = wccomSettings?.subscription_expiring_notice || {};
notice_id = 'woo-subscription-expiring-notice';
+ } else if ( type === 'missing' ) {
+ notice = wccomSettings?.subscription_missing_notice || {};
+ notice_id = 'woo-subscription-missing-notice';
} else {
return null;
}
diff --git a/plugins/woocommerce-admin/client/wp-admin-scripts/woo-purchase-subscription/index.js b/plugins/woocommerce-admin/client/wp-admin-scripts/woo-purchase-subscription/index.js
new file mode 100644
index 00000000000..42d2bd1ea14
--- /dev/null
+++ b/plugins/woocommerce-admin/client/wp-admin-scripts/woo-purchase-subscription/index.js
@@ -0,0 +1,20 @@
+/**
+ * External dependencies
+ */
+import domReady from '@wordpress/dom-ready';
+import { recordEvent } from '@woocommerce/tracks';
+
+domReady( () => {
+ const purchaseSubscriptionLink = document.querySelectorAll(
+ '.woocommerce-purchase-subscription'
+ );
+
+ if ( purchaseSubscriptionLink.length > 0 ) {
+ recordEvent( 'woo_purchase_subscription_in_plugins_shown' );
+ purchaseSubscriptionLink.forEach( ( link ) => {
+ link.addEventListener( 'click', function () {
+ recordEvent( 'woo_purchase_subscription_in_plugins_clicked' );
+ } );
+ } );
+ }
+} );
diff --git a/plugins/woocommerce/changelog/51060-fix-21455-notice-plugin-install-products-without-subscription b/plugins/woocommerce/changelog/51060-fix-21455-notice-plugin-install-products-without-subscription
new file mode 100644
index 00000000000..8e432f67f7a
--- /dev/null
+++ b/plugins/woocommerce/changelog/51060-fix-21455-notice-plugin-install-products-without-subscription
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Add a new notice to the plugins list for products used without subscriptions reminding them to purchase.
\ No newline at end of file
diff --git a/plugins/woocommerce/includes/admin/helper/class-wc-helper-admin.php b/plugins/woocommerce/includes/admin/helper/class-wc-helper-admin.php
index 96cc160502c..15ed3ffe3bb 100644
--- a/plugins/woocommerce/includes/admin/helper/class-wc-helper-admin.php
+++ b/plugins/woocommerce/includes/admin/helper/class-wc-helper-admin.php
@@ -85,6 +85,7 @@ class WC_Helper_Admin {
if ( WC_Helper::is_site_connected() ) {
$settings['wccomHelper']['subscription_expired_notice'] = PluginsHelper::get_expired_subscription_notice( false );
$settings['wccomHelper']['subscription_expiring_notice'] = PluginsHelper::get_expiring_subscription_notice( false );
+ $settings['wccomHelper']['subscription_missing_notice'] = PluginsHelper::get_missing_subscription_notice();
}
return $settings;
diff --git a/plugins/woocommerce/includes/admin/helper/class-wc-helper-updater.php b/plugins/woocommerce/includes/admin/helper/class-wc-helper-updater.php
index 02eeb89e0aa..086071717d4 100644
--- a/plugins/woocommerce/includes/admin/helper/class-wc-helper-updater.php
+++ b/plugins/woocommerce/includes/admin/helper/class-wc-helper-updater.php
@@ -40,11 +40,13 @@ class WC_Helper_Updater {
}
if ( WC_Helper::is_site_connected() ) {
add_action( 'load-plugins.php', array( __CLASS__, 'setup_message_for_expired_and_expiring_subscriptions' ), 11 );
+ add_action( 'load-plugins.php', array( __CLASS__, 'setup_message_for_plugins_without_subscription' ), 11 );
}
}
/**
* Add the hook for modifying default WPCore update notices on the plugins management page.
+ * This is for plugins with expired or expiring subscriptions.
*/
public static function setup_message_for_expired_and_expiring_subscriptions() {
foreach ( WC_Helper::get_local_woo_plugins() as $plugin ) {
@@ -52,6 +54,16 @@ class WC_Helper_Updater {
}
}
+ /**
+ * Add the hook for modifying default WPCore update notices on the plugins management page.
+ * This is for plugins without a subscription.
+ */
+ public static function setup_message_for_plugins_without_subscription() {
+ foreach ( WC_Helper::get_local_woo_plugins() as $plugin ) {
+ add_action( 'in_plugin_update_message-' . $plugin['_filename'], array( __CLASS__, 'display_notice_for_plugins_without_subscription' ), 10, 2 );
+ }
+ }
+
/**
* Runs in a cron thread, or in a visitor thread if triggered
* by _maybe_update_plugins(), or in an auto-update thread.
@@ -294,10 +306,11 @@ class WC_Helper_Updater {
$renew_link = add_query_arg(
array(
+ 'add-to-cart' => $product_id,
'utm_source' => 'pu',
'utm_campaign' => 'pu_plugin_screen_renew',
),
- PluginsHelper::WOO_SUBSCRIPTION_PAGE_URL
+ PluginsHelper::WOO_CART_PAGE_URL
);
/* translators: 1: Product regular price */
@@ -340,6 +353,52 @@ class WC_Helper_Updater {
}
}
+ /**
+ * Runs on in_plugin_update_message-{file-name}, show a message if plugin is without a subscription.
+ * Only Woo local plugins are passed to this function.
+ *
+ * @see setup_message_for_plugins_without_subscription
+ * @param object $plugin_data An array of plugin metadata.
+ * @param object $response An object of metadata about the available plugin update.
+ *
+ * @return void.
+ */
+ public static function display_notice_for_plugins_without_subscription( $plugin_data, $response ) {
+ // Extract product ID from the response.
+ $product_id = preg_replace( '/[^0-9]/', '', $response->id );
+
+ if ( WC_Helper::has_product_subscription( $product_id ) ) {
+ return;
+ }
+
+ // Prepare the expiry notice based on subscription status.
+ $purchase_link = add_query_arg(
+ array(
+ 'add-to-cart' => $product_id,
+ 'utm_source' => 'pu',
+ 'utm_campaign' => 'pu_plugin_screen_purchase',
+ ),
+ PluginsHelper::WOO_CART_PAGE_URL,
+ );
+
+ $notice = sprintf(
+ /* translators: 1: URL to My Subscriptions page */
+ __( ' You don\'t have a subscription, subscribe to update.', 'woocommerce' ),
+ esc_url( $purchase_link ),
+ );
+
+ // Display the expiry notice.
+ echo wp_kses(
+ $notice,
+ array(
+ 'a' => array(
+ 'href' => array(),
+ 'class' => array(),
+ ),
+ )
+ );
+ }
+
/**
* Get update data for all plugins.
*
diff --git a/plugins/woocommerce/src/Admin/PluginsHelper.php b/plugins/woocommerce/src/Admin/PluginsHelper.php
index efcf9152f9e..1900bfdf493 100644
--- a/plugins/woocommerce/src/Admin/PluginsHelper.php
+++ b/plugins/woocommerce/src/Admin/PluginsHelper.php
@@ -33,17 +33,24 @@ if ( ! function_exists( 'get_plugins' ) ) {
class PluginsHelper {
/**
- * Indicates whether the expiration notice for subscriptions can be displayed.
+ * Subscription notices in Woo screens are shown in clear priority order, first
+ * expired, and if those don't exist, expiring, and finally if none of those exist,
+ * then missing. This keeps track of whether we can show the next set of notices.
*
* @var bool
*/
- public static $can_show_expiring_subs_notice = true;
+ public static $subscription_usage_notices_already_shown = false;
/**
* The URL for the WooCommerce subscription page.
*/
const WOO_SUBSCRIPTION_PAGE_URL = 'https://woocommerce.com/my-account/my-subscriptions/';
+ /**
+ * The URL for the WooCommerce.com cart page.
+ */
+ const WOO_CART_PAGE_URL = 'https://woocommerce.com/cart/';
+
/**
* The URL for the WooCommerce.com add payment method page.
*/
@@ -59,6 +66,11 @@ class PluginsHelper {
*/
const DISMISS_EXPIRING_SUBS_NOTICE = 'woo_subscription_expiring_notice_dismiss';
+ /**
+ * Meta key for dismissing missing subscription notices
+ */
+ const DISMISS_MISSING_SUBS_NOTICE = 'woo_subscription_missing_notice_dismiss';
+
/**
* Initialize hooks.
*/
@@ -67,10 +79,7 @@ class PluginsHelper {
add_action( 'woocommerce_plugins_install_and_activate_async_callback', array( __CLASS__, 'install_and_activate_plugins_async_callback' ), 10, 2 );
add_action( 'woocommerce_plugins_activate_callback', array( __CLASS__, 'activate_plugins' ), 10, 2 );
add_action( 'admin_notices', array( __CLASS__, 'maybe_show_connect_notice_in_plugin_list' ) );
- add_action( 'admin_notices', array( __CLASS__, 'maybe_show_expired_subscriptions_notice' ), 10 );
- add_action( 'admin_notices', array( __CLASS__, 'maybe_show_expiring_subscriptions_notice' ), 11 );
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'maybe_enqueue_scripts_for_connect_notice' ) );
- add_action( 'admin_enqueue_scripts', array( __CLASS__, 'maybe_enqueue_scripts_for_subscription_notice' ) );
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'maybe_enqueue_scripts_for_notices_in_plugins' ) );
}
@@ -659,6 +668,7 @@ class PluginsHelper {
wp_enqueue_script( 'woo-plugin-update-connect-notice' );
wp_enqueue_script( 'woo-enable-autorenew' );
wp_enqueue_script( 'woo-renew-subscription' );
+ wp_enqueue_script( 'woo-purchase-subscription' );
}
/**
@@ -733,11 +743,15 @@ class PluginsHelper {
* @return array notice data to return. Contains type, parsed_message and product_id.
*/
public static function get_subscriptions_notice_data( array $all_subs, array $subs_to_show, int $total, array $messages, string $type ) {
+ $utm_campaign = 'expired' === $type ?
+ 'pu_settings_screen_renew' :
+ ( 'missing' === $type ? 'pu_settings_screen_purchase' : 'pu_settings_screen_enable_autorenew' );
+
if ( 1 < $total ) {
$hyperlink_url = add_query_arg(
array(
'utm_source' => 'pu',
- 'utm_campaign' => 'expired' === $type ? 'pu_settings_screen_renew' : 'pu_settings_screen_enable_autorenew',
+ 'utm_campaign' => $utm_campaign,
),
self::WOO_SUBSCRIPTION_PAGE_URL
@@ -750,10 +764,18 @@ class PluginsHelper {
esc_attr( $total ),
);
+ // All product ids.
+ $product_ids = array_map(
+ function ( $sub ) {
+ return $sub['product_id'];
+ },
+ $subs_to_show
+ );
+
return array(
'type' => 'different_subscriptions',
'parsed_message' => $parsed_message,
- 'product_id' => '',
+ 'product_ids' => $product_ids,
);
}
@@ -769,8 +791,9 @@ class PluginsHelper {
)
);
- $message_key = $has_multiple_subs_for_product ? 'multiple_manage' : 'single_manage';
- $renew_string = __( 'Renew', 'woocommerce' );
+ $message_key = $has_multiple_subs_for_product ? 'multiple_manage' : 'single_manage';
+ $renew_string = __( 'Renew', 'woocommerce' );
+ $subscribe_string = __( 'Subscribe', 'woocommerce' );
if ( isset( $subscription['product_regular_price'] ) ) {
/* translators: 1: Product price */
$renew_string = sprintf( __( 'Renew for %1$s', 'woocommerce' ), $subscription['product_regular_price'] );
@@ -781,7 +804,7 @@ class PluginsHelper {
'product_id' => $product_id,
'type' => $type,
'utm_source' => 'pu',
- 'utm_campaign' => 'expired' === $type ? 'pu_settings_screen_renew' : 'pu_settings_screen_enable_autorenew',
+ 'utm_campaign' => $utm_campaign,
),
self::WOO_SUBSCRIPTION_PAGE_URL
@@ -798,7 +821,8 @@ class PluginsHelper {
esc_attr( $subscription['product_name'] ),
esc_attr( $expiry_date ),
esc_url( $hyperlink_url ),
- esc_attr( $renew_string ),
+ // Show subscribe for missing subscriptions, renew otherwise.
+ 'missing' === $type ? esc_attr( $subscribe_string ) : esc_attr( $renew_string ),
);
return array(
@@ -826,7 +850,7 @@ class PluginsHelper {
return array();
}
- if ( ! self::$can_show_expiring_subs_notice ) {
+ if ( self::$subscription_usage_notices_already_shown ) {
return array();
}
@@ -851,6 +875,9 @@ class PluginsHelper {
$total_expiring_subscriptions = count( $expiring_subscriptions );
+ // Don't show missing notice if there are expiring subscriptions.
+ self::$subscription_usage_notices_already_shown = true;
+
// When payment method is missing on WooCommerce.com.
$helper_notices = WC_Helper::get_notices();
if ( ! empty( $helper_notices['missing_payment_method_notice'] ) ) {
@@ -927,8 +954,8 @@ class PluginsHelper {
return array();
}
- $total_expired_subscriptions = count( $expired_subscriptions );
- self::$can_show_expiring_subs_notice = false;
+ $total_expired_subscriptions = count( $expired_subscriptions );
+ self::$subscription_usage_notices_already_shown = true;
$notice_data = self::get_subscriptions_notice_data(
$subscriptions,
@@ -947,17 +974,17 @@ class PluginsHelper {
$button_link = add_query_arg(
array(
+ 'add-to-cart' => $notice_data['product_ids'],
'utm_source' => 'pu',
'utm_campaign' => $allowed_link ? 'pu_settings_screen_renew' : 'pu_in_apps_screen_renew',
),
- self::WOO_SUBSCRIPTION_PAGE_URL
+ self::WOO_CART_PAGE_URL
);
if ( in_array( $notice_data['type'], array( 'single_manage', 'multiple_manage' ), true ) ) {
$button_link = add_query_arg(
array(
- 'product_id' => $notice_data['product_id'],
- 'type' => 'expiring',
+ 'add-to-cart' => $notice_data['product_id'],
),
$button_link
);
@@ -970,6 +997,86 @@ class PluginsHelper {
);
}
+ /**
+ * Get formatted notice information for missing subscription.
+ *
+ * @return array notice information.
+ */
+ public static function get_missing_subscription_notice() {
+ if ( ! WC_Helper::is_site_connected() ) {
+ return array();
+ }
+
+ if ( self::$subscription_usage_notices_already_shown ) {
+ return array();
+ }
+
+ if ( ! self::should_show_notice( self::DISMISS_MISSING_SUBS_NOTICE ) ) {
+ return array();
+ }
+
+ $subscriptions = WC_Helper::get_subscription_list_data();
+ $missing_subscriptions = array_filter(
+ $subscriptions,
+ function ( $sub ) {
+ return ( ! empty( $sub['local']['installed'] ) && empty( $sub['product_key'] ) );
+ },
+ );
+
+ // Remove WUM from missing subscriptions list.
+ $missing_subscriptions = array_filter(
+ $missing_subscriptions,
+ function ( $sub ) {
+ return 'woo-update-manager' !== $sub['zip_slug'];
+ }
+ );
+
+ if ( ! $missing_subscriptions ) {
+ return array();
+ }
+
+ $total_missing_subscriptions = count( $missing_subscriptions );
+
+ $notice_data = self::get_subscriptions_notice_data(
+ $subscriptions,
+ $missing_subscriptions,
+ $total_missing_subscriptions,
+ array(
+ /* translators: 1) product name */
+ 'single_manage' => __( 'You don\'t have a subscription for %1$s. Subscribe to receive updates and streamlined support.', 'woocommerce' ),
+ /* translators: 1) total expired subscriptions */
+ 'different_subscriptions' => __( 'You don\'t have subscriptions for %1$s Woo extensions. Subscribe to receive updates and streamlined support.', 'woocommerce' ),
+ ),
+ 'missing',
+ );
+
+ $button_link = add_query_arg(
+ array(
+ 'add-to-cart' => $notice_data['product_ids'],
+ 'utm_source' => 'pu',
+ 'utm_campaign' => 'pu_in_apps_screen_purchase',
+ ),
+ self::WOO_CART_PAGE_URL
+ );
+
+ if ( in_array( $notice_data['type'], array( 'single_manage', 'multiple_manage' ), true ) ) {
+ $button_link = add_query_arg(
+ array(
+ 'add-to-cart' => $notice_data['product_id'],
+ ),
+ $button_link
+ );
+ }
+
+ $button_text = __( 'Subscribe', 'woocommerce' );
+
+ return array(
+ 'description' => $notice_data['parsed_message'],
+ 'button_text' => $button_text,
+ 'button_link' => $button_link,
+ );
+ }
+
/**
* Determine whether a specific notice should be shown to the current user.
*