Remove the ability to update Woo.com extensions not available in WP.org plugin directory (#44279)

* Remove the hook for updating update_plugins transient with data fetched from Woo.com

This prevents the WP core from updating Woo.com plugins.

* Remove the hook for updating update_themes transient with data fetched from Woo.com

This prevents the WP core from updating Woo.com themes.

* Remove the class hand hooks used for updating the API response of WP.org with update URI's from Woo.com.

* Add change log file.

* Add update_plugins and themes transient update hooks back while removing the package from Woo.com extensions.

When a Woo hosted plugin has an update, this will allow showing the update message but block the ability to update.

* Adding a hook to modify the plugin update notification on plugins.php.

When a Woo.com hosted plugin has an update available it will show a message to install Woo Marketplace plugin, if it's not installed.

* Utility class for Woo Marketplace plugin.

* Check wether the Woo marketplace plugin is already installed before modifying update notice.

* Update docblocks.

* Add status end point for wccom-site API.

* Check the existence of marketplace plugin only after the plugins are loaded.

* Add WP.org plugin api response updater back to core.

* Function to identify if the market-place plugin is installed.

* Update status API response with installation status.

* Update the status variable push to store admin frontend with information about market-place plugin.

* Styles for woo-connect-plugin component.

* Component for displaying the message to install Woo Connect plugin based on the installation status.

* Add Woo Connect plugin notice to my-subscriptions page.

* Add Woo connect plugin message to discover page.

* Add Woo Connect install/download urls to constants file.

* Add a modal for asking the user to install Woo Connect plugin.

* Update strings with constants.

* Show the Woo Connect modal when user updating plugins without Woo Connect installed.

* Show Woo Connect install notifications only when the site is connected.

* Update plugin management page messaging.

* Update notification on connect screen.

* Update notifications on plugin management page.

* Remove additional spacing in Plugin Install Notice and spacing in the discover page results and the notice.

* Use notice component instead of Card.

* Minor improvements to readability.

* Add translation support for messages displayed on plugins.php.

* Update woo connect plugin installation URL (#45127)

* Ad functions for generating WooConnect plugin install URLs.

* Make WooConnect install url to available to marketplace components.

* Make WooConnect install url to available to marketplace components.

* Link install buttons to install URL with access_token and secret.

* Update Woo Update Manager download url.

* Show admin notice to install Woo Update Manager on plugins.php.

* Adding the view for admin notice for installing woo_update_manager.

* Show woo update manager installation notice on WC admin pages and make the notice dissmisable.

* Update the install url for Woo Update Manager by adding the product ID and introduce a filter to override the product ID.

* Change the plugin name to Woo Update Manager.

* Change the download url for woo update manager

* Fix the margin below the Woo Update plugin install notice.

* Rename Woo Connect to Woo Update Manager.

* Rename wccom-site status endpoint response variables based on plugin name changes.

* Update the plugin main file name for Woo Update Manager.

* Updating the download URL.

* Updating css class names and file names from `woo-connect-plugin` to `woo-update-manager`.

* Change wooConnect variables to wooUpdateManager.

* Rename WC_Helper_Plugin to WC_Woo_Update_Manager_Plugin and remove references to market place plugin.

* Extract and reuse the logic for creating the signature.

* Use WC built-in method to check if the current page is a WC page.

* Add source parameter for installer.

* Update text copies based on flows for in-app Woo Update Manager install message.

* Update admin notice based on the flows.

* Update connect store message.

* Update the text based on the latest flow and show a different message when WUM is installed but not activated.

* Use html text for install notice message.

* Implement the bubble showing number of updates available on WooCommerce -> Extensions menu. Add one to available Woo.com updates if WUM is not installed or activated.

* Update install url to new structure.

* Update download url for WUM.

* Add a method to get the id of the WUM.

* Remove WUM from available to install list of extensions on my-subscriptions tab.

* Remove the hard coded ID of the Woo Update Manager and use the slug to get the ID of the plugin based on update-check response.

* Fixing the notice generated due to empty body.

* Update CTA and image for the not connected screen.

* Increasing the left margin of the install notice to match admin notices.

* Add close button to modal with message to activate WUM.

* Add install/activate notice to Browse and Themes tabs.

* Add the view for showing admin notice for activating the plugin.

* Update plugin name on install admin notice.

* Show install or activate admin notice based on the context.

* Update comment

* Make the linked text shorter in plugin update message.

* Generate a signed auto-install URL for woo hosted plugins without helper API (#45313)

* Generate a signed auto-install URL for woo hosted plugins without helper API

* Use product slug in auto-install URL instead of product id.

* Update activate Woo Update Manager modal buttons.

* Change install url from Woo Update Manager ID to slug.

* Allow defining the menu title and page title separately for when adding menus and sub menus in WooCommerce Admin.

* Set the page title separately for WooCommerce Extensions menu.

* Fix the update count issue.

* Update the download URL for Woo Update Manager.

* Update the download link for WUM in admin notices.

* Show WUM admin notices on WC Admin pages only when the site is connected.

* Remove additional slash in plugin admin url.

* Add wum-installed parameter to the connect URL (#45702)

* Add wum-installed parameter to the connect URL

* Send status of Woo Update Manager installation on connection init.

* Revert "Add wum-installed parameter to the connect URL"

This reverts commit 4c55038b03.

---------

Co-authored-by: Denis Dvali <denis.dvali@automattic.com>

* Fix lint errors.

* Fix lint error.

* Fix lint errors.

* Fix js lint issues.

* Update Woo Update manager admin notice.

---------

Co-authored-by: Muhammad Anas <anastts.pk@gmail.com>
Co-authored-by: Denis Dvali <denis.dvali@automattic.com>
This commit is contained in:
Thilina Pituwala 2024-03-20 15:02:23 +01:00 committed by GitHub
parent a7fb611665
commit d065b786f5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 815 additions and 142 deletions

View File

@ -16,4 +16,6 @@ export const MARKETPLACE_CART_PATH = MARKETPLACE_HOST + '/cart/';
export const MARKETPLACE_COLLABORATION_PATH = export const MARKETPLACE_COLLABORATION_PATH =
MARKETPLACE_HOST + MARKETPLACE_HOST +
'/document/managing-woocommerce-com-subscriptions/#transfer-a-woocommerce-com-subscription'; '/document/managing-woocommerce-com-subscriptions/#transfer-a-woocommerce-com-subscription';
export const WP_ADMIN_PLUGIN_LIST_URL = ADMIN_URL + '/plugins.php'; export const WP_ADMIN_PLUGIN_LIST_URL = ADMIN_URL + 'plugins.php';
export const WOO_CONNECT_PLUGIN_DOWNLOAD_URL =
MARKETPLACE_HOST + '/product-download/woo-update-manager';

View File

@ -13,6 +13,7 @@ import ProductLoader from '../product-loader/product-loader';
import { MarketplaceContext } from '../../contexts/marketplace-context'; import { MarketplaceContext } from '../../contexts/marketplace-context';
import { ProductType } from '../product-list/types'; import { ProductType } from '../product-list/types';
import './discover.scss'; import './discover.scss';
import PluginInstallNotice from '../woo-update-manager-plugin/plugin-install-notice';
export default function Discover(): JSX.Element | null { export default function Discover(): JSX.Element | null {
const [ productGroups, setProductGroups ] = useState< const [ productGroups, setProductGroups ] = useState<
@ -70,6 +71,7 @@ export default function Discover(): JSX.Element | null {
const groupsList = productGroups.flatMap( ( group ) => group ); const groupsList = productGroups.flatMap( ( group ) => group );
return ( return (
<div className="woocommerce-marketplace__discover"> <div className="woocommerce-marketplace__discover">
<PluginInstallNotice />
{ groupsList.map( ( groups ) => ( { groupsList.map( ( groups ) => (
<ProductList <ProductList
key={ groups.id } key={ groups.id }

View File

@ -256,7 +256,7 @@
.woocommerce-marketplace__my-subscriptions__icon { .woocommerce-marketplace__my-subscriptions__icon {
width: 80px; width: 80px;
height: 80px; height: 80px;
background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODAiIGhlaWdodD0iODAiIHZpZXdCb3g9IjAgMCA4MCA4MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzI5NzBfMTA0MzcpIj4KPHJlY3Qgd2lkdGg9IjgwIiBoZWlnaHQ9IjgwIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNNzQuMTMyNSAwTDY4LjQ0ODEgMy4yOTQ4NEw2Mi43NTc5IDBMNTcuMDczNCAzLjI5NDg0TDUxLjM4MzIgMEw0NS42OTMxIDMuMjk0ODRMNDAuMDA4NiAwTDM0LjMxODQgMy4yOTQ4NEwyOC42MzM5IDBMMjIuOTQzNyAzLjI5NDg0TDE3LjI1MzYgMEwxMS41NjM0IDMuMjk0ODRMNS44NzMxOCAwTDAgMy40MDIwNlY3Ni41OTc5TDUuODY3NDcgODBMMTEuNTU3NiA3Ni43MDFMMTcuMjQyMSA4MEwyMi45MzIzIDc2LjcwMUwyOC42MTY4IDgwTDM0LjMwNjkgNzYuNzAxTDM5Ljk5NzEgODBMNDUuNjgxNiA3Ni43MDFMNTEuMzcxOCA4MEw1Ny4wNTYzIDc2LjcwMUw2Mi43NDY1IDgwTDY4LjQzNjYgNzYuNzAxTDc0LjEzMjUgODBMODAgNzYuNTk3OVYzLjQwMjA2TDc0LjEzMjUgMFoiIGZpbGw9IiNGMEYwRjAiLz4KPHBhdGggZD0iTTcxLjE5OTIgMTQuNzk5Nkg4LjM5OTE3VjE4Ljc5OTZINzEuMTk5MlYxNC43OTk2WiIgZmlsbD0iI0RERERERCIvPgo8cGF0aCBkPSJNNzEuMTk5MiAyNS4xOTk1SDguMzk5MTdWMjkuMTk5NUg3MS4xOTkyVjI1LjE5OTVaIiBmaWxsPSIjREREREREIi8+CjxwYXRoIGQ9Ik03MS4xOTkyIDM1LjU5OTZIOC4zOTkxN1YzOS41OTk2SDcxLjE5OTJWMzUuNTk5NloiIGZpbGw9IiNEREREREQiLz4KPHBhdGggZD0iTTY4IDUzLjYwMDFINDkuMkM0Ny40MzI3IDUzLjYwMDEgNDYgNTUuMDMyOCA0NiA1Ni44MDAxVjYwLjgwMDFDNDYgNjIuNTY3NCA0Ny40MzI3IDY0LjAwMDEgNDkuMiA2NC4wMDAxSDY4QzY5Ljc2NzMgNjQuMDAwMSA3MS4yIDYyLjU2NzQgNzEuMiA2MC44MDAxVjU2LjgwMDFDNzEuMiA1NS4wMzI4IDY5Ljc2NzMgNTMuNjAwMSA2OCA1My42MDAxWiIgZmlsbD0iI0RERERERCIvPgo8L2c+CjxkZWZzPgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzI5NzBfMTA0MzciPgo8cmVjdCB3aWR0aD0iODAiIGhlaWdodD0iODAiIGZpbGw9IndoaXRlIi8+CjwvY2xpcFBhdGg+CjwvZGVmcz4KPC9zdmc+Cg==); background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODAiIGhlaWdodD0iODAiIHZpZXdCb3g9IjAgMCA4MCA4MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzM4NV80Njc4KSI+CjxwYXRoIGQ9Ik0xMC43NTcgNDguNzI2N0M3LjkwMzE2IDUxLjU4MDUgNi42NjMxOCA1NC42ODk3IDcuMDc3NzQgNTcuOTY1NUM3LjQyOTM4IDYwLjc1MjcgOC45NjkxOCA2My40ODQzIDExLjc3ODYgNjYuMzA4NUMxMS43OTcxIDY2LjMyNyAxNC4zNCA2OC44Njk5IDE0LjM1ODUgNjguODg4NEMxNy4xODY0IDcxLjcwMTUgMTkuOTE0MyA3My4yMzc2IDIyLjcwMTUgNzMuNTg5M0MyNS45NzczIDc0LjAwMzggMjkuMDg2NSA3Mi43NjM5IDMxLjk0MDMgNjkuOTFMMzguMjgwOSA2My41Njk1QzQxLjEzNDcgNjAuNzE1NyA0Mi4zNzEgNTcuNjA2NSA0MS45NjAxIDU0LjMzMDdDNDEuNjA4NSA1MS41NDM1IDQwLjA2ODcgNDguODExOCAzNy4yNTkzIDQ1Ljk4NzZDMzcuMjQwOCA0NS45NjkxIDM0LjY5NzkgNDMuNDI2MiAzNC42Nzk0IDQzLjQwNzdDMzEuODUxNSA0MC41OTQ2IDI5LjEyMzUgMzkuMDU4NSAyNi4zMzYzIDM4LjcwNjlDMjMuMDYwNiAzOC4yOTIzIDE5Ljk1MTQgMzkuNTMyMyAxNy4wOTc1IDQyLjM4NjFMMTAuNzU3IDQ4LjcyNjdaTTIxLjEzOTUgNDcuNzY0M0MyMy4wNzkxIDQ1LjgyNDggMjYuMjQwMSA0NC40NDA0IDI5Ljk1NjQgNDguMTA4NkMyOS45Nzg2IDQ4LjEzMDggMzIuNTM2MyA1MC42ODg1IDMyLjU1ODUgNTAuNzEwN0MzNi4yMjY2IDU0LjQzMDYgMzQuODQyMyA1Ny41ODc5IDMyLjkwMjcgNTkuNTI3NUwyNy44OTgzIDY0LjUzMThDMjUuOTU4OCA2Ni40NzE0IDIyLjc5NzggNjcuODU1NyAxOS4wODE1IDY0LjE4NzZDMTkuMDU5MyA2NC4xNjU0IDE2LjUwMTYgNjEuNjA3NyAxNi40Nzk0IDYxLjU4NTVDMTIuODExMyA1Ny44NjU2IDE0LjE5NTYgNTQuNzA4MiAxNi4xMzUyIDUyLjc2ODdMMjEuMTM5NSA0Ny43NjQzWiIgZmlsbD0iI0YwRjBGMCIvPgo8cGF0aCBkPSJNMzQuNDE2NSAzOS4xNjIxQzM0LjQxNjUgMzkuMTYyMSAyOS45MTkzIDQzLjY1OTQgMjguNzYwNyA0NC44MTc5QzI3LjAyMSA0Ni41NTc2IDI3LjAxMzYgNDguNTM0MiAyOC43NjgxIDUwLjI4ODZMMzAuMzgxOSA1MS45MDI1QzMyLjEzNjQgNTMuNjU3IDM0LjExMyA1My42NDk2IDM1Ljg1MjcgNTEuOTA5OUMzNy4wMTEyIDUwLjc1MTMgNDEuNTA4NSA0Ni4yNTQxIDQxLjUwODUgNDYuMjU0MUwzNC40MTY1IDM5LjE2MjFaIiBmaWxsPSIjRTBFMEUwIi8+CjxwYXRoIGQ9Ik02OS45MTM0IDMxLjk0MDZDNzIuNzY3MiAyOS4wODY4IDc0LjAwMzUgMjUuOTc3NSA3My41OTI2IDIyLjcwMThDNzMuMjQxIDE5LjkxNDYgNzEuNzAxMiAxNy4xODI5IDY4Ljg5MTggMTQuMzU4N0M2OC44NzMzIDE0LjM0MDIgNjYuMzMwNCAxMS43OTczIDY2LjMxMTkgMTEuNzc4OEM2My40ODQgOC45NjU3MiA2MC43NTYgNy40Mjk2MiA1Ny45Njg4IDcuMDc3OThDNTQuNjkzIDYuNjYzNDIgNTEuNTgzOCA3LjkwMzQxIDQ4LjczIDEwLjc1NzJMNDIuMzg5NSAxNy4wOTc4QzM5LjUzNTYgMTkuOTUxNiAzOC4yOTk0IDIzLjA2MDggMzguNzEwMiAyNi4zMzY2QzM5LjA2MTkgMjkuMTIzOCA0MC42MDE3IDMxLjg1NTQgNDMuNDExMSAzNC42Nzk2QzQzLjQyOTYgMzQuNjk4MSA0NS45NzI0IDM3LjI0MSA0NS45OTEgMzcuMjU5NUM0OC44MTg5IDQwLjA3MjYgNTEuNTQ2OCA0MS42MDg3IDU0LjMzNCA0MS45NjA0QzU3LjYwOTggNDIuMzc0OSA2MC43MTkgNDEuMTM0OSA2My41NzI4IDM4LjI4MTFMNjkuOTEzNCAzMS45NDA2Wk01OS41MzA4IDMyLjkwMjlDNTcuNTkxMyAzNC44NDI1IDU0LjQzMDIgMzYuMjI2OCA1MC43MTQgMzIuNTU4N0M1MC42OTE4IDMyLjUzNjUgNDguMTM0MSAyOS45Nzg4IDQ4LjExMTkgMjkuOTU2NkM0NC40NDM4IDI2LjIzNjYgNDUuODI4MSAyMy4wNzkzIDQ3Ljc2NzcgMjEuMTM5OEw1Mi43NzIgMTYuMTM1NEM1NC43MTE2IDE0LjE5NTkgNTcuODcyNiAxMi44MTE1IDYxLjU4ODggMTYuNDc5NkM2MS42MTEgMTYuNTAxOSA2NC4xNjg3IDE5LjA1OTYgNjQuMTkwOSAxOS4wODE4QzY3Ljg1OTEgMjIuODAxNyA2Ni40NzQ3IDI1Ljk1OSA2NC41MzUyIDI3Ljg5ODZMNTkuNTMwOCAzMi45MDI5WiIgZmlsbD0iI0YwRjBGMCIvPgo8cGF0aCBkPSJNNDYuMjUwMiA0MS41MDg1QzQ2LjI1MDIgNDEuNTA4NSA1MC43NDc0IDM3LjAxMTIgNTEuOTA2IDM1Ljg1MjdDNTMuNjQ1NiAzNC4xMTMgNTMuNjUzMSAzMi4xMzY0IDUxLjg5ODYgMzAuMzgxOUw1MC4yODQ3IDI4Ljc2ODFDNDguNTMwMyAyNy4wMTM2IDQ2LjU1MzcgMjcuMDIxIDQ0LjgxNCAyOC43NjA3QzQzLjY1NTUgMjkuOTE5MyAzOS4xNTgyIDM0LjQxNjUgMzkuMTU4MiAzNC40MTY1TDQ2LjI1MDIgNDEuNTA4NVoiIGZpbGw9IiNFMEUwRTAiLz4KPHBhdGggZD0iTTMzLjgwOTYgMjAuMTg5VjEzLjUyNjRIMzAuNDc4M1YyMC4xODlWMjMuNTE2NkgzMy44MDk2VjIwLjE4OVoiIGZpbGw9IiNFMEUwRTAiLz4KPHBhdGggZD0iTTI1LjE4NTIgMjIuNjcyOUwyMC40NzMzIDE3Ljk2MDlMMTguMTE5MSAyMC4zMTUxTDIyLjgzMTEgMjUuMDI3TDI1LjE4NTIgMjcuMzgxMUwyNy41MzkzIDI1LjAyN0wyNS4xODUyIDIyLjY3MjlaIiBmaWxsPSIjRTBFMEUwIi8+CjxwYXRoIGQ9Ik0yMC4zNDc0IDMwLjMxOTNIMTMuNjg0OFYzMy42NTA2SDIwLjM0NzRIMjMuNjc4N1YzMC4zMTkzSDIwLjM0NzRaIiBmaWxsPSIjRTBFMEUwIi8+CjxwYXRoIGQ9Ik00Ni43OTA1IDYwLjQ0OTVWNjcuMTEyMUg1MC4xMTgxVjYwLjQ0OTVWNTcuMTE4Mkg0Ni43OTA1VjYwLjQ0OTVaIiBmaWxsPSIjRTBFMEUwIi8+CjxwYXRoIGQ9Ik01NS40MTEyIDU3Ljk2Nkw2MC4xMjMyIDYyLjY3OEw2Mi40NzczIDYwLjMyMzlMNTcuNzY1NCA1NS42MTE5TDU1LjQxMTIgNTMuMjU3OEw1My4wNTcxIDU1LjYxMTlMNTUuNDExMiA1Ny45NjZaIiBmaWxsPSIjRTBFMEUwIi8+CjxwYXRoIGQ9Ik02MC4yNDkgNTAuMzE4Nkg2Ni45MTUzVjQ2Ljk4NzNINjAuMjQ5SDU2LjkyMTRWNTAuMzE4Nkg2MC4yNDlaIiBmaWxsPSIjRTBFMEUwIi8+CjwvZz4KPGRlZnM+CjxjbGlwUGF0aCBpZD0iY2xpcDBfMzg1XzQ2NzgiPgo8cmVjdCB3aWR0aD0iNjYuNjY2NyIgaGVpZ2h0PSI2Ni42NjY3IiBmaWxsPSJ3aGl0ZSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNyA3KSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo=);
} }
.woocommerce-marketplace__my-subscriptions__header { .woocommerce-marketplace__my-subscriptions__header {

View File

@ -25,6 +25,7 @@ import { RefreshButton } from './table/actions/refresh-button';
import Notices from './notices'; import Notices from './notices';
import InstallModal from './table/actions/install-modal'; import InstallModal from './table/actions/install-modal';
import { connectUrl } from '../../utils/functions'; import { connectUrl } from '../../utils/functions';
import PluginInstallNotice from '../woo-update-manager-plugin/plugin-install-notice';
export default function MySubscriptions(): JSX.Element { export default function MySubscriptions(): JSX.Element {
const { subscriptions, isLoading } = useContext( SubscriptionsContext ); const { subscriptions, isLoading } = useContext( SubscriptionsContext );
@ -54,7 +55,10 @@ export default function MySubscriptions(): JSX.Element {
); );
const subscriptionsAvailable: Array< Subscription > = subscriptions.filter( const subscriptionsAvailable: Array< Subscription > = subscriptions.filter(
( subscription: Subscription ) => ! subscription.subscription_installed ( subscription: Subscription ) =>
! subscription.subscription_installed &&
wccomSettings?.wooUpdateManagerPluginSlug !==
subscription.product_slug
); );
if ( ! wccomSettings?.isConnected ) { if ( ! wccomSettings?.isConnected ) {
@ -67,12 +71,12 @@ export default function MySubscriptions(): JSX.Element {
</h2> </h2>
<p className="woocommerce-marketplace__my-subscriptions__description"> <p className="woocommerce-marketplace__my-subscriptions__description">
{ __( { __(
'Connect your account to get updates, manage your subscriptions, and get seamless support. Once connected, your Woo.com subscriptions will appear here.', "Connect your store to Woo.com using the Woo.com Update Manager. Once connected, you'll be able to manage your subscriptions, receive product updates, and access streamlined support from this screen.",
'woocommerce' 'woocommerce'
) } ) }
</p> </p>
<Button href={ connectUrl() } variant="primary"> <Button href={ connectUrl() } variant="primary">
{ __( 'Connect Account', 'woocommerce' ) } { __( 'Connect your store', 'woocommerce' ) }
</Button> </Button>
</div> </div>
); );
@ -84,6 +88,7 @@ export default function MySubscriptions(): JSX.Element {
<section className="woocommerce-marketplace__my-subscriptions__notices"> <section className="woocommerce-marketplace__my-subscriptions__notices">
<Notices /> <Notices />
</section> </section>
<PluginInstallNotice />
<section className="woocommerce-marketplace__my-subscriptions-section woocommerce-marketplace__my-subscriptions__installed"> <section className="woocommerce-marketplace__my-subscriptions-section woocommerce-marketplace__my-subscriptions__installed">
<header className="woocommerce-marketplace__my-subscriptions__header"> <header className="woocommerce-marketplace__my-subscriptions__header">
<div className="woocommerce-marketplace__my-subscriptions__header-content"> <div className="woocommerce-marketplace__my-subscriptions__header-content">

View File

@ -20,9 +20,11 @@ import {
updateProduct, updateProduct,
} from '../../../../utils/functions'; } from '../../../../utils/functions';
import { NoticeStatus } from '../../../../contexts/types'; import { NoticeStatus } from '../../../../contexts/types';
import InstallWooConnectModal from '../../../woo-update-manager-plugin/install-woo-connect-modal';
interface UpdateProps { interface UpdateProps {
subscription: Subscription; subscription: Subscription;
wooUpdateManagerActive: boolean;
} }
export default function Update( props: UpdateProps ) { export default function Update( props: UpdateProps ) {
@ -34,7 +36,8 @@ export default function Update( props: UpdateProps ) {
props.subscription.active && props.subscription.active &&
props.subscription.local && props.subscription.local &&
props.subscription.local.slug && props.subscription.local.slug &&
props.subscription.local.path; props.subscription.local.path &&
props.wooUpdateManagerActive;
function update() { function update() {
recordEvent( 'marketplace_product_update_button_clicked', { recordEvent( 'marketplace_product_update_button_clicked', {
@ -156,6 +159,13 @@ export default function Update( props: UpdateProps ) {
onClose={ () => setShowModal( false ) } onClose={ () => setShowModal( false ) }
/> />
); );
} else if ( ! props.wooUpdateManagerActive ) {
return (
<InstallWooConnectModal
subscription={ props.subscription }
onClose={ () => setShowModal( false ) }
/>
);
} }
return null; return null;

View File

@ -25,6 +25,7 @@ import {
subscribeUrl, subscribeUrl,
} from '../../../../utils/functions'; } from '../../../../utils/functions';
import { MARKETPLACE_COLLABORATION_PATH } from '../../../constants'; import { MARKETPLACE_COLLABORATION_PATH } from '../../../constants';
import { getAdminSetting } from '../../../../../utils/admin-settings';
type StatusBadge = { type StatusBadge = {
text: string; text: string;
@ -126,12 +127,19 @@ function getStatusBadge( subscription: Subscription ): StatusBadge | false {
} }
function getVersion( subscription: Subscription ): string | JSX.Element { function getVersion( subscription: Subscription ): string | JSX.Element {
const wccomSettings = getAdminSetting( 'wccomHelper', {} );
if ( subscription.local.version === subscription.version ) { if ( subscription.local.version === subscription.version ) {
return <Version span={ subscription.local.version } />; return <Version span={ subscription.local.version } />;
} }
if ( subscription.local.version && subscription.version ) { if ( subscription.local.version && subscription.version ) {
return <Update subscription={ subscription } />; return (
<Update
subscription={ subscription }
wooUpdateManagerActive={ wccomSettings?.wooUpdateManagerActive }
/>
);
} }
if ( subscription.version ) { if ( subscription.version ) {

View File

@ -27,6 +27,7 @@ import { Product, ProductType, SearchResultType } from '../product-list/types';
import { MARKETPLACE_ITEMS_PER_PAGE } from '../constants'; import { MARKETPLACE_ITEMS_PER_PAGE } from '../constants';
import { ADMIN_URL } from '~/utils/admin-settings'; import { ADMIN_URL } from '~/utils/admin-settings';
import { ThemeSwitchWarningModal } from '~/customize-store/intro/warning-modals'; import { ThemeSwitchWarningModal } from '~/customize-store/intro/warning-modals';
import PluginInstallNotice from '../woo-update-manager-plugin/plugin-install-notice';
interface ProductsProps { interface ProductsProps {
categorySelector?: boolean; categorySelector?: boolean;
@ -149,6 +150,7 @@ export default function Products( props: ProductsProps ) {
return ( return (
<div className={ containerClassName }> <div className={ containerClassName }>
<PluginInstallNotice />
<h2 className={ productListTitleClassName }> <h2 className={ productListTitleClassName }>
{ isLoading ? ' ' : title } { isLoading ? ' ' : title }
</h2> </h2>

View File

@ -0,0 +1,104 @@
/**
* External dependencies
*/
import { Button, ButtonGroup, Modal } from '@wordpress/components';
import { __, sprintf } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import sanitizeHTML from '../../../lib/sanitize-html';
import { Subscription } from '../my-subscriptions/types';
import {
WOO_CONNECT_PLUGIN_DOWNLOAD_URL,
WP_ADMIN_PLUGIN_LIST_URL,
} from '../constants';
import { getAdminSetting } from '../../../utils/admin-settings';
interface ConnectProps {
subscription: Subscription;
onClose: () => void;
}
export default function InstallWooConnectModal( props: ConnectProps ) {
const wccomSettings = getAdminSetting( 'wccomHelper', {} );
if ( ! wccomSettings?.wooUpdateManagerInstalled ) {
return (
<Modal
title={ __( 'Access your updates', 'woocommerce' ) }
onRequestClose={ props.onClose }
focusOnMount={ true }
className="woocommerce-marketplace__header-account-modal"
style={ { borderRadius: 4 } }
overlayClassName="woocommerce-marketplace__header-account-modal-overlay"
>
<p className="woocommerce-marketplace__header-account-modal-text">
<span
dangerouslySetInnerHTML={ sanitizeHTML(
sprintf(
// translators: %s is the product version number (e.g. 1.0.2).
__(
'Version %s is available. To access this update, please first <b>install the Woo.com Update Manager</b> extension. Alternatively, you can download and install it manually.',
'woocommerce'
),
props.subscription.version
)
) }
/>
</p>
<ButtonGroup className="woocommerce-marketplace__header-account-modal-button-group">
<Button
href={ WOO_CONNECT_PLUGIN_DOWNLOAD_URL }
variant="secondary"
>
{ __( 'Download', 'woocommerce' ) }
</Button>
<Button
href={ wccomSettings?.wooUpdateManagerInstallUrl }
variant="primary"
>
{ __( 'Install', 'woocommerce' ) }
</Button>
</ButtonGroup>
</Modal>
);
}
if ( ! wccomSettings?.wooUpdateManagerActive ) {
return (
<Modal
title={ __( 'Access your updates', 'woocommerce' ) }
onRequestClose={ props.onClose }
focusOnMount={ true }
className="woocommerce-marketplace__header-account-modal"
style={ { borderRadius: 4 } }
overlayClassName="woocommerce-marketplace__header-account-modal-overlay"
>
<p className="woocommerce-marketplace__header-account-modal-text">
<span
dangerouslySetInnerHTML={ sanitizeHTML(
sprintf(
// translators: %s is the product version number (e.g. 1.0.2).
__(
'Version %s is available. To access this update, please <b>activate the Woo.com Update Manager</b> extension.',
'woocommerce'
),
props.subscription.version
)
) }
/>
</p>
<ButtonGroup className="woocommerce-marketplace__header-account-modal-button-group">
<Button onClick={ props.onClose } variant="link">
{ __( 'Cancel', 'woocommerce' ) }
</Button>
<Button href={ WP_ADMIN_PLUGIN_LIST_URL } variant="primary">
{ __( 'Activate', 'woocommerce' ) }
</Button>
</ButtonGroup>
</Modal>
);
}
return null;
}

View File

@ -0,0 +1,84 @@
/**
* External dependencies
*/
import { Button, Notice } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import sanitizeHTML from '../../../lib/sanitize-html';
import { getAdminSetting } from '../../../utils/admin-settings';
import {
WP_ADMIN_PLUGIN_LIST_URL,
WOO_CONNECT_PLUGIN_DOWNLOAD_URL,
} from '../constants';
import './woo-update-manager-plugin.scss';
export default function PluginInstallNotice() {
const wccomSettings = getAdminSetting( 'wccomHelper', {} );
if ( ! wccomSettings?.isConnected ) {
return null;
}
if (
! wccomSettings?.wooUpdateManagerActive &&
! wccomSettings?.wooUpdateManagerInstalled
) {
return (
<section className="woocommerce-marketplace__woo-update-manager-plugin__notices">
<Notice status="error" isDismissible={ false }>
<span
dangerouslySetInnerHTML={ sanitizeHTML(
__(
'Please install the <b>Woo.com Update Manager</b> to continue receiving the updates and streamlined support included in your Woo.com subscriptions.<br/>Alternatively, you can download and install it manually.',
'woocommerce'
)
) }
></span>
<div className="components-notice__buttons">
<Button
href={ wccomSettings?.wooUpdateManagerInstallUrl }
variant="secondary"
>
{ __( 'Install', 'woocommerce' ) }
</Button>
<Button
href={ WOO_CONNECT_PLUGIN_DOWNLOAD_URL }
variant="link"
>
{ __( 'Download', 'woocommerce' ) }
</Button>
</div>
</Notice>
</section>
);
} else if (
wccomSettings?.wooUpdateManagerInstalled &&
! wccomSettings?.wooUpdateManagerActive
) {
return (
<section className="woocommerce-marketplace__woo-update-manager-plugin__notices">
<Notice status="error" isDismissible={ false }>
<span
dangerouslySetInnerHTML={ sanitizeHTML(
__(
'Activate the <b>Woo.com Update Manager</b> to continue receiving the updates and streamlined support included in your Woo.com subscriptions.',
'woocommerce'
)
) }
></span>
<div className="components-notice__buttons">
<Button
href={ WP_ADMIN_PLUGIN_LIST_URL }
variant="secondary"
>
{ __( 'Activate', 'woocommerce' ) }
</Button>
</div>
</Notice>
</section>
);
}
return null;
}

View File

@ -0,0 +1,67 @@
@import '@wordpress/base-styles/_colors.native.scss';
@import '../../stylesheets/_variables.scss';
.woocommerce-marketplace__woo-update-manager-plugin__notices {
.components-notice {
margin-left: 0;
margin-right: 0;
background-color: #fff;
box-shadow: 0 2px 6px 0 rgba($gray-100, 0.05);
border: 1px solid var(--gutenberg-gray-100, #f0f0f0);
padding-right: $grid-unit-15;
position: relative;
&::before {
content: '';
display: block;
width: 4px;
height: 100%;
background-color: var(--wp-admin-theme-color, #007cba);
position: absolute;
left: 0;
top: 0;
bottom: 0;
}
&.is-error::before {
background-color: $alert-red;
}
.components-notice__content {
align-items: center;
gap: $grid-unit-15;
padding: 0;
margin-left: 12px;
}
.components-notice__buttons {
margin-top: 12px;
}
}
.components-button {
&.is-link {
text-decoration: none;
padding: 6px 12px;
}
}
}
.woocommerce-marketplace__woo-update-manager-plugin__notices {
margin-bottom: $grid-unit-50;
}
.woocommerce-marketplace__discover {
.woocommerce-marketplace__woo-update-manager-plugin__notices {
margin-bottom: 0;
}
}
.woocommerce-marketplace__header-account-modal {
.components-button {
&.is-link {
text-decoration: none;
padding: 6px 12px;
}
}
}

View File

@ -0,0 +1,4 @@
Significance: major
Type: update
Remove the ability to update Woo.com plugins that are not available under WordPress.org plugin directory

View File

@ -56,6 +56,10 @@ class WC_Helper_Admin {
'storeCountry' => wc_get_base_location()['country'], 'storeCountry' => wc_get_base_location()['country'],
'inAppPurchaseURLParams' => WC_Admin_Addons::get_in_app_purchase_url_params(), 'inAppPurchaseURLParams' => WC_Admin_Addons::get_in_app_purchase_url_params(),
'installedProducts' => $installed_products, 'installedProducts' => $installed_products,
'wooUpdateManagerInstalled' => WC_Woo_Update_Manager_Plugin::is_plugin_installed(),
'wooUpdateManagerActive' => WC_Woo_Update_Manager_Plugin::is_plugin_active(),
'wooUpdateManagerInstallUrl' => WC_Woo_Update_Manager_Plugin::generate_install_url(),
'wooUpdateManagerPluginSlug' => WC_Woo_Update_Manager_Plugin::WOO_UPDATE_MANAGER_SLUG,
); );
return $settings; return $settings;

View File

@ -67,6 +67,61 @@ class WC_Helper_API {
return wp_safe_remote_request( $url, $args ); return wp_safe_remote_request( $url, $args );
} }
/**
* Create signature for a request.
*
* @param string $access_token_secret The access token secret.
* @param string $url The URL to add the access token and signature to.
* @param string $method The request method.
* @param array $body The body of the request.
* @return string The signature.
*/
private static function create_request_signature( string $access_token_secret, string $url, string $method, $body = null ): string {
$request_uri = wp_parse_url( $url, PHP_URL_PATH );
$query_string = wp_parse_url( $url, PHP_URL_QUERY );
if ( is_string( $query_string ) ) {
$request_uri .= '?' . $query_string;
}
$data = array(
'host' => wp_parse_url( $url, PHP_URL_HOST ),
'request_uri' => $request_uri,
'method' => $method,
);
if ( ! empty( $body ) ) {
$data['body'] = $body;
}
return hash_hmac( 'sha256', wp_json_encode( $data ), $access_token_secret );
}
/**
* Add the access token and signature to the provided URL.
*
* @param string $url The URL to add the access token and signature to.
* @return string
*/
public static function add_auth_parameters( string $url ): string {
$auth = WC_Helper_Options::get( 'auth' );
if ( empty( $auth['access_token'] ) || empty( $auth['access_token_secret'] ) ) {
return false;
}
$signature = self::create_request_signature( (string) $auth['access_token_secret'], $url, 'GET' );
return add_query_arg(
array(
'token' => $auth['access_token'],
'signature' => $signature,
),
$url
);
}
/** /**
* Adds authentication headers to an HTTP request. * Adds authentication headers to an HTTP request.
* *
@ -81,24 +136,13 @@ class WC_Helper_API {
return false; return false;
} }
$request_uri = parse_url( $url, PHP_URL_PATH ); $signature = self::create_request_signature(
$query_string = parse_url( $url, PHP_URL_QUERY ); (string) $auth['access_token_secret'],
$url,
if ( is_string( $query_string ) ) { ! empty( $args['method'] ) ? $args['method'] : 'GET',
$request_uri .= '?' . $query_string; $args['body'] ?? null
}
$data = array(
'host' => parse_url( $url, PHP_URL_HOST ),
'request_uri' => $request_uri,
'method' => ! empty( $args['method'] ) ? $args['method'] : 'GET',
); );
if ( ! empty( $args['body'] ) ) {
$data['body'] = $args['body'];
}
$signature = hash_hmac( 'sha256', json_encode( $data ), $auth['access_token_secret'] );
if ( empty( $args['headers'] ) ) { if ( empty( $args['headers'] ) ) {
$args['headers'] = array(); $args['headers'] = array();
} }

View File

@ -300,7 +300,10 @@ class WC_Helper_Subscriptions_API {
); );
} }
$install_url = WC_Helper::get_subscription_install_url( $subscription['product_key'] ); $install_url = WC_Helper::get_subscription_install_url(
$subscription['product_key'],
$subscription['product_slug']
);
if ( ! $install_url ) { if ( ! $install_url ) {
wp_send_json_error( wp_send_json_error(

View File

@ -26,6 +26,16 @@ class WC_Helper_Updater {
add_action( 'pre_set_site_transient_update_themes', array( __CLASS__, 'transient_update_themes' ), 21, 1 ); add_action( 'pre_set_site_transient_update_themes', array( __CLASS__, 'transient_update_themes' ), 21, 1 );
add_action( 'upgrader_process_complete', array( __CLASS__, 'upgrader_process_complete' ) ); add_action( 'upgrader_process_complete', array( __CLASS__, 'upgrader_process_complete' ) );
add_action( 'upgrader_pre_download', array( __CLASS__, 'block_expired_updates' ), 10, 2 ); add_action( 'upgrader_pre_download', array( __CLASS__, 'block_expired_updates' ), 10, 2 );
add_action( 'plugins_loaded', array( __CLASS__, 'add_hook_for_modifying_update_notices' ) );
}
/**
* Add the hook for modifying default WPCore update notices on the plugins management page.
*/
public static function add_hook_for_modifying_update_notices() {
if ( ! WC_Woo_Update_Manager_Plugin::is_plugin_active() ) {
add_action( 'load-plugins.php', array( __CLASS__, 'setup_update_plugins_messages' ), 11 );
}
} }
/** /**
@ -53,21 +63,25 @@ class WC_Helper_Updater {
'plugin' => $filename, 'plugin' => $filename,
'new_version' => $data['version'], 'new_version' => $data['version'],
'url' => $data['url'], 'url' => $data['url'],
'package' => $data['package'], 'package' => '',
'upgrade_notice' => $data['upgrade_notice'], 'upgrade_notice' => $data['upgrade_notice'],
); );
/**
* Filters the Woo plugin data before saving it in transient used for updates.
*
* @since 8.7.0
*
* @param array $item Plugin item to modify.
* @param array $data Subscription data fetched from Helper API for the plugin.
* @param int $product_id Woo product id assigned to the plugin.
*/
$item = apply_filters( 'update_woo_com_subscription_details', $item, $data, $plugin['_product_id'] );
if ( isset( $data['requires_php'] ) ) { if ( isset( $data['requires_php'] ) ) {
$item['requires_php'] = $data['requires_php']; $item['requires_php'] = $data['requires_php'];
} }
// We don't want to deliver a valid upgrade package when their subscription has expired.
// To avoid the generic "no_package" error that empty strings give, we will store an
// indication of expiration for the `upgrader_pre_download` filter to error on.
if ( ! self::_has_active_subscription( $plugin['_product_id'] ) ) {
$item['package'] = 'woocommerce-com-expired-' . $plugin['_product_id'];
}
if ( $transient instanceof stdClass ) { if ( $transient instanceof stdClass ) {
if ( version_compare( $plugin['Version'], $data['version'], '<' ) ) { if ( version_compare( $plugin['Version'], $data['version'], '<' ) ) {
$transient->response[ $filename ] = (object) $item; $transient->response[ $filename ] = (object) $item;
@ -113,9 +127,16 @@ class WC_Helper_Updater {
'package' => '', 'package' => '',
); );
if ( self::_has_active_subscription( $theme['_product_id'] ) ) { /**
$item['package'] = $data['package']; * Filters the Woo plugin data before saving it in transient used for updates.
} *
* @since 8.7.0
*
* @param array $item Plugin item to modify.
* @param array $data Subscription data fetched from Helper API for the plugin.
* @param int $product_id Woo product id assigned to the plugin.
*/
$item = apply_filters( 'update_woo_com_subscription_details', $item, $data, $theme['_product_id'] );
if ( version_compare( $theme['Version'], $data['version'], '<' ) ) { if ( version_compare( $theme['Version'], $data['version'], '<' ) ) {
$transient->response[ $slug ] = $item; $transient->response[ $slug ] = $item;
@ -128,6 +149,53 @@ class WC_Helper_Updater {
return $transient; return $transient;
} }
/**
* Runs on load-plugins.php, adds a hook to show a custom plugin update message for Woo.com hosted plugins.
*
* @return void.
*/
public static function setup_update_plugins_messages() {
foreach ( WC_Helper::get_local_woo_plugins() as $plugin ) {
$filename = $plugin['_filename'];
add_action( 'in_plugin_update_message-' . $filename, array( __CLASS__, 'add_install_marketplace_plugin_message' ), 10, 2 );
}
}
/**
* Runs on in_plugin_update_message-{file-name}, show a message to install the Woo Marketplace plugin, on plugin update notification,
* if the Woo Marketplace plugin isn't already installed.
*
* @param object $plugin_data TAn array of plugin metadata.
* @param object $response An object of metadata about the available plugin update.
*
* @return void.
*/
public static function add_install_marketplace_plugin_message( $plugin_data, $response ) {
if ( ! empty( $response->package ) || WC_Woo_Update_Manager_Plugin::is_plugin_active() ) {
return;
}
if ( ! WC_Woo_Update_Manager_Plugin::is_plugin_installed() ) {
printf(
wp_kses(
/* translators: 1: Woo Update Manager plugin install URL */
__( ' <a href="%1$s">Install Woo.com Update Manager</a> to update.', 'woocommerce' ),
array(
'a' => array(
'href' => array(),
),
)
),
esc_url( WC_Woo_Update_Manager_Plugin::generate_install_url() ),
);
return;
}
if ( ! WC_Woo_Update_Manager_Plugin::is_plugin_active() ) {
echo esc_html_e( ' Activate Woo.com Update Manager to update.', 'woocommerce' );
}
}
/** /**
* Get update data for all plugins. * Get update data for all plugins.
* *
@ -245,6 +313,11 @@ class WC_Helper_Updater {
$active_for_translations = array_filter( $active_for_translations = array_filter(
$active_woo_plugins, $active_woo_plugins,
function( $plugin ) use ( $plugins ) { function( $plugin ) use ( $plugins ) {
/**
* Filters the plugins that are subscribed to the automatic translations updates.
*
* @since 3.7.0
*/
return apply_filters( 'woocommerce_translations_updates_for_' . $plugins[ $plugin ]['slug'], false ); return apply_filters( 'woocommerce_translations_updates_for_' . $plugins[ $plugin ]['slug'], false );
} }
); );
@ -274,7 +347,7 @@ class WC_Helper_Updater {
$raw_response = wp_remote_post( $raw_response = wp_remote_post(
'https://translate.wordpress.com/api/translations-updates/woocommerce', 'https://translate.wordpress.com/api/translations-updates/woocommerce',
array( array(
'body' => json_encode( $request_body ), 'body' => wp_json_encode( $request_body ),
'headers' => array( 'Content-Type: application/json' ), 'headers' => array( 'Content-Type: application/json' ),
'timeout' => $timeout, 'timeout' => $timeout,
) )
@ -367,44 +440,6 @@ class WC_Helper_Updater {
return $data['products']; return $data['products'];
} }
/**
* Check for an active subscription.
*
* Checks a given product id against all subscriptions on
* the current site. Returns true if at least one active
* subscription is found.
*
* @param int $product_id The product id to look for.
*
* @return bool True if active subscription found.
*/
private static function _has_active_subscription( $product_id ) {
if ( ! isset( $auth ) ) {
$auth = WC_Helper_Options::get( 'auth' );
}
if ( ! isset( $subscriptions ) ) {
$subscriptions = WC_Helper::get_subscriptions();
}
if ( empty( $auth['site_id'] ) || empty( $subscriptions ) ) {
return false;
}
// Check for an active subscription.
foreach ( $subscriptions as $subscription ) {
if ( $subscription['product_id'] != $product_id ) {
continue;
}
if ( in_array( absint( $auth['site_id'] ), $subscription['connections'] ) ) {
return true;
}
}
return false;
}
/** /**
* Get the number of products that have updates. * Get the number of products that have updates.
* *

View File

@ -54,14 +54,15 @@ class WC_Helper {
* Include supporting helper classes. * Include supporting helper classes.
*/ */
protected static function includes() { protected static function includes() {
include_once dirname( __FILE__ ) . '/class-wc-helper-options.php'; include_once __DIR__ . '/class-wc-helper-options.php';
include_once dirname( __FILE__ ) . '/class-wc-helper-api.php'; include_once __DIR__ . '/class-wc-helper-api.php';
include_once dirname( __FILE__ ) . '/class-wc-helper-updater.php'; include_once __DIR__ . '/class-wc-woo-update-manager-plugin.php';
include_once dirname( __FILE__ ) . '/class-wc-helper-plugin-info.php'; include_once __DIR__ . '/class-wc-helper-updater.php';
include_once dirname( __FILE__ ) . '/class-wc-helper-compat.php'; include_once __DIR__ . '/class-wc-plugin-api-updater.php';
include_once dirname( __FILE__ ) . '/class-wc-helper-admin.php'; include_once __DIR__ . '/class-wc-helper-compat.php';
include_once dirname( __FILE__ ) . '/class-wc-helper-subscriptions-api.php'; include_once __DIR__ . '/class-wc-helper-admin.php';
include_once dirname( __FILE__ ) . '/class-wc-helper-orders-api.php'; include_once __DIR__ . '/class-wc-helper-subscriptions-api.php';
include_once __DIR__ . '/class-wc-helper-orders-api.php';
} }
/** /**
@ -832,6 +833,7 @@ class WC_Helper {
) )
) )
: '', : '',
'wum-installed' => WC_Woo_Update_Manager_Plugin::is_plugin_installed() ? '1' : '0',
), ),
WC_Helper_API::url( 'oauth/authorize' ) WC_Helper_API::url( 'oauth/authorize' )
); );
@ -1193,34 +1195,18 @@ class WC_Helper {
* Get a subscriptions install URL. * Get a subscriptions install URL.
* *
* @param string $product_key Subscription product key. * @param string $product_key Subscription product key.
* @param string $product_slug Subscription product slug.
* @return string * @return string
*/ */
public static function get_subscription_install_url( $product_key ) { public static function get_subscription_install_url( $product_key, $product_slug ) {
$install_url_response = WC_Helper_API::post( $install_url = add_query_arg(
'install-url',
array( array(
'authenticated' => true, 'product-key' => $product_key,
'body' => wp_json_encode(
array(
'product_key' => $product_key,
'wc_version' => WC()->version,
)
), ),
) self::get_install_base_url() . "{$product_slug}/"
); );
$code = wp_remote_retrieve_response_code( $install_url_response ); return WC_Helper_API::add_auth_parameters( $install_url );
if ( 200 !== $code ) {
self::log( sprintf( 'Install URL API call returned a non-200 response code (%d)', $code ) );
return '';
}
$body = json_decode( wp_remote_retrieve_body( $install_url_response ), true );
if ( empty( $body['data']['url'] ) ) {
self::log( sprintf( 'Install URL API call returned an invalid body: %s', wp_remote_retrieve_body( $install_url_response ) ) );
return '';
}
return $body['data']['url'];
} }
/** /**
@ -1517,6 +1503,8 @@ class WC_Helper {
$source = 'inbox-notes'; $source = 'inbox-notes';
elseif ( stripos( $request_uri, 'admin-ajax.php' ) ) : elseif ( stripos( $request_uri, 'admin-ajax.php' ) ) :
$source = 'heartbeat-api'; $source = 'heartbeat-api';
elseif ( stripos( $request_uri, 'installer' ) ) :
$source = 'wccom-site-installer';
elseif ( defined( 'WP_CLI' ) && WP_CLI ) : elseif ( defined( 'WP_CLI' ) && WP_CLI ) :
$source = 'wc-cli'; $source = 'wc-cli';
endif; endif;
@ -2257,6 +2245,22 @@ class WC_Helper {
self::_flush_subscriptions_cache(); self::_flush_subscriptions_cache();
self::_flush_updates_cache(); self::_flush_updates_cache();
} }
/**
* Get base URL for plugin auto installer.
*
* @return string
*/
public static function get_install_base_url() {
/**
* Filter the base URL used to install the Woo hosted plugins.
*
* @since 8.7.0
*/
$woo_com_base_url = apply_filters( 'woo_com_base_url', 'https://woo.com/' );
return $woo_com_base_url . 'auto-install-init/';
}
} }
WC_Helper::load(); WC_Helper::load();

View File

@ -1,21 +1,17 @@
<?php <?php
/** /**
* WooCommerce Admin Helper Plugin Info * Updates the Product API response from WP.org.
* *
* @class WC_Plugin_Api_Updater
* @package WooCommerce\Admin\Helper * @package WooCommerce\Admin\Helper
*/ */
if ( ! defined( 'ABSPATH' ) ) { defined( 'ABSPATH' ) || exit;
exit;
}
/** /**
* WC_Helper_Plugin_Info Class * Class WC_Plugin_Api_Updater
*
* Provides the "View Information" core modals with data for Woo.com
* hosted extensions.
*/ */
class WC_Helper_Plugin_Info { class WC_Plugin_Api_Updater {
/** /**
* Loads the class, runs on init. * Loads the class, runs on init.
@ -38,7 +34,8 @@ class WC_Helper_Plugin_Info {
if ( 'plugin_information' !== $action ) { if ( 'plugin_information' !== $action ) {
return $response; return $response;
} }
return self::maybe_override_products_api( $response, $action, $args );
return self::override_products_api_response( $response, $action, $args );
} }
/** /**
@ -52,7 +49,8 @@ class WC_Helper_Plugin_Info {
if ( 'theme_information' !== $action ) { if ( 'theme_information' !== $action ) {
return $response; return $response;
} }
return self::maybe_override_products_api( $response, $action, $args );
return self::override_products_api_response( $response, $action, $args );
} }
/** /**
@ -62,12 +60,12 @@ class WC_Helper_Plugin_Info {
* @param string $action The requested action. * @param string $action The requested action.
* @param object $args Arguments passed to the API. * @param object $args Arguments passed to the API.
*/ */
public static function maybe_override_products_api( $response, $action, $args ) { public static function override_products_api_response( $response, $action, $args ) {
if ( empty( $args->slug ) ) { if ( empty( $args->slug ) ) {
return $response; return $response;
} }
// Only for slugs that start with woo- // Only for slugs that start with woocommerce-com-.
if ( 0 !== strpos( $args->slug, 'woocommerce-com-' ) ) { if ( 0 !== strpos( $args->slug, 'woocommerce-com-' ) ) {
return $response; return $response;
} }
@ -105,4 +103,4 @@ class WC_Helper_Plugin_Info {
} }
} }
WC_Helper_Plugin_Info::load(); WC_Plugin_Api_Updater::load();

View File

@ -0,0 +1,130 @@
<?php
/**
* A utility class for Woo Update Manager plugin.
*
* @class WC_Woo_Update_Manager_Plugin
* @package WooCommerce\Admin\Helper
*/
use Automattic\WooCommerce\Admin\PageController;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC_Helper_Plugin Class
*
* Contains the logic to manage the Woo Update Manager plugin.
*/
class WC_Woo_Update_Manager_Plugin {
const WOO_UPDATE_MANAGER_PLUGIN_MAIN_FILE = 'woo-update-manager/woo-update-manager.php';
const WOO_UPDATE_MANAGER_DOWNLOAD_URL = 'https://woo.com/product-download/woo-update-manager';
const WOO_UPDATE_MANAGER_SLUG = 'woo-update-manager';
/**
* Loads the class, runs on init.
*
* @return void
*/
public static function load(): void {
add_action( 'admin_notices', array( __CLASS__, 'show_woo_update_manager_install_notice' ) );
}
/**
* Check if the Woo Update Manager plugin is active.
*
* @return bool
*/
public static function is_plugin_active(): bool {
return is_plugin_active_for_network( self::WOO_UPDATE_MANAGER_PLUGIN_MAIN_FILE ) || is_plugin_active( self::WOO_UPDATE_MANAGER_PLUGIN_MAIN_FILE );
}
/**
* Check if the Woo Update Manager plugin is installed.
*
* @return bool
*/
public static function is_plugin_installed(): bool {
return file_exists( WP_PLUGIN_DIR . '/' . self::WOO_UPDATE_MANAGER_PLUGIN_MAIN_FILE );
}
/**
* Generate the URL to install the Woo Update Manager plugin.
*
* @return string
*/
public static function generate_install_url(): string {
$install_url = WC_Helper::get_install_base_url() . self::WOO_UPDATE_MANAGER_SLUG . '/';
return WC_Helper_API::add_auth_parameters( $install_url );
}
/**
* Get the id of the Woo Update Manager plugin.
*
* @return int
*/
public static function get_plugin_slug(): string {
return self::WOO_UPDATE_MANAGER_SLUG;
}
/**
* Show a notice on the WC admin pages to install or activate the Woo Update Manager plugin.
*
* @return void
*/
public static function show_woo_update_manager_install_notice(): void {
if ( ! current_user_can( 'install_plugins' ) ) {
return;
}
if ( ! WC_Helper::is_site_connected() ) {
return;
}
if ( ! PageController::is_admin_or_embed_page() ) {
return;
}
if ( self::is_plugin_installed() && self::is_plugin_active() ) {
return;
}
if ( ! self::is_plugin_installed() ) {
if ( self::install_admin_notice_dismissed() ) {
return;
}
include __DIR__ . '/views/html-notice-woo-updater-not-installed.php';
return;
}
if ( self::activate_admin_notice_dismissed() ) {
return;
}
include __DIR__ . '/views/html-notice-woo-updater-not-activated.php';
}
/**
* Check if the installation notice has been dismissed.
*
* @return bool
*/
protected static function install_admin_notice_dismissed(): bool {
return get_user_meta( get_current_user_id(), 'dismissed_woo_updater_not_installed_notice', true );
}
/**
* Check if the activation notice has been dismissed.
*
* @return bool
*/
protected static function activate_admin_notice_dismissed(): bool {
return get_user_meta( get_current_user_id(), 'dismissed_woo_updater_not_activated_notice', true );
}
}
WC_Woo_Update_Manager_Plugin::load();

View File

@ -0,0 +1,26 @@
<?php
/**
* Helper Admin Notice - Woo Updater Plugin is not activated.
*
* @package WooCommerce\Views
*/
defined( 'ABSPATH' ) || exit;
?>
<div id="message" class="error woocommerce-message">
<a class="woocommerce-message-close notice-dismiss" href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'wc-hide-notice', 'woo_updater_not_activated' ), 'woocommerce_hide_notices_nonce', '_wc_notice_nonce' ) ); ?>"><?php esc_html_e( 'Dismiss', 'woocommerce' ); ?></a>
<p>
<?php
echo wp_kses_post(
sprintf(
/* translators: 1: WP plugin management URL */
__(
'Please <a href="%1$s">activate the Woo.com Update Manager</a> to continue receiving the updates and streamlined support included in your Woo.com subscriptions.',
'woocommerce'
),
esc_url( admin_url( 'plugins.php' ) ),
)
);
?>
</p>
</div>

View File

@ -0,0 +1,27 @@
<?php
/**
* Helper Admin Notice - Woo Updater Plugin is not Installed.
*
* @package WooCommerce\Views
*/
defined( 'ABSPATH' ) || exit;
?>
<div id="message" class="error woocommerce-message">
<a class="woocommerce-message-close notice-dismiss" href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'wc-hide-notice', 'woo_updater_not_installed' ), 'woocommerce_hide_notices_nonce', '_wc_notice_nonce' ) ); ?>"><?php esc_html_e( 'Dismiss', 'woocommerce' ); ?></a>
<p>
<?php
echo wp_kses_post(
sprintf(
/* translators: 1: Woo Update Manager plugin install URL 2: Woo Update Manager plugin download URL */
__(
'Please <a href="%1$s">Install the Woo.com Update Manager</a> to continue receiving the updates and streamlined support included in your Woo.com subscriptions. Alternatively, you can <a href="%2$s">download</a> and install it manually.',
'woocommerce'
),
esc_url( WC_Woo_Update_Manager_Plugin::generate_install_url() ),
esc_url( WC_Woo_Update_Manager_Plugin::WOO_UPDATE_MANAGER_DOWNLOAD_URL )
)
);
?>
</p>
</div>

View File

@ -223,6 +223,7 @@ class WC_WCCOM_Site {
require_once WC_ABSPATH . 'includes/wccom-site/rest-api/endpoints/abstract-wc-rest-wccom-site-controller.php'; require_once WC_ABSPATH . 'includes/wccom-site/rest-api/endpoints/abstract-wc-rest-wccom-site-controller.php';
require_once WC_ABSPATH . 'includes/wccom-site/rest-api/endpoints/class-wc-rest-wccom-site-installer-controller.php'; require_once WC_ABSPATH . 'includes/wccom-site/rest-api/endpoints/class-wc-rest-wccom-site-installer-controller.php';
require_once WC_ABSPATH . 'includes/wccom-site/rest-api/endpoints/class-wc-rest-wccom-site-ssr-controller.php'; require_once WC_ABSPATH . 'includes/wccom-site/rest-api/endpoints/class-wc-rest-wccom-site-ssr-controller.php';
require_once WC_ABSPATH . 'includes/wccom-site/rest-api/endpoints/class-wc-rest-wccom-site-status-controller.php';
require_once WC_ABSPATH . 'includes/wccom-site/installation/class-wc-wccom-site-installation-state.php'; require_once WC_ABSPATH . 'includes/wccom-site/installation/class-wc-wccom-site-installation-state.php';
require_once WC_ABSPATH . 'includes/wccom-site/installation/class-wc-wccom-site-installation-state-storage.php'; require_once WC_ABSPATH . 'includes/wccom-site/installation/class-wc-wccom-site-installation-state-storage.php';
@ -238,6 +239,7 @@ class WC_WCCOM_Site {
$namespaces['wccom-site/v2'] = array( $namespaces['wccom-site/v2'] = array(
'installer' => 'WC_REST_WCCOM_Site_Installer_Controller', 'installer' => 'WC_REST_WCCOM_Site_Installer_Controller',
'ssr' => 'WC_REST_WCCOM_Site_SSR_Controller', 'ssr' => 'WC_REST_WCCOM_Site_SSR_Controller',
'status' => 'WC_REST_WCCOM_Site_Status_Controller',
); );
return $namespaces; return $namespaces;

View File

@ -0,0 +1,78 @@
<?php
/**
* WCCOM Site Status REST API Controller
*
* Handle requests to /status.
*
* @package WooCommerce\WCCom\API
* @since 8.7.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API WCCOM Site Status Controller Class.
*
* @extends WC_REST_WCCOM_Site_Status_Controller
*/
class WC_REST_WCCOM_Site_Status_Controller extends WC_REST_WCCOM_Site_Controller {
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'status';
/**
* Register the routes for Site Status Controller.
*
* @since 8.7.0
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'handle_status_request' ),
'permission_callback' => array( $this, 'check_permission' ),
),
),
);
}
/**
* Check whether user has permission to access controller's endpoints.
*
* @since 8.7.0
* @param WP_USER $user User object.
* @return bool
*/
public function user_has_permission( $user ): bool {
return user_can( $user, 'install_plugins' ) && user_can( $user, 'activate_plugins' );
}
/**
* Get the status details of the site.
*
* @since 8.7.0
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response
*/
public function handle_status_request( $request ) {
return rest_ensure_response(
array(
'success' => true,
'data' => array(
'wc_version' => WC()->version,
'woo_update_manager_installed' => WC_Woo_Update_Manager_Plugin::is_plugin_installed(),
'woo_update_manager_active' => WC_Woo_Update_Manager_Plugin::is_plugin_active(),
),
)
);
}
}

View File

@ -167,16 +167,17 @@ class PageController {
return apply_filters( 'woocommerce_navigation_get_breadcrumbs', array( '' ), $current_page ); return apply_filters( 'woocommerce_navigation_get_breadcrumbs', array( '' ), $current_page );
} }
$current_page['title'] = (array) $current_page['title']; $page_title = ! empty( $current_page['page_title'] ) ? $current_page['page_title'] : $current_page['title'];
if ( 1 === count( $current_page['title'] ) ) { $page_title = (array) $page_title;
$breadcrumbs = $current_page['title']; if ( 1 === count( $page_title ) ) {
$breadcrumbs = $page_title;
} else { } else {
// If this page has multiple title pieces, only link the first one. // If this page has multiple title pieces, only link the first one.
$breadcrumbs = array_merge( $breadcrumbs = array_merge(
array( array(
array( $current_page['path'], reset( $current_page['title'] ) ), array( $current_page['path'], reset( $page_title ) ),
), ),
array_slice( $current_page['title'], 1 ) array_slice( $page_title, 1 )
); );
} }
@ -438,6 +439,7 @@ class PageController {
'id' => null, 'id' => null,
'parent' => null, 'parent' => null,
'title' => '', 'title' => '',
'page_title' => '',
'capability' => 'view_woocommerce_reports', 'capability' => 'view_woocommerce_reports',
'path' => '', 'path' => '',
'icon' => '', 'icon' => '',
@ -455,9 +457,13 @@ class PageController {
$options['position'] = intval( round( $options['position'] ) ); $options['position'] = intval( round( $options['position'] ) );
} }
if ( empty( $options['page_title'] ) ) {
$options['page_title'] = $options['title'];
}
if ( is_null( $options['parent'] ) ) { if ( is_null( $options['parent'] ) ) {
add_menu_page( add_menu_page(
$options['title'], $options['page_title'],
$options['title'], $options['title'],
$options['capability'], $options['capability'],
$options['path'], $options['path'],
@ -470,7 +476,7 @@ class PageController {
// @todo check for null path. // @todo check for null path.
add_submenu_page( add_submenu_page(
$parent_path, $parent_path,
$options['title'], $options['page_title'],
$options['title'], $options['title'],
$options['capability'], $options['capability'],
$options['path'], $options['path'],

View File

@ -7,6 +7,8 @@ namespace Automattic\WooCommerce\Internal\Admin;
use Automattic\WooCommerce\Utilities\FeaturesUtil; use Automattic\WooCommerce\Utilities\FeaturesUtil;
use Automattic\WooCommerce\Internal\Features\FeaturesController; use Automattic\WooCommerce\Internal\Features\FeaturesController;
use WC_Helper_Updater;
use WC_Woo_Update_Manager_Plugin;
/** /**
* Contains backend logic for the Marketplace feature. * Contains backend logic for the Marketplace feature.
@ -17,6 +19,8 @@ class Marketplace {
/** /**
* Class initialization, to be executed when the class is resolved by the container. * Class initialization, to be executed when the class is resolved by the container.
*
* @internal
*/ */
final public function init() { final public function init() {
if ( false === FeaturesUtil::feature_is_enabled( 'marketplace' ) ) { if ( false === FeaturesUtil::feature_is_enabled( 'marketplace' ) ) {
@ -57,7 +61,8 @@ class Marketplace {
array( array(
'id' => 'woocommerce-marketplace', 'id' => 'woocommerce-marketplace',
'parent' => 'woocommerce', 'parent' => 'woocommerce',
'title' => __( 'Extensions', 'woocommerce' ), 'title' => __( 'Extensions', 'woocommerce' ) . self::get_marketplace_update_count_html(),
'page_title' => __( 'Extensions', 'woocommerce' ),
'path' => '/extensions', 'path' => '/extensions',
), ),
); );
@ -70,6 +75,29 @@ class Marketplace {
return apply_filters( 'woocommerce_marketplace_menu_items', $marketplace_pages ); return apply_filters( 'woocommerce_marketplace_menu_items', $marketplace_pages );
} }
/**
* Create the menu bubble for extensions menu based on number of updates available.
*
* @return string
*/
private static function get_marketplace_update_count_html() {
$count = WC_Helper_Updater::get_updates_count();
if ( empty( $count ) ) {
$count = 0;
}
$count = intval( $count );
if ( ! WC_Woo_Update_Manager_Plugin::is_plugin_installed() || ! WC_Woo_Update_Manager_Plugin::is_plugin_active() ) {
++$count;
}
if ( 0 === $count ) {
return '';
}
return sprintf( ' <span class="update-plugins count-%d"><span class="update-count">%d</span></span>', $count, number_format_i18n( $count ) );
}
/** /**
* Enqueue update script. * Enqueue update script.
* *