updated plugin Jetpack Protect version 2.1.0

This commit is contained in:
2024-04-19 10:49:36 +00:00
committed by Gitium
parent 620280b550
commit 7841fd5dc6
179 changed files with 6360 additions and 1476 deletions

View File

@ -5,6 +5,155 @@ 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.17.0] - 2024-03-14
### Changed
- Rewrite My Jetpack hooks to Typescript [#36288]
## [4.16.0] - 2024-03-12
### Added
- Add a red bubble notification that shows if the site is disconnected [#36263]
- Add README to data directory [#36301]
- Add whitelist to show errors only for certain queries [#36261]
### Changed
- Migrate Stats counts out of redux and into tanstack queries [#36195]
- Replace window state calls with util function [#36271]
- Rewrite My Jetpack utils to typescript [#36296]
- Show small stats card in table if large stats card isn't showing [#36136]
- Updated package dependencies. [#36325]
- Update query hooks for my-jetpack data" [#36257]
- Update useMyJetpackConnection hook to TypeScript [#36300]
### Removed
- Removing redux store [#36256]
## [4.15.0] - 2024-03-07
### Added
- Refactor My Jetpack's backup related redux state to react query. [#35982]
### Changed
- Migrate dismiss welcome banner to tanstack [#36199]
- Migrate global notices to context [#36201]
- Migrate My Jetpack's licenses query and state to react-query [#36029]
- Moved lifecycle stats function out of redux [#36205]
- Updating purchase related redux data to tanstack/react [#35994]
### Removed
- Connected Product offer is not being used, removing it to clean up a bit [#36203]
### Fixed
- fix a few My Jetpack bugs when main plugin is not installed [#36139]
- Intersitital tables were not visibly loading when pressed [#36236]
## [4.14.0] - 2024-03-04
### Changed
- Migrate My Jetpack zendesk state to react-query [#36028]
- Updated package dependencies.
### Fixed
- Add registration nonce to connect screen in My Jetpack [#36133]
## [4.13.0] - 2024-03-01
### Added
- Add site lifecycle status guess to My Jetpack [#35815]
### Changed
- Changed product plan checks on My Jetpack cards [#36046]
- Display different Boost card tooltip content based on score letter grade. [#35863]
- Improve consistency and fix bugs in product start and checkout flows [#35908]
- Instanciate the Boost Score API (new Speed_Score()) in My Jetpack. [#36080]
- My Jetpack: add Tracks events to connection section [#35804]
- Refactor react-query to reduce code repetition [#35990]
### Fixed
- fixed the purchase query for the boost card [#36004]
## [4.12.1] - 2024-02-27
### Added
- My Jetpack: Add an info popover in the Boost product card. [#35731]
## [4.12.0] - 2024-02-26
### Changed
- My Jetpack: decouple Jetpack AI insterstitial component [#35836]
- Remove translation of product names [#35830]
- Updating purchases state to use data query instead of redux [#35697]
### Removed
- Remove kebab menu on My Jetpack cards [#35829]
## [4.11.0] - 2024-02-22
### Added
- Adding accesible text for external links on connection page and footer [#35733]
### Changed
- change status and action of My Jetpack cards when plugin is missing [#35718]
- ESlint: disabled redundant role rule [#35800]
- My Jetpack: add product slugs to click events on interstitials [#35740]
- My Jetpack: let tier data pass on quantity data to checkout process for proper checkout URL crafting [#35817]
- Updated package dependencies. [#35793]
### Fixed
- Backup Card: made stats readable by screen readers [#35786]
- Connection Screen: make VoiceOver announce lists as such [#35736]
- Do not initialize My Jetpack when in Offline mode. [#35807]
- Fix wrong prop type passed to ConnectedProductCard [#35789]
## [4.10.0] - 2024-02-19
### Added
- Add Boost Speed Score into My Jetpack Boost product card [#35606]
- Add connection indicator for screen readers [#35714]
### Fixed
- Improved accessibility of Dismiss button in Connection Banner [#35694]
- My Jeptack Connection: Make footer logos a list for better screen readers interpretation. [#35667]
- My Jetpack: add label for screen readers to connect page close button [#35712]
## [4.9.2] - 2024-02-13
### Changed
- My Jetpack: various improvements to the Stats card. [#35355]
- Updated package dependencies. [#35608]
## [4.9.1] - 2024-02-12
### Added
- Add My Jetpack link to standalone plugins missing it [#35523]
## [4.9.0] - 2024-02-07
### Changed
- Add pricing info for AI and CRM on My Jetpack [#35457]
- Update the description of some cards to better describe the product on My Jetpack page [#35428]
### Fixed
- Fixes issue on My Jetpack interstitials where some prices are 1 cent off [#35492]
## [4.8.0] - 2024-02-05
### Added
- Add tracking info to the Jetpack Manage Banner CTA [#35378]
- My Jetpack: support redirect_to parameter on the product interstitial. [#35263]
### Changed
- Update CTA copy on the connection banner to make it clear which type of connection we are going to request [#35401]
- Updated package dependencies.
- Update product cards on My Jetpack to always display the status indidicator. [#35377]
### Fixed
- Fix issue where most products are not installing their standalone product upon purchase [#35399]
## [4.7.0] - 2024-01-29
### Changed
- Update the UpsellBanner to use the Card component from WP components. [#35223]
### Removed
- UpsellBanner component moved to js-packages/components [#35228]
## [4.6.2] - 2024-01-22
### Added
- My Jetpack: add contact us event for Jetpack AI [#35136]
## [4.6.1] - 2024-01-22
### Changed
- Display Jetpack Protect product card for all users. [#35142]
- Ensure that interstitial tables go straight to checkout just like insterstitial cards [#35049]
## [4.6.0] - 2024-01-18
### Added
- Add hosting provider check. [#34864]
@ -1198,6 +1347,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Created package
[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
[4.14.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.13.0...4.14.0
[4.13.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.12.1...4.13.0
[4.12.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.12.0...4.12.1
[4.12.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.11.0...4.12.0
[4.11.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.10.0...4.11.0
[4.10.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.9.2...4.10.0
[4.9.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.9.1...4.9.2
[4.9.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.9.0...4.9.1
[4.9.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.8.0...4.9.0
[4.8.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.7.0...4.8.0
[4.7.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.6.2...4.7.0
[4.6.2]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.6.1...4.6.2
[4.6.1]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.6.0...4.6.1
[4.6.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.5.0...4.6.0
[4.5.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.4.0...4.5.0
[4.4.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.3.0...4.4.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' => '8b33ddffd4b0e82ce03c');
<?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');

View File

@ -12,6 +12,16 @@
http://jedwatson.github.io/classnames
*/
/**
* @license React
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @remix-run/router v1.2.1
*

View File

@ -5,21 +5,23 @@
"license": "GPL-2.0-or-later",
"require": {
"php": ">=7.0",
"automattic/jetpack-admin-ui": "^0.3.1",
"automattic/jetpack-assets": "^2.0.4",
"automattic/jetpack-connection": "^2.2.0",
"automattic/jetpack-jitm": "^3.0.2",
"automattic/jetpack-licensing": "^2.0.1",
"automattic/jetpack-plugins-installer": "^0.3.1",
"automattic/jetpack-redirect": "^2.0.0",
"automattic/jetpack-constants": "^2.0.0",
"automattic/jetpack-plans": "^0.4.1"
"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"
},
"require-dev": {
"yoast/phpunit-polyfills": "1.1.0",
"automattic/jetpack-changelogger": "^4.0.5",
"automattic/jetpack-changelogger": "^4.1.1",
"automattic/wordbless": "@dev",
"automattic/jetpack-videopress": "^0.22.2"
"automattic/jetpack-videopress": "^0.23.10"
},
"suggest": {
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
@ -67,7 +69,7 @@
"link-template": "https://github.com/Automattic/jetpack-my-jetpack/compare/${old}...${new}"
},
"branch-alias": {
"dev-trunk": "4.6.x-dev"
"dev-trunk": "4.17.x-dev"
},
"version-constants": {
"::PACKAGE_VERSION": "src/class-initializer.php"

View File

@ -0,0 +1,245 @@
interface Window {
myJetpackInitialState?: {
siteSuffix: string;
latestBoostSpeedScores: {
scores: {
desktop: number;
mobile: number;
};
theme: string;
timestamp: number;
};
IDCContainerID: string;
adminUrl: string;
blogID: string;
fileSystemWriteAccess: 'yes' | 'no';
isStatsModuleActive: string;
isUserFromKnownHost: string;
jetpackManage: {
isAgencyAccount: boolean;
isEnabled: boolean;
};
loadAddLicenseScreen: string;
myJetpackCheckoutUri: string;
myJetpackFlags: {
showFullJetpackStatsCard: boolean;
videoPressStats: boolean;
};
lifecycleStats: {
isSiteConnected: boolean;
isUserConnected: boolean;
jetpackPlugins: Array< string >;
modules: Array< string >;
purchases: Array< string >;
};
myJetpackUrl: string;
myJetpackVersion: string;
plugins: {
[ key: string ]: {
Name: string;
PluginURI: string;
Version: string;
Title: string;
Description: string;
Author: string;
AuthorName: string;
AuthorURI: string;
DomainPath: string;
textDomain: string;
RequiresPHP: string;
RequiresWP: string;
UpdateURI: string;
Network: boolean;
active: boolean;
};
};
products: {
items: {
[ key: string ]: {
class: string;
description: string;
disclaimers: Array< string[] >;
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[];
long_description: string;
manage_url: string;
name: string;
plugin_slug: string;
post_activation_url: string;
post_checkout_url?: string;
pricing_for_ui?: {
available: boolean;
wpcom_product_slug: string;
product_term: string;
currency_code: string;
full_price: number;
discount_price: number;
coupon_discount: number;
is_introductory_offer: boolean;
introductory_offer?: {
cost_per_interval: number;
interval_count: number;
interval_unit: string;
should_prorate_when_offer_ends: boolean;
transition_after_renewal_count: number;
usage_limit?: number;
};
};
purchase_url?: string;
requires_user_connection: boolean;
slug: string;
standalone_plugin_info: {
has_standalone_plugin: boolean;
is_standalone_installed: boolean;
is_standalone_active: boolean;
};
status: string;
supported_products: string[];
tiers: string[];
title: string;
wpcom_product_slug: string;
};
};
};
purchases: {
items: Array< {
ID: string;
user_id: string;
blog_id: string;
product_id: string;
subscribed_date: string;
renew: string;
auto_renew: string;
renew_date: string;
inactive_date: string | null;
active: string;
meta: string | object;
ownership_id: string;
most_recent_renew_date: string;
amount: number;
expiry_date: string;
expiry_message: string;
expiry_sub_message: string;
expiry_status: string;
partner_name: string | null;
partner_slug: string | null;
partner_key_id: string | null;
subscription_status: string;
product_name: string;
product_slug: string;
product_type: string;
blog_created_date: string;
blogname: string;
domain: string;
description: string;
attached_to_purchase_id: string | null;
included_domain: string;
included_domain_purchase_amount: number;
currency_code: string;
currency_symbol: string;
renewal_price_tier_slug: string | null;
renewal_price_tier_usage_quantity: number | null;
current_price_tier_slug: string | null;
current_price_tier_usage_quantity: number | null;
price_tier_list: Array< object >;
price_text: string;
bill_period_label: string;
bill_period_days: number;
regular_price_text: string;
regular_price_integer: number;
product_display_price: string;
price_integer: number;
is_cancelable: boolean;
can_explicit_renew: boolean;
can_disable_auto_renew: boolean;
can_reenable_auto_renewal: boolean;
iap_purchase_management_link: string | null;
is_iap_purchase: boolean;
is_locked: boolean;
is_refundable: boolean;
refund_period_in_days: number;
is_renewable: boolean;
is_renewal: boolean;
has_private_registration: boolean;
refund_amount: number;
refund_integer: number;
refund_currency_symbol: string;
refund_text: string;
refund_options: object | null;
total_refund_amount: number;
total_refund_integer: number;
total_refund_currency: string;
total_refund_text: string;
check_dns: boolean;
} >;
};
topJetpackMenuItemUrl: string;
userIsAdmin: string;
userIsNewToJetpack: string;
welcomeBanner: {
hasBeenDismissed: boolean;
};
};
JP_CONNECTION_INITIAL_STATE: {
apiRoot: string;
apiNonce: string;
registrationNonce: string;
connectionStatus: {
isActive: boolean;
isStaging: boolean;
isRegistered: boolean;
isUserConnected: boolean;
hasConnectedOwner: boolean;
offlineMode: {
isActive: boolean;
constant: boolean;
url: boolean;
filter: boolean;
wpLocalConstant: boolean;
};
isPublic: boolean;
};
userConnectionData: {
currentUser: {
isConnected: boolean;
isMaster: boolean;
username: string;
id: number;
blogId: number;
wpcomUser: {
avatar: boolean;
};
gravatar: string;
permissions: {
admin_page?: boolean;
connect: boolean;
connect_user: boolean;
disconnect: boolean;
edit_posts?: boolean;
manage_modules?: boolean;
manage_options?: boolean;
manage_plugins?: boolean;
network_admin?: boolean;
network_sites_page?: boolean;
publish_posts?: boolean;
view_stats?: boolean;
};
};
connectionOwner: null;
};
connectedPlugins: object;
wpVersion: string;
siteSuffix: string;
connectionErrors: Array< string | object >;
};
myJetpackRest?: {
apiRoot: string;
apiNonce: string;
};
}

View File

@ -9,6 +9,9 @@ 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;
use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
@ -34,7 +37,7 @@ class Initializer {
*
* @var string
*/
const PACKAGE_VERSION = '4.6.0';
const PACKAGE_VERSION = '4.17.0';
/**
* HTML container ID for the IDC screen on My Jetpack page.
@ -52,6 +55,17 @@ class Initializer {
'jetpack-search',
);
const MY_JETPACK_SITE_INFO_TRANSIENT_KEY = 'my-jetpack-site-info';
const MISSING_SITE_CONNECTION_NOTIFICATION_KEY = 'missing-site-connection';
/**
* Holds info/data about the site (from the /sites/%d endpoint)
*
* @var stdClass Object
*/
public static $site_info;
/**
* Initialize My Jetpack
*
@ -72,6 +86,10 @@ class Initializer {
Licensing::instance()->initialize();
}
// Initialize Boost Speed Score
$boost_modules = Jetpack_Boost_Modules::init();
new Speed_Score( $boost_modules, 'jetpack-my-jetpack' );
// Add custom WP REST API endoints.
add_action( 'rest_api_init', array( __CLASS__, 'register_rest_endpoints' ) );
@ -85,6 +103,8 @@ class Initializer {
);
add_action( 'load-' . $page_suffix, array( __CLASS__, 'admin_init' ) );
// This is later than the admin-ui package, which runs on 1000
add_action( 'admin_init', array( __CLASS__, 'maybe_show_red_bubble' ), 1001 );
// Sets up JITMS.
JITM::configure();
@ -142,6 +162,7 @@ class Initializer {
* @return void
*/
public static function admin_init() {
self::$site_info = self::get_site_info();
add_filter( 'identity_crisis_container_id', array( static::class, 'get_idc_container_id' ) );
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ) );
// Product statuses are constantly changing, so we never want to cache the page.
@ -177,40 +198,53 @@ class Initializer {
'textdomain' => 'jetpack-my-jetpack',
)
);
$modules = new Modules();
$modules = new Modules();
$connection = new Connection_Manager();
$speed_score_history = new Speed_Score_History( wp_parse_url( get_site_url(), PHP_URL_HOST ) );
wp_localize_script(
'my_jetpack_main_app',
'myJetpackInitialState',
array(
'products' => array(
'products' => array(
'items' => Products::get_products(),
),
'purchases' => array(
'purchases' => array(
'items' => array(),
),
'plugins' => Plugins_Installer::get_plugins(),
'myJetpackUrl' => admin_url( 'admin.php?page=my-jetpack' ),
'myJetpackCheckoutUri' => 'admin.php?page=my-jetpack',
'topJetpackMenuItemUrl' => Admin_Menu::get_top_level_menu_item_url(),
'siteSuffix' => ( new Status() )->get_site_suffix(),
'blogID' => Connection_Manager::get_site_id( true ),
'myJetpackVersion' => self::PACKAGE_VERSION,
'myJetpackFlags' => self::get_my_jetpack_flags(),
'fileSystemWriteAccess' => self::has_file_system_write_access(),
'loadAddLicenseScreen' => self::is_licensing_ui_enabled(),
'adminUrl' => esc_url( admin_url() ),
'IDCContainerID' => static::get_idc_container_id(),
'userIsAdmin' => current_user_can( 'manage_options' ),
'userIsNewToJetpack' => self::is_jetpack_user_new(),
'isStatsModuleActive' => $modules->is_active( 'stats' ),
'isUserFromKnownHost' => self::is_user_from_known_host(),
'welcomeBanner' => array(
'plugins' => Plugins_Installer::get_plugins(),
'myJetpackUrl' => admin_url( 'admin.php?page=my-jetpack' ),
'myJetpackCheckoutUri' => admin_url( 'admin.php?page=my-jetpack' ),
'topJetpackMenuItemUrl' => Admin_Menu::get_top_level_menu_item_url(),
'siteSuffix' => ( new Status() )->get_site_suffix(),
'blogID' => Connection_Manager::get_site_id( true ),
'myJetpackVersion' => self::PACKAGE_VERSION,
'myJetpackFlags' => self::get_my_jetpack_flags(),
'fileSystemWriteAccess' => self::has_file_system_write_access(),
'loadAddLicenseScreen' => self::is_licensing_ui_enabled(),
'adminUrl' => esc_url( admin_url() ),
'IDCContainerID' => static::get_idc_container_id(),
'userIsAdmin' => current_user_can( 'manage_options' ),
'userIsNewToJetpack' => self::is_jetpack_user_new(),
'lifecycleStats' => array(
'jetpackPlugins' => self::get_installed_jetpack_plugins(),
'isSiteConnected' => $connection->is_connected(),
'isUserConnected' => $connection->is_user_connected(),
'purchases' => self::get_purchases(),
'modules' => self::get_active_modules(),
),
'redBubbleAlerts' => self::get_red_bubble_alerts(),
'isStatsModuleActive' => $modules->is_active( 'stats' ),
'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(
'jetpackManage' => array(
'isEnabled' => Jetpack_Manage::could_use_jp_manage(),
'isAgencyAccount' => Jetpack_Manage::is_agency_account(),
),
'latestBoostSpeedScores' => $speed_score_history->latest(),
)
);
@ -232,6 +266,63 @@ class Initializer {
}
}
/**
* Get product slugs of the active purchases
*
* @return array
*/
public static function get_purchases() {
$purchases = Wpcom_Products::get_site_current_purchases();
if ( is_wp_error( $purchases ) ) {
return array();
}
return array_map(
function ( $purchase ) {
return $purchase->product_slug;
},
$purchases
);
}
/**
* Get installed Jetpack plugins
*
* @return array
*/
public static function get_installed_jetpack_plugins() {
$plugin_slugs = array_keys( Plugins_Installer::get_plugins() );
$plugin_slugs = array_map(
static function ( $slug ) {
$parts = explode( '/', $slug );
if ( empty( $parts ) ) {
return '';
}
// Return the last segment of the filepath without the PHP extension
return str_replace( '.php', '', $parts[ count( $parts ) - 1 ] );
},
$plugin_slugs
);
return array_values( array_intersect( self::JETPACK_PLUGIN_SLUGS, $plugin_slugs ) );
}
/**
* Get active modules (except ones enabled by default)
*
* @return array
*/
public static function get_active_modules() {
$modules = new Modules();
$active_modules = $modules->get_active();
// if the Jetpack plugin is active, filter out the modules that are active by default
if ( class_exists( 'Jetpack' ) && ! empty( $active_modules ) ) {
$active_modules = array_diff( $active_modules, Jetpack::get_default_modules() );
}
return $active_modules;
}
/**
* Determine if the current user is "new" to Jetpack
* This is used to vary some messaging in My Jetpack
@ -305,8 +396,8 @@ class Initializer {
*/
public static function get_my_jetpack_flags() {
$flags = array(
'videoPressStats' => Jetpack_Constants::is_true( 'JETPACK_MY_JETPACK_VIDEOPRESS_STATS_ENABLED' ),
'showJetpackStatsCard' => class_exists( 'Jetpack' ),
'videoPressStats' => Jetpack_Constants::is_true( 'JETPACK_MY_JETPACK_VIDEOPRESS_STATS_ENABLED' ),
'showFullJetpackStatsCard' => class_exists( 'Jetpack' ),
);
return $flags;
@ -376,6 +467,11 @@ class Initializer {
$should = false;
}
// All options presented in My Jetpack require a connection to WordPress.com.
if ( ( new Status() )->is_offline_mode() ) {
$should = false;
}
/**
* Allows filtering whether My Jetpack should be initialized.
*
@ -406,6 +502,56 @@ class Initializer {
return rest_ensure_response( $body, 200 );
}
/**
* Populates the self::$site_info var with site data from the /sites/%d endpoint
*
* @return Object|WP_Error
*/
public static function get_site_info() {
static $site_info = null;
if ( $site_info !== null ) {
return $site_info;
}
// Check for a cached value before doing lookup
$stored_site_info = get_transient( self::MY_JETPACK_SITE_INFO_TRANSIENT_KEY );
if ( $stored_site_info !== false ) {
return $stored_site_info;
}
$response = self::get_site();
if ( is_wp_error( $response ) ) {
return $response;
}
$site_info = $response->data;
set_transient( self::MY_JETPACK_SITE_INFO_TRANSIENT_KEY, $site_info, DAY_IN_SECONDS );
return $site_info;
}
/**
* Returns whether a site has been determined "commercial" or not.
*
* @return bool
*/
public static function is_commercial_site() {
if ( is_wp_error( self::$site_info ) ) {
return null;
}
return empty( self::$site_info->options->is_commercial ) ? false : self::$site_info->options->is_commercial;
}
/**
* Check if site is registered (has been connected before).
*
* @return bool
*/
public static function is_registered() {
return (bool) \Jetpack_Options::get_option( 'id' );
}
/**
* Dismiss the welcome banner.
*
@ -465,4 +611,59 @@ class Initializer {
public static function get_idc_container_id() {
return static::IDC_CONTAINER_ID;
}
/**
* Conditionally append the red bubble notification to the "Jetpack" menu item if there are alerts to show
*
* @return void
*/
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' ) );
$red_bubble_alerts = self::get_red_bubble_alerts();
// The Jetpack menu item should be on index 3
if (
! empty( $red_bubble_alerts ) &&
is_countable( $red_bubble_alerts ) &&
isset( $menu[3] ) &&
$menu[3][0] === 'Jetpack'
) {
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$menu[3][0] .= sprintf( ' <span class="awaiting-mod">%d</span>', count( $red_bubble_alerts ) );
}
}
/**
* Collect all possible alerts that we might use a red bubble notification for
*
* @return array
*/
public static function get_red_bubble_alerts() {
static $red_bubble_alerts = array();
// using a static cache since we call this function more than once in the class
if ( ! empty( $red_bubble_alerts ) ) {
return $red_bubble_alerts;
}
// go find the alerts
$red_bubble_alerts = apply_filters( 'my_jetpack_red_bubble_notification_slugs', $red_bubble_alerts );
return $red_bubble_alerts;
}
/**
* Add an alert slug if the site is missing a site connection
*
* @param array $red_bubble_slugs - slugs that describe the reasons the red bubble is showing.
* @return array
*/
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;
}
return $red_bubble_slugs;
}
}

View File

@ -174,8 +174,17 @@ class Products {
'backup',
'boost',
'crm',
'videopress', // we use videopress here to add the plugin action to the Jetpack plugin itself
'videopress',
'social',
'protect',
'crm',
'search',
);
// Add plugin action links for the core Jetpack plugin.
Product::extend_core_plugin_action_links();
// Add plugin action links to standalone products.
foreach ( $products as $product ) {
$class_name = self::get_product_class( $product );
$class_name::extend_plugin_action_links();

View File

@ -237,17 +237,6 @@ class REST_Products {
);
}
/**
* If the product is not hybrid, there is no need to deal with a standalone plugin.
*/
if ( ! is_subclass_of( $product['class'], Hybrid_Product::class ) ) {
return new \WP_Error(
'not_hybrid',
__( 'This product does not have a standalone plugin to install', 'jetpack-my-jetpack' ),
array( 'status' => 400 )
);
}
$install_product_result = call_user_func( array( $product['class'], 'install_and_activate_standalone' ) );
if ( is_wp_error( $install_product_result ) ) {
$install_product_result->add_data( array( 'status' => 400 ) );

View File

@ -44,21 +44,21 @@ class Anti_Spam extends Product {
public static $requires_user_connection = false;
/**
* Get the internationalized product name
* Get the product name
*
* @return string
*/
public static function get_name() {
return __( 'Akismet Anti-spam', 'jetpack-my-jetpack' );
return 'Akismet Anti-spam';
}
/**
* Get the internationalized product title
* Get the product title
*
* @return string
*/
public static function get_title() {
return __( 'Jetpack Akismet Anti-spam', 'jetpack-my-jetpack' );
return 'Jetpack Akismet Anti-spam';
}
/**

View File

@ -52,21 +52,21 @@ class Backup extends Hybrid_Product {
public static $has_standalone_plugin = true;
/**
* Get the internationalized product name
* Get the product name
*
* @return string
*/
public static function get_name() {
return __( 'VaultPress Backup', 'jetpack-my-jetpack' );
return 'VaultPress Backup';
}
/**
* Get the internationalized product title
* Get the product title
*
* @return string
*/
public static function get_title() {
return __( 'Jetpack VaultPress Backup', 'jetpack-my-jetpack' );
return 'Jetpack VaultPress Backup';
}
/**
@ -129,6 +129,13 @@ class Backup extends Hybrid_Product {
return 'jetpack_backup_t1_yearly';
}
/**
* Get the URL where the user should be redirected after checkout
*/
public static function get_post_checkout_url() {
return self::get_manage_url();
}
/**
* Get the product princing details
*
@ -209,10 +216,12 @@ class Backup extends Hybrid_Product {
* @return ?string
*/
public static function get_manage_url() {
if ( static::is_jetpack_plugin_active() ) {
return Redirect::get_url( 'my-jetpack-manage-backup' );
} elseif ( static::is_plugin_active() ) {
// check standalone first
if ( static::is_standalone_plugin_active() ) {
return admin_url( 'admin.php?page=jetpack-backup' );
// otherwise, check for the main Jetpack plugin
} elseif ( static::is_jetpack_plugin_active() ) {
return Redirect::get_url( 'my-jetpack-manage-backup' );
}
}
@ -224,15 +233,4 @@ class Backup extends Hybrid_Product {
public static function is_active() {
return parent::is_active() && static::has_required_plan();
}
/**
* Get the URL where the user should be redirected after checkout
*/
public static function get_post_checkout_url() {
if ( static::is_jetpack_plugin_active() ) {
return 'admin.php?page=jetpack#/recommendations';
} elseif ( static::is_plugin_active() ) {
return 'admin.php?page=jetpack-backup';
}
}
}

View File

@ -51,21 +51,21 @@ class Boost extends Product {
public static $requires_user_connection = false;
/**
* Get the internationalized product name
* Get the product name
*
* @return string
*/
public static function get_name() {
return __( 'Boost', 'jetpack-my-jetpack' );
return 'Boost';
}
/**
* Get the internationalized product title
* Get the product title
*
* @return string
*/
public static function get_title() {
return __( 'Jetpack Boost', 'jetpack-my-jetpack' );
return 'Jetpack Boost';
}
/**
@ -74,7 +74,7 @@ class Boost extends Product {
* @return string
*/
public static function get_description() {
return __( 'The easiest speed optimization plugin for WordPress', 'jetpack-my-jetpack' );
return __( 'Speed up your site in seconds', 'jetpack-my-jetpack' );
}
/**
@ -237,6 +237,15 @@ class Boost extends Product {
);
}
/**
* Get the URL the user is taken after purchasing the product through the checkout
*
* @return ?string
*/
public static function get_post_checkout_url() {
return self::get_manage_url();
}
/**
* Get the product princing details
*
@ -260,6 +269,27 @@ class Boost extends Product {
);
}
/**
* Checks whether the current plan (or purchases) of the site already supports the product
*
* @return boolean
*/
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 ) {
// Boost is available as standalone bundle and as part of the Complete plan.
if ( strpos( $purchase->product_slug, 'jetpack_boost' ) !== false || str_starts_with( $purchase->product_slug, 'jetpack_complete' ) ) {
return true;
}
}
}
return false;
}
/**
* Get the URL where the user manages the product
*

View File

@ -50,21 +50,21 @@ class Creator extends Product {
public static $requires_user_connection = false;
/**
* Get the internationalized product name
* Get the product name
*
* @return string
*/
public static function get_name() {
return __( 'Creator', 'jetpack-my-jetpack' );
return 'Creator';
}
/**
* Get the internationalized product title
* Get the product title
*
* @return string
*/
public static function get_title() {
return __( 'Jetpack Creator', 'jetpack-my-jetpack' );
return 'Jetpack Creator';
}
/**

View File

@ -8,6 +8,7 @@
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\My_Jetpack\Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
/**
* Class responsible for handling the CRM product
@ -26,7 +27,10 @@ class Crm extends Product {
*
* @var string
*/
public static $plugin_filename = 'zero-bs-crm/ZeroBSCRM.php';
public static $plugin_filename = array(
'zero-bs-crm/ZeroBSCRM.php',
'crm/ZeroBSCRM.php',
);
/**
* The slug of the plugin associated with this product. If not defined, it will default to the Jetpack plugin
@ -43,21 +47,21 @@ class Crm extends Product {
public static $requires_user_connection = false;
/**
* Get the internationalized product name
* Get the product name
*
* @return string
*/
public static function get_name() {
return __( 'CRM', 'jetpack-my-jetpack' );
return 'CRM';
}
/**
* Get the internationalized product title
* Get the product title
*
* @return string
*/
public static function get_title() {
return __( 'Jetpack CRM', 'jetpack-my-jetpack' );
return 'Jetpack CRM';
}
/**
@ -66,7 +70,7 @@ class Crm extends Product {
* @return string
*/
public static function get_description() {
return __( 'Connect with your people', 'jetpack-my-jetpack' );
return __( 'Nurture your contacts to grow your business', 'jetpack-my-jetpack' );
}
/**
@ -98,9 +102,17 @@ class Crm extends Product {
* @return array Pricing details
*/
public static function get_pricing_for_ui() {
// We are hard coding pricing info for CRM because it is not available to us through the CRM API.
return array(
'available' => true,
'is_free' => true,
'available' => true,
'is_free' => false,
'full_price' => 132,
'discount_price' => 132,
'is_introductory_offer' => false,
'product_term' => 'year',
'introductory_offer' => null,
// CRM is only sold in USD
'currency_code' => 'USD',
);
}
@ -121,4 +133,27 @@ class Crm extends Product {
public static function get_manage_url() {
return admin_url( 'admin.php?page=zerobscrm-dash' );
}
/**
* Checks whether the current plan (or purchases) of the site already supports the product
* CRM is available as part of Jetpack Complete
*
* @return boolean
*/
public static function has_required_plan() {
$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_starts_with( $purchase->product_slug, 'jetpack_complete' ) ) {
return true;
}
}
}
return false;
}
}

View File

@ -38,21 +38,21 @@ class Extras extends Product {
public static $requires_user_connection = false;
/**
* Get the internationalized product name
* Get the product name
*
* @return string
*/
public static function get_name() {
return __( 'Extras', 'jetpack-my-jetpack' );
return 'Extras';
}
/**
* Get the internationalized product title
* Get the product title
*
* @return string
*/
public static function get_title() {
return __( 'Jetpack Extras', 'jetpack-my-jetpack' );
return 'Jetpack Extras';
}
/**

View File

@ -15,10 +15,6 @@ use WP_Error;
* Class responsible for handling the hybrid products
*
* Hybrid products are those that may work both as a stand-alone plugin or with the Jetpack plugin.
*
* In case Jetpack plugin is active, it will not attempt to install its stand-alone plugin.
*
* But if Jetpack plugin is not active, then it will prompt to install and activate its stand-alone plugin.
*/
abstract class Hybrid_Product extends Product {
@ -29,6 +25,15 @@ abstract class Hybrid_Product extends Product {
*/
public static $has_standalone_plugin = true;
/**
* For Hybrid products, we can use either the standalone or Jetpack plugin
*
* @return bool
*/
public static function is_plugin_installed() {
return parent::is_plugin_installed() || parent::is_jetpack_plugin_installed();
}
/**
* Checks whether the Product is active
*
@ -47,15 +52,6 @@ abstract class Hybrid_Product extends Product {
return parent::is_plugin_active();
}
/**
* Checks whether the plugin is installed
*
* @return boolean
*/
public static function is_plugin_installed() {
return parent::is_plugin_installed() || static::is_jetpack_plugin_installed();
}
/**
* Checks whether the Jetpack module is active only if a module_name is defined
*
@ -122,12 +118,11 @@ abstract class Hybrid_Product extends Product {
}
}
if ( ! empty( static::$module_name ) ) {
if ( ! static::has_required_plan() ) {
// translators: %s is the product name. e.g. Jetpack Search.
return new WP_Error( 'not_supported', sprintf( __( 'Your plan does not support %s.', 'jetpack-my-jetpack' ), static::get_title() ) );
}
// 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 ) ) {
$module_activation = ( new Modules() )->activate( static::$module_name, false, false );
if ( ! $module_activation ) {
return new WP_Error( 'module_activation_failed', __( 'Error activating Jetpack module', 'jetpack-my-jetpack' ) );
}
@ -143,33 +138,8 @@ abstract class Hybrid_Product extends Product {
*
* @return boolean|WP_Error
*/
final public static function install_and_activate_standalone() {
/**
* Check for the presence of the standalone plugin, ignoring Jetpack presence.
*
* If the standalone plugin is not installed and the user can install plugins, proceed with the installation.
*/
if ( ! parent::is_plugin_installed() ) {
/**
* Check for permissions
*/
if ( ! current_user_can( 'install_plugins' ) ) {
return new WP_Error( 'not_allowed', __( 'You are not allowed to install plugins on this site.', 'jetpack-my-jetpack' ) );
}
/**
* Install the plugin
*/
$installed = Plugins_Installer::install_plugin( static::get_plugin_slug() );
if ( is_wp_error( $installed ) ) {
return $installed;
}
}
/**
* Activate the installed plugin
*/
$result = static::activate_plugin();
public static function install_and_activate_standalone() {
$result = parent::install_and_activate_standalone();
if ( is_wp_error( $result ) ) {
return $result;
@ -179,7 +149,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() ) {
if ( static::has_required_plan() && isset( static::$module_name ) ) {
$module_activation = ( new Modules() )->activate( static::$module_name, false, false );
if ( ! $module_activation ) {

View File

@ -1,6 +1,6 @@
<?php
/**
* Boost product
* AI product
*
* @package my-jetpack
*/
@ -58,21 +58,21 @@ class Jetpack_Ai extends Product {
}
/**
* Get the internationalized product name
* Get the product name
*
* @return string
*/
public static function get_name() {
return __( 'Jetpack AI', 'jetpack-my-jetpack' );
return 'AI';
}
/**
* Get the internationalized product title
* Get the product title
*
* @return string
*/
public static function get_title() {
return __( 'Jetpack AI', 'jetpack-my-jetpack' );
return 'Jetpack AI';
}
/**
@ -103,7 +103,7 @@ class Jetpack_Ai extends Product {
* @return int
*/
public static function get_next_usage_tier() {
if ( ! self::is_site_connected() ) {
if ( ! self::is_site_connected() || ! self::has_required_plan() ) {
return 100;
}
@ -205,12 +205,6 @@ class Jetpack_Ai extends Product {
* @return array Pricing details
*/
public static function get_pricing_for_ui_by_usage_tier( $tier ) {
// Bail early if the site is not connected.
if ( ! self::is_site_connected() ) {
return array();
}
$product = Wpcom_Products::get_product( static::get_wpcom_product_slug() );
if ( empty( $product ) ) {
@ -316,24 +310,7 @@ class Jetpack_Ai extends Product {
* @return boolean
*/
public static function has_required_plan() {
$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_starts_with( $purchase->product_slug, static::get_wpcom_product_slug() ) ) {
return true;
}
if ( str_starts_with( $purchase->product_slug, static::get_wpcom_monthly_product_slug() ) ) {
return true;
}
if ( str_starts_with( $purchase->product_slug, static::get_wpcom_bi_yearly_product_slug() ) ) {
return true;
}
}
}
return false;
return static::does_site_have_feature( 'ai-assistant' );
}
/**

View File

@ -86,7 +86,7 @@ abstract class Module_Product extends Product {
*/
public static function get_status() {
$status = parent::get_status();
if ( 'active' === $status && ! static::is_module_active() ) {
if ( 'inactive' === $status && ! static::is_module_active() ) {
$status = 'module_disabled';
}
return $status;

View File

@ -7,8 +7,10 @@
namespace Automattic\Jetpack\My_Jetpack;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Plugins_Installer;
use Jetpack_Options;
use WP_Error;
/**
@ -47,13 +49,20 @@ abstract class Product {
/**
* The Jetpack plugin filename
*
* @var string
* @var array
*/
const JETPACK_PLUGIN_FILENAME = array(
'jetpack/jetpack.php',
'jetpack-dev/jetpack.php',
);
/**
* Whether this product requires a site connection
*
* @var string
*/
public static $requires_site_connection = true;
/**
* Whether this product requires a user connection
*
@ -118,44 +127,91 @@ abstract class Product {
throw new \Exception( 'Product classes must declare the $slug attribute.' );
}
return array(
'slug' => static::$slug,
'plugin_slug' => static::$plugin_slug,
'name' => static::get_name(),
'title' => static::get_title(),
'description' => static::get_description(),
'long_description' => static::get_long_description(),
'tiers' => static::get_tiers(),
'features' => static::get_features(),
'features_by_tier' => static::get_features_by_tier(),
'disclaimers' => static::get_disclaimers(),
'status' => static::get_status(),
'pricing_for_ui' => static::get_pricing_for_ui(),
'is_bundle' => static::is_bundle_product(),
'is_plugin_active' => static::is_plugin_active(),
'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_required_tier' => static::has_required_tier(),
'manage_url' => static::get_manage_url(),
'purchase_url' => static::get_purchase_url(),
'post_activation_url' => static::get_post_activation_url(),
'standalone_plugin_info' => static::get_standalone_info(),
'class' => static::class,
'post_checkout_url' => static::get_post_checkout_url(),
'slug' => static::$slug,
'plugin_slug' => static::$plugin_slug,
'name' => static::get_name(),
'title' => static::get_title(),
'description' => static::get_description(),
'long_description' => static::get_long_description(),
'tiers' => static::get_tiers(),
'features' => static::get_features(),
'features_by_tier' => static::get_features_by_tier(),
'disclaimers' => static::get_disclaimers(),
'status' => static::get_status(),
'pricing_for_ui' => static::get_pricing_for_ui(),
'is_bundle' => static::is_bundle_product(),
'is_plugin_active' => static::is_plugin_active(),
'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_paid_plan_for_product' => static::has_paid_plan_for_product(),
'has_required_tier' => static::has_required_tier(),
'manage_url' => static::get_manage_url(),
'purchase_url' => static::get_purchase_url(),
'post_activation_url' => static::get_post_activation_url(),
'standalone_plugin_info' => static::get_standalone_info(),
'class' => static::class,
'post_checkout_url' => static::get_post_checkout_url(),
);
}
/**
* Get the internationalized product name
* Collect the site's active features
*
* @return WP_Error|array
*/
private static function get_site_features_from_wpcom() {
static $features = null;
if ( $features !== null ) {
return $features;
}
$site_id = Jetpack_Options::get_option( 'id' );
$response = Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d/features', $site_id ), '1.1' );
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
return new WP_Error( 'site_features_fetch_failed' );
}
$body = wp_remote_retrieve_body( $response );
$feature_return = json_decode( $body );
$features = $feature_return->active;
return $features;
}
/**
* Check to see if the site has a feature
* This will check the features provided by the site plans and products (including free ones)
*
* @param string $feature - the feature to check for.
* @return bool
*/
public static function does_site_have_feature( $feature ) {
if ( ! $feature ) {
return false;
}
$features = self::get_site_features_from_wpcom();
if ( is_wp_error( $features ) ) {
return false;
}
return in_array( $feature, $features, true );
}
/**
* Get the product name
*
* @return string
*/
abstract public static function get_name();
/**
* Get the internationalized product title
* Get the product title
*
* @return string
*/
@ -289,6 +345,19 @@ abstract class Product {
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
*
* @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();
}
/**
* Checks whether the current plan (or purchases) of the site already supports the tiers
*
@ -358,13 +427,15 @@ abstract class Product {
public static function get_status() {
if ( ! static::is_plugin_installed() ) {
$status = 'plugin_absent';
if ( static::has_required_plan() ) {
if ( static::has_paid_plan_for_product() ) {
$status = 'plugin_absent_with_plan';
}
} elseif ( static::is_active() ) {
$status = 'active';
// We only consider missing user connection an error when the Product is active.
if ( static::$requires_user_connection && ! ( new Connection_Manager() )->has_connected_owner() ) {
// 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.
@ -452,7 +523,8 @@ abstract class Product {
return true;
}
if ( ! static::is_plugin_installed() ) {
// Default to installing the standalone plugin for the product
if ( ! self::is_plugin_installed() ) {
$installed = Plugins_Installer::install_plugin( static::get_plugin_slug() );
if ( is_wp_error( $installed ) ) {
return $installed;
@ -537,15 +609,11 @@ abstract class Product {
}
/**
* Extend the plugin action links.
* Filter the action links for the plugins specified.
*
* @param string|string[] $filenames The plugin filename(s) to filter the action links for.
*/
public static function extend_plugin_action_links() {
$filenames = static::get_plugin_filename();
if ( ! is_array( $filenames ) ) {
$filenames = array( $filenames );
}
private static function filter_action_links( $filenames ) {
foreach ( $filenames as $filename ) {
$hook = 'plugin_action_links_' . $filename;
$callback = array( static::class, 'get_plugin_actions_links' );
@ -554,4 +622,65 @@ abstract class Product {
}
}
}
/**
* Extend the plugin action links.
*/
public static function extend_plugin_action_links() {
$filenames = static::get_plugin_filename();
if ( ! is_array( $filenames ) ) {
$filenames = array( $filenames );
}
self::filter_action_links( $filenames );
}
/**
* Extend the Jetpack plugin action links.
*/
public static function extend_core_plugin_action_links() {
$filenames = self::JETPACK_PLUGIN_FILENAME;
self::filter_action_links( $filenames );
}
/**
* Install and activate the standalone plugin in the case it's missing.
*
* @return boolean|WP_Error
*/
public static function install_and_activate_standalone() {
/**
* Check for the presence of the standalone plugin, ignoring Jetpack presence.
*
* If the standalone plugin is not installed and the user can install plugins, proceed with the installation.
*/
if ( ! static::is_plugin_installed() ) {
/**
* Check for permissions
*/
if ( ! current_user_can( 'install_plugins' ) ) {
return new WP_Error( 'not_allowed', __( 'You are not allowed to install plugins on this site.', 'jetpack-my-jetpack' ) );
}
/**
* Install the plugin
*/
$installed = Plugins_Installer::install_plugin( static::get_plugin_slug() );
if ( is_wp_error( $installed ) ) {
return $installed;
}
}
/**
* Activate the installed plugin
*/
$result = static::activate_plugin();
if ( is_wp_error( $result ) ) {
return $result;
}
return true;
}
}

View File

@ -7,8 +7,11 @@
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\My_Jetpack\Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
use Jetpack_Options;
use WP_Error;
/**
* Class responsible for handling the Protect product
@ -52,21 +55,21 @@ class Protect extends Product {
public static $requires_user_connection = false;
/**
* Get the internationalized product name
* Get the product name
*
* @return string
*/
public static function get_name() {
return __( 'Protect', 'jetpack-my-jetpack' );
return 'Protect';
}
/**
* Get the internationalized product title
* Get the product title
*
* @return string
*/
public static function get_title() {
return __( 'Jetpack Protect', 'jetpack-my-jetpack' );
return 'Jetpack Protect';
}
/**
@ -75,7 +78,7 @@ class Protect extends Product {
* @return string
*/
public static function get_description() {
return __( 'Stay one step ahead of threats', 'jetpack-my-jetpack' );
return __( 'Powerful, automated site security', 'jetpack-my-jetpack' );
}
/**
@ -101,6 +104,33 @@ class Protect extends Product {
);
}
/**
* Hits the wpcom api to check scan status.
*
* @todo Maybe add caching.
*
* @return Object|WP_Error
*/
private static function get_state_from_wpcom() {
static $status = null;
if ( $status !== null ) {
return $status;
}
$site_id = Jetpack_Options::get_option( 'id' );
$response = Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d/scan', $site_id ) . '?force=wpcom', '2', array( 'timeout' => 2 ), null, 'wpcom' );
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
return new WP_Error( 'scan_state_fetch_failed' );
}
$body = wp_remote_retrieve_body( $response );
$status = json_decode( $body );
return $status;
}
/**
* Get the product's available tiers
*
@ -217,6 +247,28 @@ class Protect extends Product {
);
}
/**
* Checks if the site has a paid plan for the product
*
* @return bool
*/
public static function has_paid_plan_for_product() {
$scan_data = static::get_state_from_wpcom();
if ( is_wp_error( $scan_data ) ) {
return false;
}
return is_object( $scan_data ) && isset( $scan_data->state ) && 'unavailable' !== $scan_data->state;
}
/**
* Get the URL the user is taken after purchasing the product through the checkout
*
* @return ?string
*/
public static function get_post_checkout_url() {
return self::get_manage_url();
}
/**
* Get the URL where the user manages the product
*

View File

@ -34,21 +34,21 @@ class Scan extends Module_Product {
public static $module_name = 'scan';
/**
* Get the internationalized product name
* Get the product name
*
* @return string
*/
public static function get_name() {
return __( 'Scan', 'jetpack-my-jetpack' );
return 'Scan';
}
/**
* Get the internationalized product title
* Get the product title
*
* @return string
*/
public static function get_title() {
return __( 'Jetpack Scan', 'jetpack-my-jetpack' );
return 'Jetpack Scan';
}
/**
@ -57,7 +57,7 @@ class Scan extends Module_Product {
* @return string
*/
public static function get_description() {
return __( 'Stay one step ahead of threats', 'jetpack-my-jetpack' );
return __( 'Powerful, automated site security', 'jetpack-my-jetpack' );
}
/**

View File

@ -66,21 +66,21 @@ class Search extends Hybrid_Product {
public static $requires_user_connection = false;
/**
* Get the internationalized product name
* Get the product name
*
* @return string
*/
public static function get_name() {
return __( 'Search', 'jetpack-my-jetpack' );
return 'Search';
}
/**
* Get the internationalized product title
* Get the product title
*
* @return string
*/
public static function get_title() {
return __( 'Jetpack Search', 'jetpack-my-jetpack' );
return 'Jetpack Search';
}
/**
@ -89,7 +89,7 @@ class Search extends Hybrid_Product {
* @return string
*/
public static function get_description() {
return __( 'Help them find what they need', 'jetpack-my-jetpack' );
return __( 'Custom instant site search', 'jetpack-my-jetpack' );
}
/**
@ -144,6 +144,15 @@ class Search extends Hybrid_Product {
return array_merge( $pricing, $search_pricing );
}
/**
* Get the URL the user is taken after purchasing the product through the checkout
*
* @return ?string
*/
public static function get_post_checkout_url() {
return self::get_manage_url();
}
/**
* Get the WPCOM product slug used to make the purchase
*

View File

@ -31,21 +31,21 @@ class Security extends Module_Product {
public static $module_name = 'security';
/**
* Get the internationalized product name
* Get the product name
*
* @return string
*/
public static function get_name() {
return _x( 'Security', 'Jetpack product name', 'jetpack-my-jetpack' );
return 'Security';
}
/**
* Get the internationalized product title
* Get the product title
*
* @return string
*/
public static function get_title() {
return _x( 'Security', 'Jetpack product name', 'jetpack-my-jetpack' );
return 'Jetpack Security';
}
/**

View File

@ -9,6 +9,7 @@ namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\My_Jetpack\Hybrid_Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
use Automattic\Jetpack\Status\Host;
/**
* Class responsible for handling the Social product
@ -55,21 +56,21 @@ class Social extends Hybrid_Product {
);
/**
* Get the internationalized product name
* Get the product name
*
* @return string
*/
public static function get_name() {
return __( 'Social', 'jetpack-my-jetpack' );
return 'Social';
}
/**
* Get the internationalized product title
* Get the product title
*
* @return string
*/
public static function get_title() {
return __( 'Jetpack Social', 'jetpack-my-jetpack' );
return 'Jetpack Social';
}
/**
@ -78,7 +79,7 @@ class Social extends Hybrid_Product {
* @return string
*/
public static function get_description() {
return __( 'Reach your audience on social media', 'jetpack-my-jetpack' );
return __( 'Auto-publish to social media', 'jetpack-my-jetpack' );
}
/**
@ -118,6 +119,15 @@ class Social extends Hybrid_Product {
);
}
/**
* Get the URL the user is taken after purchasing the product through the checkout
*
* @return ?string
*/
public static function get_post_checkout_url() {
return self::get_manage_url();
}
/**
* Get the WPCOM product slug used to make the purchase
*
@ -133,6 +143,12 @@ class Social extends Hybrid_Product {
* @return boolean
*/
public static function has_required_plan() {
// 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() ) {
return static::does_site_have_feature( 'republicize' );
}
$purchases_data = Wpcom_Products::get_site_current_purchases();
if ( is_wp_error( $purchases_data ) ) {
return false;

View File

@ -31,21 +31,21 @@ class Starter extends Module_Product {
public static $module_name = 'starter';
/**
* Get the internationalized product name
* Get the product name
*
* @return string
*/
public static function get_name() {
return _x( 'Starter', 'Jetpack product name', 'jetpack-my-jetpack' );
return 'Starter';
}
/**
* Get the internationalized product title
* Get the product title
*
* @return string
*/
public static function get_title() {
return _x( 'Jetpack Starter', 'Jetpack product name', 'jetpack-my-jetpack' );
return 'Jetpack Starter';
}
/**

View File

@ -7,8 +7,10 @@
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\My_Jetpack\Initializer;
use Automattic\Jetpack\My_Jetpack\Module_Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
use Automattic\Jetpack\Status\Host;
use Jetpack_Options;
/**
@ -44,21 +46,35 @@ class Stats extends Module_Product {
public static $plugin_filename = self::JETPACK_PLUGIN_FILENAME;
/**
* Get the internationalized product name
* Stats only requires site connection, not user connection
*
* @var bool
*/
public static $requires_user_connection = false;
/**
* Stats does not have a standalone plugin (yet?)
*
* @var bool
*/
public static $has_standalone_plugin = false;
/**
* Get the product name
*
* @return string
*/
public static function get_name() {
return __( 'Stats', 'jetpack-my-jetpack' );
return 'Stats';
}
/**
* Get the internationalized product title
* Get the product title
*
* @return string
*/
public static function get_title() {
return __( 'Jetpack Stats', 'jetpack-my-jetpack' );
return 'Jetpack Stats';
}
/**
@ -140,21 +156,72 @@ class Stats extends Module_Product {
}
/**
* Checks whether the site already supports this product through an existing plan or purchase
* Gets the 'status' of the Stats product
*
* @return string
*/
public static function get_status() {
$status = parent::get_status();
if ( 'module_disabled' === $status && ! Initializer::is_registered() ) {
// If the site has never been connected before, show the "Learn more" CTA,
// that points to the add Stats product interstitial.
$status = 'needs_purchase_or_free';
}
return $status;
}
/**
* Checks whether the product can be upgraded to a different product.
* Stats Commercial plan (highest tier) & Jetpack Complete are not upgradable.
* Also we don't push PWYW users to upgrade.
*
* @return boolean
*/
public static function has_required_plan() {
public static function is_upgradable() {
// For now, atomic sites with stats are not in a position to upgrade
if ( ( new Host() )->is_woa_site() ) {
return false;
}
$purchases_data = Wpcom_Products::get_site_current_purchases();
if ( ! is_wp_error( $purchases_data ) && is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
foreach ( $purchases_data as $purchase ) {
// Jetpack complete includes Stats commercial & cannot be upgraded
if ( str_starts_with( $purchase->product_slug, 'jetpack_complete' ) ) {
return false;
} elseif (
// Stats commercial purchased with highest tier cannot be upgraded.
in_array(
$purchase->product_slug,
array( 'jetpack_stats_yearly', 'jetpack_stats_monthly', 'jetpack_stats_bi_yearly' ),
true
) && $purchase->current_price_tier_slug === 'more_than_1m_views'
) {
return false;
} elseif (
// If user already has Stats PWYW, we won't push them to upgrade.
$purchase->product_slug === 'jetpack_stats_pwyw_yearly'
) {
return false;
}
}
}
return true;
}
/**
* Checks if the site has a paid plan that supports this product
*
* @return boolean
*/
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_starts_with( $purchase->product_slug, 'jetpack_stats' ) ) {
return true;
}
if ( str_starts_with( $purchase->product_slug, 'jetpack_complete' ) ) {
// Stats is available as standalone product and as part of the Complete plan.
if ( strpos( $purchase->product_slug, 'jetpack_stats' ) !== false || str_starts_with( $purchase->product_slug, 'jetpack_complete' ) ) {
return true;
}
}
@ -163,58 +230,19 @@ class Stats extends Module_Product {
}
/**
* Checks whether the product can be upgraded to a different product.
* Only Jetpack Stats Commercial plan is not upgradable.
*
* @return boolean
*/
public static function is_upgradable() {
$purchases_data = Wpcom_Products::get_site_current_purchases();
if ( is_wp_error( $purchases_data ) ) {
return false;
}
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
// For now, only the free and commercial tiered subs show as upgradable
$upgradeable_stats_purchases = array_filter(
$purchases_data,
static function ( $purchase ) {
// Free plan is upgradeable
if ( $purchase->product_slug === 'jetpack_stats_free_yearly' ) {
return true;
// Commercial plans are upgradeable if they have a tier
} elseif (
in_array(
$purchase->product_slug,
array( 'jetpack_stats_yearly', 'jetpack_stats_monthly', 'jetpack_stats_bi_yearly' ),
true
) &&
! empty( $purchase->current_price_tier_slug )
) {
return true;
}
return false;
}
);
return ! empty( $upgradeable_stats_purchases );
}
// If there are no plans found, don't consider the product as upgradeable
return false;
}
/**
* Returns a redirect parameter for an upgrade URL if current purchase license is a free license
* or an empty string otherwise.
* Returns a productType parameter for an upgrade URL, determining whether
* to show the PWYW upgrade interstitial or commercial upgrade interstitial.
*
* @return string
*/
public static function get_url_redirect_string() {
$purchases_data = Wpcom_Products::get_site_current_purchases();
public static function get_url_product_type() {
$purchases_data = Wpcom_Products::get_site_current_purchases();
$is_commercial_site = Initializer::is_commercial_site();
if ( is_wp_error( $purchases_data ) ) {
return '';
return $is_commercial_site ? '&productType=commercial' : '';
}
if ( $is_commercial_site ) {
return '&productType=commercial';
}
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
foreach ( $purchases_data as $purchase ) {
@ -258,7 +286,7 @@ class Stats extends Module_Product {
'%s#!/stats/purchase/%d?from=jetpack-my-jetpack%s&redirect_uri=%s',
admin_url( 'admin.php?page=stats' ),
Jetpack_Options::get_option( 'id' ),
static::get_url_redirect_string(),
static::get_url_product_type(),
rawurlencode( 'admin.php?page=stats' )
);
}

View File

@ -7,7 +7,6 @@
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\Current_Plan;
use Automattic\Jetpack\My_Jetpack\Hybrid_Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
@ -63,21 +62,21 @@ class Videopress extends Hybrid_Product {
public static $has_standalone_plugin = true;
/**
* Get the internationalized product name
* Get the product name
*
* @return string
*/
public static function get_name() {
return __( 'VideoPress', 'jetpack-my-jetpack' );
return 'VideoPress';
}
/**
* Get the internationalized product title
* Get the product title
*
* @return string
*/
public static function get_title() {
return __( 'Jetpack VideoPress', 'jetpack-my-jetpack' );
return 'Jetpack VideoPress';
}
/**
@ -127,6 +126,15 @@ class Videopress extends Hybrid_Product {
);
}
/**
* Get the URL the user is taken after purchasing the product through the checkout
*
* @return ?string
*/
public static function get_post_checkout_url() {
return self::get_manage_url();
}
/**
* Get the WPCOM product slug used to make the purchase
*
@ -164,7 +172,6 @@ class Videopress extends Hybrid_Product {
* @return boolean
*/
public static function has_required_plan() {
// using second argument `true` to force fetching from wpcom
return Current_Plan::supports( 'videopress-1tb-storage', true );
return static::does_site_have_feature( 'videopress' );
}
}