From 411d9ae78c035dd7b5b4cabed3141346c13102b1 Mon Sep 17 00:00:00 2001 From: Vishnu Gopal <533+vishnugopal@users.noreply.github.com> Date: Fri, 13 Sep 2024 10:33:54 +0530 Subject: [PATCH] Add subscribe notices for products without subscription (#51060) - Add a new notice to the plugins list for products used without subscriptions reminding them to purchase. - Remove notices for expired and expiring subscriptions from WooCommerce settings screens. - Change link for subscribe and renew to add the product directly to cart. --------- Co-authored-by: github-actions --- .../components/content/content.tsx | 3 + .../subscriptions-expired-expiring-notice.tsx | 9 ++ .../woo-purchase-subscription/index.js | 20 +++ ...ugin-install-products-without-subscription | 4 + .../admin/helper/class-wc-helper-admin.php | 1 + .../admin/helper/class-wc-helper-updater.php | 61 +++++++- .../woocommerce/src/Admin/PluginsHelper.php | 141 +++++++++++++++--- 7 files changed, 221 insertions(+), 18 deletions(-) create mode 100644 plugins/woocommerce-admin/client/wp-admin-scripts/woo-purchase-subscription/index.js create mode 100644 plugins/woocommerce/changelog/51060-fix-21455-notice-plugin-install-products-without-subscription 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. *