updated plugin Jetpack Protect version 2.2.0

This commit is contained in:
2024-06-27 12:10:57 +00:00
committed by Gitium
parent ec9d8a5834
commit 938cef2946
218 changed files with 7469 additions and 1864 deletions

View File

@ -5,6 +5,145 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [4.24.1] - 2024-05-24
### Changed
- Update Search to require user connection. [#37496]
## [4.24.0] - 2024-05-23
### Changed
- Improve the active/inactive statuses on My Jetpack. [#37217]
## [4.23.3] - 2024-05-20
### Added
- Add tracks events for dropdown on action buttons. [#37292]
### Changed
- Updated package dependencies. [#37379] [#37380] [#37382]
## [4.23.2] - 2024-05-09
### Changed
- My Jetpack Agency banner copy change. [#37248]
### Fixed
- Fix typo [#37303]
## [4.23.1] - 2024-05-08
### Changed
- Update dependencies.
## [4.23.0] - 2024-05-06
### Added
- Add a has_free_offering boolean on My Jetpack products. [#36710]
- Add copy for AI features on connection screen. [#37218]
- Define `$module_name` static property on `Hybrid_Product` (defaulting to null). [#37201]
- Jetpack AI: Add new badge to Featured Image video on the product page. [#37197]
- My Jetpack: Add errors from the connection package to the new notice component. [#36840]
### Changed
- Change "go back" in My Jetpack interstitials to always return to the My Jetpack page. [#36685]
- Fix z-index issue and close action button dropdown when clicked outside. [#37169]
- Jetpack AI: Add featured image generation as a feature on the product interstitial. [#37199]
- Jetpack AI: Include video about featured image generation on the product page. [#37192]
- Updated package dependencies. [#37147] [#37148] [#37160]
## [4.22.3] - 2024-04-29
### Changed
- Internal updates.
## [4.22.2] - 2024-04-25
### Fixed
- My Jetpack: Fix issue where the TOS component was being called inside of a <p>, throwing a warning that <p> can't be a descendant of <p>. This also fixes the font size of the TOS text. [#37034]
## [4.22.1] - 2024-04-22
### Changed
- Internal updates.
## [4.22.0] - 2024-04-11
### Added
- Add new tracking event for product activations made through My Jetpack [#36745]
- My Jetpack: add a way to connect a Jetpack site to WordPress.com asynchronously and update the message in the notice to reflect that. [#36771]
### Fixed
- Ensure page_view gets loaded before product_card_loaded [#36790]
## [4.21.0] - 2024-04-08
### Added
- Add 'from' property to connection call to WP.com. [#36741]
- My Jetpack: Update Notice component to allow adding a loading text when an action is in a loading state. Add a new resetNotice action to NoticeContext. [#36614]
### Changed
- My Jetpack: Show Boost score increase in Boost product card. [#36072]
- Updated package dependencies. [#36756] [#36760] [#36761]
- Update My Jetpack to use the Notice component from @automattic/jetpack-components and be more consistent with the other notices in Jetpack. [#36711]
### Removed
- My Jetpack: Removed custom Notice component from My Jetpack. [#36772]
- My Jetpack: Remove red bubble from connection notice in favor of using the status of the Notice component. [#36773]
### Fixed
- Fix cache on front end request for boost speed scores [#36700]
- fix tier upgrades in my Jetpack [#36705]
## [4.20.2] - 2024-04-02
### Fixed
- Fix Boost score inconsistency [#36679]
## [4.20.1] - 2024-04-01
### Added
- Change Phan baselines. [#36627]
## [4.20.0] - 2024-03-29
### Added
- Track active red bubble slugs on My Jetpack page view [#36611]
### Fixed
- Better handling on product interstitial pages if the site already has a paid product [#36570]
- Shows Jetpack CRM card as active on My Jetpack if the plugin is installed and active [#36594]
## [4.19.0] - 2024-03-27
### Added
- Add red bubble to notices tied to red bubble notifications [#36543]
- My Jetpack: add a version of WordPress' Notice component to My Jetpack considering the context of how we use notices on that screen [#36551]
### Changed
- Updated package dependencies. [#36539, #36585]
### Fixed
- Fixed Jetpack Creator going to the wrong screen when the free version is selected" [#36547]
- Fixes some pricing showing twice by connecting sites that select a free option [#36533]
## [4.18.0] - 2024-03-25
### Added
- Hook into red bubble notification when bad installation is detected [#36449]
- Jetpack AI: add notices on product page for exhausted requests [#35910]
- Jetpack AI: add plans/tier information on product page and corresponding CTAs [#35910]
- Jetpack AI: add support and create post links on product page [#35910]
- My Jetpack: add AI product page view event [#36488]
- My Jetpack: add feedback link on Jetpack AI product page [#35910]
- My Jetpack: AI pricing table is skipped once the user has opted for the free version [#35910]
- My Jetpack: change AI product for tiered pricing table display [#35910]
### Changed
- Add notice priorities to My Jetpack [#36438]
- Jetpack AI: address responsive issues on the styles [#35910]
- My Jetpack: AI product page styles update and responsive fixes [#35910]
- My Jetpack: change AI card action button target for upgraded users, point to product page [#35910]
### Removed
- My Jetpack: remove Jetpack AI code added throughout the new product page project [#35910]
- Removed reference to Creator Network, which is being deprecated. [#36168]
### Fixed
- Boost tooltips: fix typo in string. [#36520]
- My Jetpack: fix AI interstitial "remain free" flow [#35910]
- My Jetpack: fix interstitial event property malformed name productSlug -> product_slug [#36486]
- My Jetpack: fix spacing issues on the new product page [#35910]
- My Jetpack: new AI interstitial margin on admin page was messing with correct top spacing [#35910]
## [4.17.1] - 2024-03-18
### Added
- Add a loaded event for My Jetpack product cards [#36397]
## [4.17.0] - 2024-03-14
### Changed
- Rewrite My Jetpack hooks to Typescript [#36288]
@ -1347,6 +1486,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Created package
[4.24.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.24.0...4.24.1
[4.24.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.23.3...4.24.0
[4.23.3]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.23.2...4.23.3
[4.23.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.23.1...4.23.2
[4.23.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.23.0...4.23.1
[4.23.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.22.3...4.23.0
[4.22.3]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.22.2...4.22.3
[4.22.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.22.1...4.22.2
[4.22.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.22.0...4.22.1
[4.22.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.21.0...4.22.0
[4.21.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.20.2...4.21.0
[4.20.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.20.1...4.20.2
[4.20.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.20.0...4.20.1
[4.20.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.19.0...4.20.0
[4.19.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.18.0...4.19.0
[4.18.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.17.1...4.18.0
[4.17.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.17.0...4.17.1
[4.17.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.16.0...4.17.0
[4.16.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.15.0...4.16.0
[4.15.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.14.0...4.15.0

View File

@ -1 +1 @@
<?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives', 'wp-url'), 'version' => '5782f9bf704638c25046');
<?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives', 'wp-url'), 'version' => 'cc72b3b90919a178b53e');

View File

@ -5,23 +5,22 @@
"license": "GPL-2.0-or-later",
"require": {
"php": ">=7.0",
"automattic/jetpack-admin-ui": "^0.4.1",
"automattic/jetpack-assets": "^2.1.4",
"automattic/jetpack-boost-speed-score": "^0.3.6",
"automattic/jetpack-connection": "^2.4.1",
"automattic/jetpack-jitm": "^3.1.1",
"automattic/jetpack-licensing": "^2.0.2",
"automattic/jetpack-plugins-installer": "^0.3.2",
"automattic/jetpack-redirect": "^2.0.1",
"automattic/jetpack-constants": "^2.0.1",
"automattic/jetpack-plans": "^0.4.3",
"automattic/jetpack-status": "^2.1.2"
"automattic/jetpack-admin-ui": "^0.4.2",
"automattic/jetpack-assets": "^2.1.11",
"automattic/jetpack-boost-speed-score": "^0.3.11",
"automattic/jetpack-connection": "^2.8.4",
"automattic/jetpack-jitm": "^3.1.11",
"automattic/jetpack-licensing": "^2.0.5",
"automattic/jetpack-plugins-installer": "^0.4.0",
"automattic/jetpack-redirect": "^2.0.2",
"automattic/jetpack-constants": "^2.0.2",
"automattic/jetpack-plans": "^0.4.7",
"automattic/jetpack-status": "^3.2.0"
},
"require-dev": {
"yoast/phpunit-polyfills": "1.1.0",
"automattic/jetpack-changelogger": "^4.1.1",
"automattic/wordbless": "@dev",
"automattic/jetpack-videopress": "^0.23.10"
"automattic/jetpack-changelogger": "^4.2.4",
"automattic/wordbless": "@dev"
},
"suggest": {
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
@ -69,10 +68,16 @@
"link-template": "https://github.com/Automattic/jetpack-my-jetpack/compare/${old}...${new}"
},
"branch-alias": {
"dev-trunk": "4.17.x-dev"
"dev-trunk": "4.24.x-dev"
},
"version-constants": {
"::PACKAGE_VERSION": "src/class-initializer.php"
},
"dependencies": {
"test-only": [
"packages/search",
"packages/videopress"
]
}
},
"config": {

View File

@ -1,7 +1,24 @@
declare module '*.png';
declare module '*.svg';
declare module '*.jpeg';
declare module '*.jpg';
declare module '*.scss';
// These libraries don't have types, this suppresses the TypeScript errors
declare module '@wordpress/components';
declare module '@wordpress/compose';
declare module '@wordpress/i18n';
declare module '@wordpress/icons';
interface Window {
myJetpackInitialState?: {
siteSuffix: string;
siteUrl: string;
latestBoostSpeedScores: {
previousScores: {
desktop: number;
mobile: number;
};
scores: {
desktop: number;
mobile: number;
@ -62,8 +79,6 @@ interface Window {
features: string[];
has_paid_plan_for_product: boolean;
features_by_tier: Array< string >;
has_required_plan: boolean;
has_required_tier: Array< string >;
is_bundle: boolean;
is_plugin_active: boolean;
is_upgradable_by_bundle: string[];
@ -179,12 +194,18 @@ interface Window {
check_dns: boolean;
} >;
};
redBubbleAlerts: {
'missing-site-connection'?: null;
'welcome-banner-active'?: null;
[ key: `${ string }-bad-installation` ]: {
data: {
plugin: string;
};
};
};
topJetpackMenuItemUrl: string;
userIsAdmin: string;
userIsNewToJetpack: string;
welcomeBanner: {
hasBeenDismissed: boolean;
};
};
JP_CONNECTION_INITIAL_STATE: {
apiRoot: string;

View File

@ -9,7 +9,6 @@ namespace Automattic\Jetpack\My_Jetpack;
use Automattic\Jetpack\Admin_UI\Admin_Menu;
use Automattic\Jetpack\Assets;
use Automattic\Jetpack\Boost_Speed_Score\Jetpack_Boost_Modules;
use Automattic\Jetpack\Boost_Speed_Score\Speed_Score;
use Automattic\Jetpack\Boost_Speed_Score\Speed_Score_History;
use Automattic\Jetpack\Connection\Client;
@ -26,6 +25,7 @@ use Automattic\Jetpack\Status\Host as Status_Host;
use Automattic\Jetpack\Terms_Of_Service;
use Automattic\Jetpack\Tracking;
use Jetpack;
use WP_Error;
/**
* The main Initializer class that registers the admin menu and eneuque the assets.
@ -37,7 +37,7 @@ class Initializer {
*
* @var string
*/
const PACKAGE_VERSION = '4.17.0';
const PACKAGE_VERSION = '4.24.1';
/**
* HTML container ID for the IDC screen on My Jetpack page.
@ -62,7 +62,7 @@ class Initializer {
/**
* Holds info/data about the site (from the /sites/%d endpoint)
*
* @var stdClass Object
* @var object
*/
public static $site_info;
@ -87,8 +87,7 @@ class Initializer {
}
// Initialize Boost Speed Score
$boost_modules = Jetpack_Boost_Modules::init();
new Speed_Score( $boost_modules, 'jetpack-my-jetpack' );
new Speed_Score( array(), 'jetpack-my-jetpack' );
// Add custom WP REST API endoints.
add_action( 'rest_api_init', array( __CLASS__, 'register_rest_endpoints' ) );
@ -200,7 +199,14 @@ class Initializer {
);
$modules = new Modules();
$connection = new Connection_Manager();
$speed_score_history = new Speed_Score_History( wp_parse_url( get_site_url(), PHP_URL_HOST ) );
$speed_score_history = new Speed_Score_History( get_site_url() );
$latest_score = $speed_score_history->latest();
$previous_score = array();
if ( $speed_score_history->count() > 1 ) {
$previous_score = $speed_score_history->latest( 1 );
}
$latest_score['previousScores'] = $previous_score['scores'] ?? array();
wp_localize_script(
'my_jetpack_main_app',
'myJetpackInitialState',
@ -216,6 +222,7 @@ class Initializer {
'myJetpackCheckoutUri' => admin_url( 'admin.php?page=my-jetpack' ),
'topJetpackMenuItemUrl' => Admin_Menu::get_top_level_menu_item_url(),
'siteSuffix' => ( new Status() )->get_site_suffix(),
'siteUrl' => esc_url( get_site_url() ),
'blogID' => Connection_Manager::get_site_id( true ),
'myJetpackVersion' => self::PACKAGE_VERSION,
'myJetpackFlags' => self::get_my_jetpack_flags(),
@ -237,14 +244,11 @@ class Initializer {
'isUserFromKnownHost' => self::is_user_from_known_host(),
'isCommercial' => self::is_commercial_site(),
'isAtomic' => ( new Status_Host() )->is_woa_site(),
'welcomeBanner' => array(
'hasBeenDismissed' => \Jetpack_Options::get_option( 'dismissed_welcome_banner', false ),
),
'jetpackManage' => array(
'isEnabled' => Jetpack_Manage::could_use_jp_manage(),
'isAgencyAccount' => Jetpack_Manage::is_agency_account(),
),
'latestBoostSpeedScores' => $speed_score_history->latest(),
'latestBoostSpeedScores' => $latest_score,
)
);
@ -496,7 +500,7 @@ class Initializer {
$body = json_decode( wp_remote_retrieve_body( $response ) );
if ( is_wp_error( $response ) || empty( $response['body'] ) ) {
return new \WP_Error( 'site_data_fetch_failed', 'Site data fetch failed', array( 'status' => $response_code ) );
return new WP_Error( 'site_data_fetch_failed', 'Site data fetch failed', array( 'status' => $response_code ) );
}
return rest_ensure_response( $body, 200 );
@ -505,7 +509,7 @@ class Initializer {
/**
* Populates the self::$site_info var with site data from the /sites/%d endpoint
*
* @return Object|WP_Error
* @return object|WP_Error
*/
public static function get_site_info() {
static $site_info = null;
@ -620,7 +624,7 @@ class Initializer {
public static function maybe_show_red_bubble() {
global $menu;
// filters for the items in this file
add_filter( 'my_jetpack_red_bubble_notification_slugs', array( __CLASS__, 'alert_if_missing_site_connection' ) );
add_filter( 'my_jetpack_red_bubble_notification_slugs', array( __CLASS__, 'add_red_bubble_alerts' ) );
$red_bubble_alerts = self::get_red_bubble_alerts();
// The Jetpack menu item should be on index 3
@ -653,6 +657,22 @@ class Initializer {
return $red_bubble_alerts;
}
/**
* Add relevant red bubble notifications
*
* @param array $red_bubble_slugs - slugs that describe the reasons the red bubble is showing.
* @return array
*/
public static function add_red_bubble_alerts( array $red_bubble_slugs ) {
$welcome_banner_dismissed = \Jetpack_Options::get_option( 'dismissed_welcome_banner', false );
if ( self::is_jetpack_user_new() && ! $welcome_banner_dismissed ) {
$red_bubble_slugs['welcome-banner-active'] = null;
return $red_bubble_slugs;
} else {
return self::alert_if_missing_site_connection( $red_bubble_slugs );
}
}
/**
* Add an alert slug if the site is missing a site connection
*
@ -661,7 +681,7 @@ class Initializer {
*/
public static function alert_if_missing_site_connection( array $red_bubble_slugs ) {
if ( ! ( new Connection_Manager() )->is_connected() ) {
$red_bubble_slugs[] = self::MISSING_SITE_CONNECTION_NOTIFICATION_KEY;
$red_bubble_slugs[ self::MISSING_SITE_CONNECTION_NOTIFICATION_KEY ] = null;
}
return $red_bubble_slugs;

View File

@ -36,6 +36,7 @@ class Products {
'protect' => Products\Protect::class,
'videopress' => Products\Videopress::class,
'stats' => Products\Stats::class,
'ai' => Products\Jetpack_Ai::class,
);
/**
@ -155,7 +156,7 @@ class Products {
'status' => array(
'title' => 'The product status',
'type' => 'string',
'enum' => array( 'active', 'inactive', 'plugin_absent', 'needs_purchase', 'needs_purchase_or_free', 'error' ),
'enum' => array( 'active', 'inactive', 'plugin_absent', 'needs_purchase', 'needs_purchase_or_free', 'needs_first_site_connection', 'user_connection_error', 'site_connection_error' ),
),
'class' => array(
'title' => 'The product class handler',
@ -179,6 +180,7 @@ class Products {
'protect',
'crm',
'search',
'ai',
);
// Add plugin action links for the core Jetpack plugin.

View File

@ -190,6 +190,7 @@ class REST_Products {
$activate_product_result->add_data( array( 'status' => 400 ) );
return $activate_product_result;
}
set_transient( 'my_jetpack_product_activated', $product_slug, 10 );
return rest_ensure_response( Products::get_product( $product_slug ), 200 );
}

View File

@ -9,6 +9,7 @@ namespace Automattic\Jetpack\My_Jetpack;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use WP_Error;
/**
* Registers the REST routes for Purchases.
@ -42,7 +43,7 @@ class REST_Purchases {
$is_site_connected = $connection->is_connected();
if ( ! $is_site_connected ) {
return new \WP_Error(
return new WP_Error(
'not_connected',
__( 'Your site is not connected to Jetpack.', 'jetpack-my-jetpack' ),
array(
@ -68,7 +69,7 @@ class REST_Purchases {
$body = json_decode( wp_remote_retrieve_body( $response ) );
if ( is_wp_error( $response ) || empty( $response['body'] ) || 200 !== $response_code ) {
return new \WP_Error( 'site_data_fetch_failed', 'Site data fetch failed', array( 'status' => $response_code ? $response_code : 400 ) );
return new WP_Error( 'site_data_fetch_failed', 'Site data fetch failed', array( 'status' => $response_code ? $response_code : 400 ) );
}
return rest_ensure_response( $body, 200 );

View File

@ -8,6 +8,8 @@
namespace Automattic\Jetpack\My_Jetpack;
use Automattic\Jetpack\Connection\Client;
use WP_Error;
use WP_REST_Response;
/**
* Registers the REST routes for Zendesk Chat.
@ -56,11 +58,11 @@ class REST_Zendesk_Chat {
* @access public
* @static
*
* @return \WP_Error|true
* @return WP_Error|true
*/
public static function chat_authentication_permissions_callback() {
if ( ! get_current_user_id() ) {
return new \WP_Error( 'unauthorized', 'You must be logged in to access this resource.', array( 'status' => 401 ) );
return new WP_Error( 'unauthorized', 'You must be logged in to access this resource.', array( 'status' => 401 ) );
}
return true;
@ -69,7 +71,7 @@ class REST_Zendesk_Chat {
/**
* Gets the chat authentication token.
*
* @return \WP_Error|object Object: { token: string }
* @return WP_Error|WP_REST_Response { token: string }
*/
public static function get_chat_authentication() {
$authentication = get_transient( self::ZENDESK_AUTH_TOKEN );
@ -91,7 +93,7 @@ class REST_Zendesk_Chat {
$body = json_decode( wp_remote_retrieve_body( $response ) );
if ( is_wp_error( $response ) || empty( $response['body'] ) ) {
return new \WP_Error( 'chat_authentication_failed', 'Chat authentication failed', array( 'status' => $response_code ) );
return new WP_Error( 'chat_authentication_failed', 'Chat authentication failed', array( 'status' => $response_code ) );
}
set_transient( self::ZENDESK_AUTH_TOKEN, $body, self::TRANSIENT_EXPIRY );
@ -102,7 +104,7 @@ class REST_Zendesk_Chat {
* Calls `wpcom/v2/presales/chat?group=jp_presales` endpoint.
* This endpoint returns whether or not the Jetpack presales chat group is available
*
* @return \WP_Error/object Object: { is_available: bool }
* @return WP_Error|WP_REST_Response { is_available: bool }
*/
public static function get_chat_availability() {
$wpcom_endpoint = '/presales/chat?group=jp_presales';
@ -112,7 +114,7 @@ class REST_Zendesk_Chat {
$body = json_decode( wp_remote_retrieve_body( $response ) );
if ( is_wp_error( $response ) || empty( $response['body'] ) ) {
return new \WP_Error( 'chat_config_data_fetch_failed', 'Chat config data fetch failed', array( 'status' => $response_code ) );
return new WP_Error( 'chat_config_data_fetch_failed', 'Chat config data fetch failed', array( 'status' => $response_code ) );
}
return rest_ensure_response( $body, 200 );

View File

@ -202,10 +202,10 @@ class Wpcom_Products {
/**
* Populate the pricing array with the discount information.
*
* @param {object} $product - The product object.
* @param {object} $pricing - The pricing array.
* @param {float} $price - The price to be discounted.
* @return {object} The pricing array with the discount information.
* @param object $product - The product object.
* @param array $pricing - The pricing array.
* @param float $price - The price to be discounted.
* @return array The pricing array with the discount information.
*/
public static function populate_with_discount( $product, $pricing, $price ) {
// Check whether the product has a coupon.

View File

@ -43,6 +43,13 @@ class Anti_Spam extends Product {
*/
public static $requires_user_connection = false;
/**
* Whether this product has a free offering
*
* @var bool
*/
public static $has_free_offering = true;
/**
* Get the product name
*
@ -94,10 +101,11 @@ class Anti_Spam extends Product {
/**
* Determine if the site has an Akismet plan by checking for an API key
* Note that some Akismet Plans are free - we're just checking for an API key and don't have the perspective of the plan attached to it here
*
* @return bool - whether an API key was found
*/
public static function has_required_plan() {
public static function has_paid_plan_for_product() {
// Check if the site has an API key for Akismet
$akismet_api_key = apply_filters( 'akismet_get_api_key', defined( 'WPCOM_API_KEY' ) ? constant( 'WPCOM_API_KEY' ) : get_option( 'wordpress_api_key' ) );
$fallback = ! empty( $akismet_api_key );

View File

@ -51,6 +51,20 @@ class Backup extends Hybrid_Product {
*/
public static $has_standalone_plugin = true;
/**
* Whether this product has a free offering
*
* @var bool
*/
public static $has_free_offering = false;
/**
* Whether this product requires a plan to work at all
*
* @var bool
*/
public static $requires_plan = true;
/**
* Get the product name
*
@ -183,7 +197,7 @@ class Backup extends Hybrid_Product {
*
* @return boolean
*/
public static function has_required_plan() {
public static function has_paid_plan_for_product() {
$rewind_data = static::get_state_from_wpcom();
if ( is_wp_error( $rewind_data ) ) {
return false;
@ -224,13 +238,4 @@ class Backup extends Hybrid_Product {
return Redirect::get_url( 'my-jetpack-manage-backup' );
}
}
/**
* Checks whether the Product is active
*
* @return boolean
*/
public static function is_active() {
return parent::is_active() && static::has_required_plan();
}
}

View File

@ -9,6 +9,7 @@ namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\My_Jetpack\Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
use WP_Error;
/**
* Class responsible for handling the Boost product
@ -50,6 +51,13 @@ class Boost extends Product {
*/
public static $requires_user_connection = false;
/**
* Whether this product has a free offering
*
* @var bool
*/
public static $has_free_offering = true;
/**
* Get the product name
*
@ -303,7 +311,7 @@ class Boost extends Product {
* Activates the product by installing and activating its plugin
*
* @param bool|WP_Error $current_result Is the result of the top level activation actions. You probably won't do anything if it is an WP_Error.
* @return boolean|\WP_Error
* @return boolean|WP_Error
*/
public static function do_product_specific_activation( $current_result ) {

View File

@ -49,6 +49,13 @@ class Creator extends Product {
*/
public static $requires_user_connection = false;
/**
* Whether this product has a free offering
*
* @var bool
*/
public static $has_free_offering = true;
/**
* Get the product name
*
@ -156,20 +163,6 @@ class Creator extends Product {
),
),
),
array(
'name' => __( 'Creator network', 'jetpack-my-jetpack' ),
'info' => array(
'content' => __(
'<p>The creator network is the network of websites either hosted with WordPress.com or self-hosted and connected with Jetpack.</p>
<p>Sites that are part of the creator network can gain exposure to new readers. Sites on the Creator plan have enhanced distribution to more areas of the Reader.</p>',
'jetpack-my-jetpack'
),
),
'tiers' => array(
self::FREE_TIER_SLUG => array( 'included' => true ),
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
),
),
array(
'name' => __( 'Jetpack Blocks', 'jetpack-my-jetpack' ),
'info' => array(
@ -330,7 +323,7 @@ class Creator extends Product {
*
* @return boolean
*/
public static function has_required_plan() {
public static function has_paid_plan_for_product() {
$purchases_data = Wpcom_Products::get_site_current_purchases();
if ( is_wp_error( $purchases_data ) ) {
return false;
@ -352,7 +345,6 @@ class Creator extends Product {
* @return boolean
*/
public static function is_upgradable() {
$has_required_plan = self::has_required_plan();
return ! $has_required_plan;
return ! self::has_paid_plan_for_product();
}
}

View File

@ -46,6 +46,13 @@ class Crm extends Product {
*/
public static $requires_user_connection = false;
/**
* Whether this product has a free offering
*
* @var bool
*/
public static $has_free_offering = true;
/**
* Get the product name
*
@ -140,12 +147,14 @@ class Crm extends Product {
*
* @return boolean
*/
public static function has_required_plan() {
public static function has_paid_plan_for_product() {
$purchases_data = Wpcom_Products::get_site_current_purchases();
if ( is_wp_error( $purchases_data ) ) {
return false;
}
// TODO: check if CRM has a separate plan
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
foreach ( $purchases_data as $purchase ) {
if ( str_starts_with( $purchase->product_slug, 'jetpack_complete' ) ) {

View File

@ -8,6 +8,7 @@
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\My_Jetpack\Product;
use WP_Error;
/**
* Class responsible for handling the Extras product.

View File

@ -17,7 +17,6 @@ use WP_Error;
* Hybrid products are those that may work both as a stand-alone plugin or with the Jetpack plugin.
*/
abstract class Hybrid_Product extends Product {
/**
* All hybrid products have a standalone plugin
*
@ -52,18 +51,6 @@ abstract class Hybrid_Product extends Product {
return parent::is_plugin_active();
}
/**
* Checks whether the Jetpack module is active only if a module_name is defined
*
* @return bool
*/
public static function is_module_active() {
if ( ! empty( static::$module_name ) ) {
return ( new Modules() )->is_active( static::$module_name );
}
return true;
}
/**
* Checks whether the Product is active
*
@ -118,9 +105,13 @@ abstract class Hybrid_Product extends Product {
}
}
// Only activate the module if the plan supports it
// We don't want to throw an error for a missing plan here since we try activation before purchase
if ( static::has_required_plan() && ! empty( static::$module_name ) ) {
if ( ! empty( static::$module_name ) ) {
// Only activate the module if the plan supports it
// We don't want to throw an error for a missing plan here since we try activation before purchase
if ( static::$requires_plan && ! static::has_any_plan_for_product() ) {
return true;
}
$module_activation = ( new Modules() )->activate( static::$module_name, false, false );
if ( ! $module_activation ) {
@ -149,7 +140,7 @@ abstract class Hybrid_Product extends Product {
* Activate the module as well, if the user has a plan
* or the product does not require a plan to work
*/
if ( static::has_required_plan() && isset( static::$module_name ) ) {
if ( static::has_any_plan_for_product() && isset( static::$module_name ) ) {
$module_activation = ( new Modules() )->activate( static::$module_name, false, false );
if ( ! $module_activation ) {

View File

@ -8,14 +8,19 @@
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\My_Jetpack\Initializer;
use Automattic\Jetpack\My_Jetpack\Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
use WP_Post;
/**
* Class responsible for handling the Jetpack AI product
*/
class Jetpack_Ai extends Product {
const CURRENT_TIER_SLUG = 'free';
const UPGRADED_TIER_SLUG = 'upgraded';
/**
* The product slug
*
@ -23,6 +28,13 @@ class Jetpack_Ai extends Product {
*/
public static $slug = 'jetpack-ai';
/**
* Whether this product has a free offering
*
* @var bool
*/
public static $has_free_offering = true;
/**
* Get the Product info for the API
*
@ -75,6 +87,84 @@ class Jetpack_Ai extends Product {
return 'Jetpack AI';
}
/**
* Get the product's available tiers
*
* @return string[] Slugs of the available tiers
*/
public static function get_tiers() {
return array(
self::UPGRADED_TIER_SLUG,
self::CURRENT_TIER_SLUG,
);
}
/**
* Get the internationalized comparison of free vs upgraded features
*
* @return array[] Protect features comparison
*/
public static function get_features_by_tier() {
$current_tier = self::get_current_usage_tier();
$current_description = 0 === $current_tier
? __( 'Up to 20 requests', 'jetpack-my-jetpack' )
/* translators: number of requests */
: sprintf( __( 'Up to %d requests per month', 'jetpack-my-jetpack' ), $current_tier );
$next_tier = self::get_next_usage_tier();
$next_description = $next_tier === null
? __( 'Let\'s get in touch', 'jetpack-my-jetpack' )
/* translators: number of requests */
: sprintf( __( 'Up to %d requests per month', 'jetpack-my-jetpack' ), $next_tier );
return array(
array(
'name' => __( 'Number of requests', 'jetpack-my-jetpack' ),
'info' => array(
'title' => __( 'Requests', 'jetpack-my-jetpack' ),
'content' => __( 'Increase your monthly request limit. Upgrade now and have the option to further increase your requests with additional upgrades.', 'jetpack-my-jetpack' ),
),
'tiers' => array(
self::CURRENT_TIER_SLUG => array(
'included' => true,
'description' => $current_description,
),
self::UPGRADED_TIER_SLUG => array(
'included' => true,
'description' => $next_description,
),
),
),
array(
'name' => __( 'Generate and edit content', 'jetpack-my-jetpack' ),
'tiers' => array(
self::CURRENT_TIER_SLUG => array( 'included' => true ),
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
),
),
array(
'name' => __( 'Build forms from prompts', 'jetpack-my-jetpack' ),
'tiers' => array(
self::CURRENT_TIER_SLUG => array( 'included' => true ),
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
),
),
array(
'name' => __( 'Get feedback on posts', 'jetpack-my-jetpack' ),
'tiers' => array(
self::CURRENT_TIER_SLUG => array( 'included' => true ),
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
),
),
array(
'name' => __( 'Generate featured images', 'jetpack-my-jetpack' ),
'tiers' => array(
self::CURRENT_TIER_SLUG => array( 'included' => true ),
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
),
),
);
}
/**
* Get the current usage tier
*
@ -89,7 +179,7 @@ class Jetpack_Ai extends Product {
// Bail early if it's not possible to fetch the feature data.
if ( is_wp_error( $info ) ) {
return null;
return 0;
}
$current_tier = isset( $info['current-tier']['value'] ) ? $info['current-tier']['value'] : null;
@ -103,7 +193,7 @@ class Jetpack_Ai extends Product {
* @return int
*/
public static function get_next_usage_tier() {
if ( ! self::is_site_connected() || ! self::has_required_plan() ) {
if ( ! self::is_site_connected() || ! self::has_paid_plan_for_product() ) {
return 100;
}
@ -126,7 +216,7 @@ class Jetpack_Ai extends Product {
* @return string
*/
public static function get_description() {
return __( 'Experimental tool to add AI to your editor', 'jetpack-my-jetpack' );
return __( 'The most powerful AI tool for WordPress', 'jetpack-my-jetpack' );
}
/**
@ -201,10 +291,14 @@ class Jetpack_Ai extends Product {
/**
* Get the product pricing details by tier
*
* @param int $tier The usage tier.
* @param int|null $tier The usage tier.
* @return array Pricing details
*/
public static function get_pricing_for_ui_by_usage_tier( $tier ) {
if ( $tier === null ) {
return array();
}
$product = Wpcom_Products::get_product( static::get_wpcom_product_slug() );
if ( empty( $product ) ) {
@ -266,14 +360,34 @@ class Jetpack_Ai extends Product {
* @return array Pricing details
*/
public static function get_pricing_for_ui() {
$next_tier = self::get_next_usage_tier();
$next_tier = self::get_next_usage_tier();
$current_tier = self::get_current_usage_tier();
$current_call_to_action = $current_tier === 0
? __( 'Continue for free', 'jetpack-my-jetpack' )
: __( 'I\'m fine with my plan, thanks', 'jetpack-my-jetpack' );
$next_call_to_action = $next_tier === null
? __( 'Contact Us', 'jetpack-my-jetpack' )
: __( 'Upgrade', 'jetpack-my-jetpack' );
return array_merge(
array(
'available' => true,
'wpcom_product_slug' => static::get_wpcom_product_slug(),
return array(
'tiers' => array(
self::CURRENT_TIER_SLUG => array_merge(
self::get_pricing_for_ui_by_usage_tier( $current_tier ),
array(
'available' => true,
'is_free' => true,
'call_to_action' => $current_call_to_action,
)
),
self::UPGRADED_TIER_SLUG => array_merge(
self::get_pricing_for_ui_by_usage_tier( $next_tier ),
array(
'wpcom_product_slug' => static::get_wpcom_product_slug(),
'quantity' => $next_tier,
'call_to_action' => $next_call_to_action,
)
),
),
self::get_pricing_for_ui_by_usage_tier( $next_tier )
);
}
@ -305,12 +419,23 @@ class Jetpack_Ai extends Product {
}
/**
* Checks whether the current plan (or purchases) of the site already supports the product
* Checks whether the site has a paid plan for this product
*
* @return boolean
*/
public static function has_required_plan() {
return static::does_site_have_feature( 'ai-assistant' );
public static function has_paid_plan_for_product() {
$purchases_data = Wpcom_Products::get_site_current_purchases();
if ( is_wp_error( $purchases_data ) ) {
return false;
}
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
foreach ( $purchases_data as $purchase ) {
if ( str_contains( $purchase->product_slug, 'jetpack_ai' ) ) {
return true;
}
}
}
return false;
}
/**
@ -319,24 +444,60 @@ class Jetpack_Ai extends Product {
* @return boolean
*/
public static function is_upgradable() {
$has_required_plan = self::has_required_plan();
$current_tier = self::get_current_usage_tier();
$has_ai_feature = static::does_site_have_feature( 'ai-assistant' );
$current_tier = self::get_current_usage_tier();
// Mark as not upgradable if user is on unlimited tier or does not have any plan.
if ( ! $has_required_plan || null === $current_tier || 1 === $current_tier ) {
if ( ! $has_ai_feature || null === $current_tier || 1 === $current_tier ) {
return false;
}
return true;
}
/**
* Get the URL the user is taken after purchasing the product through the checkout
*
* @return ?string
*/
public static function get_post_checkout_url() {
return '/wp-admin/admin.php?page=my-jetpack#/jetpack-ai';
}
/**
* Get the URL the user is taken after activating the product through the checkout
*
* @return ?string
*/
public static function get_post_activation_url() {
return '/wp-admin/admin.php?page=my-jetpack#/jetpack-ai';
}
/**
* Get the URL where the user manages the product
*
* @return ?string
*/
public static function get_manage_url() {
return '';
return '/wp-admin/admin.php?page=my-jetpack#/add-jetpack-ai';
}
/**
* Checks whether the plugin is installed
*
* @return boolean
*/
public static function is_plugin_installed() {
return self::is_jetpack_plugin_installed();
}
/**
* Checks whether the plugin is active
*
* @return boolean
*/
public static function is_plugin_active() {
return (bool) static::is_jetpack_plugin_active();
}
/**
@ -383,4 +544,61 @@ class Jetpack_Ai extends Product {
private static function is_site_connected() {
return ( new Connection_Manager() )->is_connected();
}
/**
* Get the URL where the user manages the product
*
* NOTE: this method is the only thing that resembles an initialization for the product.
*
* @return void
*/
public static function extend_plugin_action_links() {
add_action( 'admin_enqueue_scripts', array( static::class, 'admin_enqueue_scripts' ) );
add_filter( 'default_content', array( static::class, 'add_ai_block' ), 10, 2 );
}
/**
* Enqueue the AI Assistant script
*
* The script is just a global variable used for the nonce, needed for the create post link.
*
* @return void
*/
public static function admin_enqueue_scripts() {
wp_register_script(
'my_jetpack_ai_app',
false,
array(),
Initializer::PACKAGE_VERSION,
array( 'in_footer' => true )
);
wp_localize_script(
'my_jetpack_ai_app',
'jetpackAi',
array(
'nonce' => wp_create_nonce( 'ai-assistant-content-nonce' ),
)
);
wp_enqueue_script( 'my_jetpack_ai_app' );
}
/**
* Add AI block to the post content
*
* Used only from the link on the product page, the filter will insert an AI Assistant block in the post content.
*
* @param string $content The post content.
* @param WP_Post $post The post object.
* @return string
*/
public static function add_ai_block( $content, WP_Post $post ) {
if ( isset( $_GET['use_ai_block'] ) && isset( $_GET['_wpnonce'] )
&& wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'ai-assistant-content-nonce' )
&& current_user_can( 'edit_post', $post->ID )
&& '' === $content
) {
return '<!-- wp:jetpack/ai-assistant /-->';
}
return $content;
}
}

View File

@ -9,6 +9,7 @@ namespace Automattic\Jetpack\My_Jetpack;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Modules;
use Automattic\Jetpack\Plugins_Installer;
use Jetpack_Options;
use WP_Error;
@ -25,6 +26,13 @@ abstract class Product {
*/
public static $slug = null;
/**
* The Jetpack module name, if any.
*
* @var ?string
*/
public static $module_name = null;
/**
* The filename (id) of the plugin associated with this product. Can be a string with a single value or a list of possible values
*
@ -77,6 +85,21 @@ abstract class Product {
*/
public static $has_standalone_plugin = false;
/**
* Whether this product has a free offering
*
* @var bool
*/
public static $has_free_offering = false;
/**
* Whether the product requires a plan to run
* The plan could be paid or free
*
* @var bool
*/
public static $requires_plan = false;
/**
* Get the plugin slug
*
@ -141,13 +164,15 @@ abstract class Product {
'pricing_for_ui' => static::get_pricing_for_ui(),
'is_bundle' => static::is_bundle_product(),
'is_plugin_active' => static::is_plugin_active(),
'is_upgradable' => static::is_upgradable(),
'is_upgradable_by_bundle' => static::is_upgradable_by_bundle(),
'supported_products' => static::get_supported_products(),
'wpcom_product_slug' => static::get_wpcom_product_slug(),
'requires_user_connection' => static::$requires_user_connection,
'has_required_plan' => static::has_required_plan(),
'has_any_plan_for_product' => static::has_any_plan_for_product(),
'has_free_plan_for_product' => static::has_free_plan_for_product(),
'has_paid_plan_for_product' => static::has_paid_plan_for_product(),
'has_required_tier' => static::has_required_tier(),
'has_free_offering' => static::$has_free_offering,
'manage_url' => static::get_manage_url(),
'purchase_url' => static::get_purchase_url(),
'post_activation_url' => static::get_post_activation_url(),
@ -332,19 +357,6 @@ abstract class Product {
);
}
/**
* Checks whether the current plan (or purchases) of the site already supports the product
*
* Returns true if it supports. Return false if a purchase is still required.
*
* Free products will always return true.
*
* @return boolean
*/
public static function has_required_plan() {
return true;
}
/**
* Checks whether the site has a paid plan for the product
* This ignores free products, it only checks if there is a purchase that supports the product
@ -352,19 +364,26 @@ abstract class Product {
* @return boolean
*/
public static function has_paid_plan_for_product() {
// TODO: this is not always the same.
// There should be checks on each individual product class for paid plans if the product has a free offering
// For products with no free offering, checking has_required_plan works fine
return static::has_required_plan();
return false;
}
/**
* Checks whether the current plan (or purchases) of the site already supports the tiers
* Checks whether the site has a free plan for the product
* Note, this should not return true if a product does not have a WPCOM plan (ex: search free, Akismet Free, stats free)
*
* @return array Key/value pairs of tier slugs and whether they are supported or not.
* @return false
*/
public static function has_required_tier() {
return array();
public static function has_free_plan_for_product() {
return false;
}
/**
* Checks whether the site has any WPCOM plan for a product (paid or free)
*
* @return bool
*/
public static function has_any_plan_for_product() {
return static::has_paid_plan_for_product() || static::has_free_plan_for_product();
}
/**
@ -434,25 +453,38 @@ abstract class Product {
$status = 'active';
// We only consider missing site & user connection an error when the Product is active.
if ( static::$requires_site_connection && ! ( new Connection_Manager() )->is_connected() ) {
$status = 'error';
} elseif ( static::$requires_user_connection && ! ( new Connection_Manager() )->has_connected_owner() ) {
$status = 'error';
} elseif ( static::is_upgradable() ) {
// Upgradable plans should ignore whether or not they have the required plan.
$status = 'can_upgrade';
} elseif ( ! static::has_required_plan() ) { // We need needs_purchase here as well because some products we consider active without the required plan.
if ( static::has_trial_support() ) {
$status = 'needs_purchase_or_free';
// Site has never been connected before
if ( ! \Jetpack_Options::get_option( 'id' ) ) {
$status = 'needs_first_site_connection';
} else {
$status = 'needs_purchase';
$status = 'site_connection_error';
}
} elseif ( static::$requires_user_connection && ! ( new Connection_Manager() )->has_connected_owner() ) {
$status = 'user_connection_error';
} elseif ( static::is_upgradable() ) {
$status = 'can_upgrade';
}
} elseif ( ! static::has_required_plan() ) {
if ( static::has_trial_support() ) {
$status = 'needs_purchase_or_free';
} else {
$status = 'needs_purchase';
// Check specifically for inactive modules, which will prevent a product from being active
} elseif ( static::$module_name && ! static::is_module_active() ) {
$status = 'module_disabled';
// If there is not a plan associated with the disabled module, encourage a plan first
// Getting a plan set up should help resolve any connection issues
// However if the standalone plugin for this product is active, then we will defer to showing errors that prevent the module from being active
// This is because if a standalone plugin is installed, we expect the product to not show as "inactive" on My Jetpack
if ( static::$requires_plan || ( ! static::has_any_plan_for_product() && static::$has_standalone_plugin && ! self::is_plugin_active() ) ) {
$status = static::$has_free_offering ? 'needs_purchase_or_free' : 'needs_purchase';
} elseif ( static::$requires_site_connection && ! ( new Connection_Manager() )->is_connected() ) {
// Site has never been connected before
if ( ! \Jetpack_Options::get_option( 'id' ) ) {
$status = 'needs_first_site_connection';
} else {
$status = 'site_connection_error';
}
} elseif ( static::$requires_user_connection && ! ( new Connection_Manager() )->has_connected_owner() ) {
$status = 'user_connection_error';
}
} elseif ( ! static::has_any_plan_for_product() ) {
$status = static::$has_free_offering ? 'needs_purchase_or_free' : 'needs_purchase';
} else {
$status = 'inactive';
}
@ -465,7 +497,7 @@ abstract class Product {
* @return boolean
*/
public static function is_active() {
return static::is_plugin_active() && static::has_required_plan();
return static::is_plugin_active() && ( static::has_any_plan_for_product() || ( ! static::$requires_plan && static::$has_free_offering ) );
}
/**
@ -504,6 +536,18 @@ abstract class Product {
return Plugins_Installer::is_plugin_active( static::get_installed_plugin_filename( 'jetpack' ) );
}
/**
* Checks whether the Jetpack module is active only if a module_name is defined
*
* @return bool
*/
public static function is_module_active() {
if ( static::$module_name ) {
return ( new Modules() )->is_active( static::$module_name );
}
return true;
}
/**
* Activates the plugin
*

View File

@ -54,6 +54,13 @@ class Protect extends Product {
*/
public static $requires_user_connection = false;
/**
* Whether this product has a free offering
*
* @var bool
*/
public static $has_free_offering = true;
/**
* Get the product name
*

View File

@ -47,6 +47,20 @@ class Search extends Hybrid_Product {
*/
public static $has_standalone_plugin = true;
/**
* Whether this product has a free offering
*
* @var bool
*/
public static $has_free_offering = true;
/**
* Whether this product requires a plan to work at all
*
* @var bool
*/
public static $requires_plan = true;
/**
* The filename (id) of the plugin associated with this product.
*
@ -63,7 +77,7 @@ class Search extends Hybrid_Product {
*
* @var boolean
*/
public static $requires_user_connection = false;
public static $requires_user_connection = true;
/**
* Get the product name
@ -286,17 +300,46 @@ class Search extends Hybrid_Product {
}
/**
* Checks whether the current plan of the site already supports the product
* Checks if the site purchases contain a paid search plan
*
* Returns true if it supports. Return false if a purchase is still required.
*
* Free products will always return true.
*
* @return boolean
* @return bool
*/
public static function has_required_plan() {
$search_state = static::get_state_from_wpcom();
return ! empty( $search_state->supports_search ) || ! empty( $search_state->supports_instant_search );
public static function has_paid_plan_for_product() {
$purchases_data = Wpcom_Products::get_site_current_purchases();
if ( is_wp_error( $purchases_data ) ) {
return false;
}
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
foreach ( $purchases_data as $purchase ) {
// Search is available as standalone product and as part of the Complete plan.
if (
( str_contains( $purchase->product_slug, 'jetpack_search' ) && ! str_contains( $purchase->product_slug, 'jetpack_search_free' ) ) ||
str_starts_with( $purchase->product_slug, 'jetpack_complete' ) ) {
return true;
}
}
}
return false;
}
/**
* Checks if the site purchases contain a free search plan
*
* @return bool
*/
public static function has_free_plan_for_product() {
$purchases_data = Wpcom_Products::get_site_current_purchases();
if ( is_wp_error( $purchases_data ) ) {
return false;
}
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
foreach ( $purchases_data as $purchase ) {
if ( str_contains( $purchase->product_slug, 'jetpack_search_free' ) ) {
return true;
}
}
}
return false;
}
/**

View File

@ -55,6 +55,13 @@ class Social extends Hybrid_Product {
'jetpack-social-dev/jetpack-social.php',
);
/**
* Whether this product has a free offering
*
* @var bool
*/
public static $has_free_offering = true;
/**
* Get the product name
*
@ -142,7 +149,7 @@ class Social extends Hybrid_Product {
*
* @return boolean
*/
public static function has_required_plan() {
public static function has_paid_plan_for_product() {
// For atomic sites, do a feature check to see if the republicize feature is available
// This feature is available by default on all Jetpack sites
if ( ( new Host() )->is_woa_site() ) {

View File

@ -59,6 +59,13 @@ class Stats extends Module_Product {
*/
public static $has_standalone_plugin = false;
/**
* Whether this product has a free offering
*
* @var bool
*/
public static $has_free_offering = true;
/**
* Get the product name
*

View File

@ -61,6 +61,13 @@ class Videopress extends Hybrid_Product {
*/
public static $has_standalone_plugin = true;
/**
* Whether this product has a free offering
*
* @var bool
*/
public static $has_free_offering = true;
/**
* Get the product name
*
@ -167,11 +174,22 @@ class Videopress extends Hybrid_Product {
}
/**
* Checks whether the current plan (or purchases) of the site already supports the product
* Checks whether the site has a paid plan for this product
*
* @return boolean
*/
public static function has_required_plan() {
return static::does_site_have_feature( 'videopress' );
public static function has_paid_plan_for_product() {
$purchases_data = Wpcom_Products::get_site_current_purchases();
if ( is_wp_error( $purchases_data ) ) {
return false;
}
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
foreach ( $purchases_data as $purchase ) {
if ( str_contains( $purchase->product_slug, 'jetpack_videopress' ) || str_starts_with( $purchase->product_slug, 'jetpack_complete' ) ) {
return true;
}
}
}
return false;
}
}