updated plugin Jetpack Protect
version 3.0.2
This commit is contained in:
@ -51,7 +51,7 @@ class Activitylog {
|
||||
'manage_options',
|
||||
esc_url( Redirect::get_url( 'cloud-activity-log-wp-menu', $args ) ),
|
||||
null,
|
||||
1
|
||||
8
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -16,14 +16,19 @@ use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
|
||||
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
|
||||
use Automattic\Jetpack\Connection\Rest_Authentication as Connection_Rest_Authentication;
|
||||
use Automattic\Jetpack\Constants as Jetpack_Constants;
|
||||
use Automattic\Jetpack\ExPlat;
|
||||
use Automattic\Jetpack\JITMS\JITM;
|
||||
use Automattic\Jetpack\Licensing;
|
||||
use Automattic\Jetpack\Modules;
|
||||
use Automattic\Jetpack\Plugins_Installer;
|
||||
use Automattic\Jetpack\Protect_Status\Status as Protect_Status;
|
||||
use Automattic\Jetpack\Status;
|
||||
use Automattic\Jetpack\Status\Host as Status_Host;
|
||||
use Automattic\Jetpack\Sync\Functions as Sync_Functions;
|
||||
use Automattic\Jetpack\Terms_Of_Service;
|
||||
use Automattic\Jetpack\Tracking;
|
||||
use Automattic\Jetpack\VideoPress\Stats as VideoPress_Stats;
|
||||
use Automattic\Jetpack\Waf\Waf_Runner;
|
||||
use Jetpack;
|
||||
use WP_Error;
|
||||
|
||||
@ -37,7 +42,7 @@ class Initializer {
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PACKAGE_VERSION = '4.24.1';
|
||||
const PACKAGE_VERSION = '4.35.3';
|
||||
|
||||
/**
|
||||
* HTML container ID for the IDC screen on My Jetpack page.
|
||||
@ -55,9 +60,11 @@ class Initializer {
|
||||
'jetpack-search',
|
||||
);
|
||||
|
||||
const MY_JETPACK_SITE_INFO_TRANSIENT_KEY = 'my-jetpack-site-info';
|
||||
|
||||
const MISSING_SITE_CONNECTION_NOTIFICATION_KEY = 'missing-site-connection';
|
||||
const MY_JETPACK_SITE_INFO_TRANSIENT_KEY = 'my-jetpack-site-info';
|
||||
const UPDATE_HISTORICALLY_ACTIVE_JETPACK_MODULES_KEY = 'update-historically-active-jetpack-modules';
|
||||
const MISSING_CONNECTION_NOTIFICATION_KEY = 'missing-connection';
|
||||
const VIDEOPRESS_STATS_KEY = 'my-jetpack-videopress-stats';
|
||||
const VIDEOPRESS_PERIOD_KEY = 'my-jetpack-videopress-period';
|
||||
|
||||
/**
|
||||
* Holds info/data about the site (from the /sites/%d endpoint)
|
||||
@ -102,9 +109,13 @@ class Initializer {
|
||||
);
|
||||
|
||||
add_action( 'load-' . $page_suffix, array( __CLASS__, 'admin_init' ) );
|
||||
add_action( 'admin_init', array( __CLASS__, 'setup_historically_active_jetpack_modules_sync' ) );
|
||||
// This is later than the admin-ui package, which runs on 1000
|
||||
add_action( 'admin_init', array( __CLASS__, 'maybe_show_red_bubble' ), 1001 );
|
||||
|
||||
// Set up the ExPlat package endpoints
|
||||
ExPlat::init();
|
||||
|
||||
// Sets up JITMS.
|
||||
JITM::configure();
|
||||
|
||||
@ -206,6 +217,13 @@ class Initializer {
|
||||
$previous_score = $speed_score_history->latest( 1 );
|
||||
}
|
||||
$latest_score['previousScores'] = $previous_score['scores'] ?? array();
|
||||
$scan_data = Protect_Status::get_status();
|
||||
self::update_historically_active_jetpack_modules();
|
||||
|
||||
$waf_config = array();
|
||||
if ( class_exists( 'Automattic\Jetpack\Waf\Waf_Runner' ) ) {
|
||||
$waf_config = Waf_Runner::get_config();
|
||||
}
|
||||
|
||||
wp_localize_script(
|
||||
'my_jetpack_main_app',
|
||||
@ -218,6 +236,7 @@ class Initializer {
|
||||
'items' => array(),
|
||||
),
|
||||
'plugins' => Plugins_Installer::get_plugins(),
|
||||
'themes' => Sync_Functions::get_themes(),
|
||||
'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(),
|
||||
@ -233,13 +252,21 @@ class Initializer {
|
||||
'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(),
|
||||
'jetpackPlugins' => self::get_installed_jetpack_plugins(),
|
||||
'historicallyActiveModules' => \Jetpack_Options::get_option( 'historically_active_modules', array() ),
|
||||
'ownedProducts' => Products::get_products_by_ownership( 'owned' ),
|
||||
'unownedProducts' => Products::get_products_by_ownership( 'unowned' ),
|
||||
'brokenModules' => self::check_for_broken_modules(),
|
||||
'isSiteConnected' => $connection->is_connected(),
|
||||
'isUserConnected' => $connection->is_user_connected(),
|
||||
'purchases' => self::get_purchases(),
|
||||
'modules' => self::get_active_modules(),
|
||||
),
|
||||
'redBubbleAlerts' => self::get_red_bubble_alerts(),
|
||||
'recommendedModules' => array(
|
||||
'modules' => self::get_recommended_modules(),
|
||||
'dismissed' => \Jetpack_Options::get_option( 'dismissed_recommendations', false ),
|
||||
),
|
||||
'isStatsModuleActive' => $modules->is_active( 'stats' ),
|
||||
'isUserFromKnownHost' => self::is_user_from_known_host(),
|
||||
'isCommercial' => self::is_commercial_site(),
|
||||
@ -249,6 +276,14 @@ class Initializer {
|
||||
'isAgencyAccount' => Jetpack_Manage::is_agency_account(),
|
||||
),
|
||||
'latestBoostSpeedScores' => $latest_score,
|
||||
'protect' => array(
|
||||
'scanData' => $scan_data,
|
||||
'wafConfig' => array_merge(
|
||||
$waf_config,
|
||||
array( 'blocked_logins' => (int) get_site_option( 'jetpack_protect_blocked_attempts', 0 ) )
|
||||
),
|
||||
),
|
||||
'videopress' => self::get_videopress_stats(),
|
||||
)
|
||||
);
|
||||
|
||||
@ -270,6 +305,67 @@ class Initializer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stats for VideoPress
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public static function get_videopress_stats() {
|
||||
$video_count = array_sum( (array) wp_count_attachments( 'video' ) );
|
||||
|
||||
if ( ! class_exists( 'Automattic\Jetpack\VideoPress\Stats' ) ) {
|
||||
return array(
|
||||
'videoCount' => $video_count,
|
||||
);
|
||||
}
|
||||
|
||||
$featured_stats = get_transient( self::VIDEOPRESS_STATS_KEY );
|
||||
|
||||
if ( $featured_stats ) {
|
||||
return array(
|
||||
'featuredStats' => $featured_stats,
|
||||
'videoCount' => $video_count,
|
||||
);
|
||||
}
|
||||
|
||||
$stats_period = get_transient( self::VIDEOPRESS_PERIOD_KEY );
|
||||
$videopress_stats = new VideoPress_Stats();
|
||||
|
||||
// If the stats period exists, retrieve that information without checking the view count.
|
||||
// If it does not, check the view count of monthly stats and determine if we want to show yearly or monthly stats.
|
||||
if ( $stats_period ) {
|
||||
if ( $stats_period === 'day' ) {
|
||||
$featured_stats = $videopress_stats->get_featured_stats( 60, 'day' );
|
||||
} else {
|
||||
$featured_stats = $videopress_stats->get_featured_stats( 2, 'year' );
|
||||
}
|
||||
} else {
|
||||
$featured_stats = $videopress_stats->get_featured_stats( 60, 'day' );
|
||||
|
||||
if (
|
||||
! is_wp_error( $featured_stats ) &&
|
||||
$featured_stats &&
|
||||
( $featured_stats['data']['views']['current'] < 500 || $featured_stats['data']['views']['previous'] < 500 )
|
||||
) {
|
||||
$featured_stats = $videopress_stats->get_featured_stats( 2, 'year' );
|
||||
}
|
||||
}
|
||||
|
||||
if ( is_wp_error( $featured_stats ) || ! $featured_stats ) {
|
||||
return array(
|
||||
'videoCount' => $video_count,
|
||||
);
|
||||
}
|
||||
|
||||
set_transient( self::VIDEOPRESS_PERIOD_KEY, $featured_stats['period'], WEEK_IN_SECONDS );
|
||||
set_transient( self::VIDEOPRESS_STATS_KEY, $featured_stats, DAY_IN_SECONDS );
|
||||
|
||||
return array(
|
||||
'featuredStats' => $featured_stats,
|
||||
'videoCount' => $video_count,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get product slugs of the active purchases
|
||||
*
|
||||
@ -285,7 +381,7 @@ class Initializer {
|
||||
function ( $purchase ) {
|
||||
return $purchase->product_slug;
|
||||
},
|
||||
$purchases
|
||||
(array) $purchases
|
||||
);
|
||||
}
|
||||
|
||||
@ -324,7 +420,7 @@ class Initializer {
|
||||
if ( class_exists( 'Jetpack' ) && ! empty( $active_modules ) ) {
|
||||
$active_modules = array_diff( $active_modules, Jetpack::get_default_modules() );
|
||||
}
|
||||
return $active_modules;
|
||||
return array_values( $active_modules );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -345,12 +441,7 @@ class Initializer {
|
||||
// TODO: add a data point for the last known connection/ disconnection time
|
||||
|
||||
// are any modules active?
|
||||
$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() );
|
||||
}
|
||||
$active_modules = self::get_active_modules();
|
||||
if ( ! empty( $active_modules ) ) {
|
||||
return false;
|
||||
}
|
||||
@ -427,6 +518,7 @@ class Initializer {
|
||||
new REST_Zendesk_Chat();
|
||||
new REST_Product_Data();
|
||||
new REST_AI();
|
||||
new REST_Recommendations_Evaluation();
|
||||
|
||||
register_rest_route(
|
||||
'my-jetpack/v1',
|
||||
@ -486,6 +578,85 @@ class Initializer {
|
||||
return apply_filters( 'jetpack_my_jetpack_should_initialize', $should );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set transient to queue an update to the historically active Jetpack modules on the next wp-admin load
|
||||
*
|
||||
* @param string $plugin The plugin that triggered the update. This will be present if the function was queued by a plugin activation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function queue_historically_active_jetpack_modules_update( $plugin = null ) {
|
||||
$plugin_filenames = Products::get_all_plugin_filenames();
|
||||
|
||||
if ( ! $plugin || in_array( $plugin, $plugin_filenames, true ) ) {
|
||||
set_transient( self::UPDATE_HISTORICALLY_ACTIVE_JETPACK_MODULES_KEY, true );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook into several connection-based actions to update the historically active Jetpack modules
|
||||
* If the transient that indicates the list needs to be synced, update it and delete the transient
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setup_historically_active_jetpack_modules_sync() {
|
||||
if ( get_transient( self::UPDATE_HISTORICALLY_ACTIVE_JETPACK_MODULES_KEY ) && ! wp_doing_ajax() ) {
|
||||
self::update_historically_active_jetpack_modules();
|
||||
delete_transient( self::UPDATE_HISTORICALLY_ACTIVE_JETPACK_MODULES_KEY );
|
||||
}
|
||||
|
||||
$actions = array(
|
||||
'jetpack_site_registered',
|
||||
'jetpack_user_authorized',
|
||||
'activated_plugin',
|
||||
);
|
||||
|
||||
foreach ( $actions as $action ) {
|
||||
add_action( $action, array( __CLASS__, 'queue_historically_active_jetpack_modules_update' ), 5 );
|
||||
}
|
||||
|
||||
// Modules are often updated async, so we need to update them right away as there will sometimes be no page reload.
|
||||
add_action( 'jetpack_activate_module', array( __CLASS__, 'update_historically_active_jetpack_modules' ), 5 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update historically active Jetpack plugins
|
||||
* Historically active is defined as the Jetpack plugins that are installed and active with the required connections
|
||||
* This array will consist of any plugins that were active at one point in time and are still enabled on the site
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function update_historically_active_jetpack_modules() {
|
||||
$historically_active_modules = \Jetpack_Options::get_option( 'historically_active_modules', array() );
|
||||
$products = Products::get_products();
|
||||
|
||||
foreach ( $products as $product ) {
|
||||
$status = $product['status'];
|
||||
$product_slug = $product['slug'];
|
||||
// We want to leave modules in the array if they've been active in the past
|
||||
// and were not manually disabled by the user.
|
||||
if ( in_array( $status, Products::$broken_module_statuses, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the module is active and not already in the array, add it
|
||||
if (
|
||||
in_array( $status, Products::$active_module_statuses, true ) &&
|
||||
! in_array( $product_slug, $historically_active_modules, true )
|
||||
) {
|
||||
$historically_active_modules[] = $product_slug;
|
||||
}
|
||||
|
||||
// If the module has been disabled due to a manual user action,
|
||||
// or because of a missing plan error, remove it from the array
|
||||
if ( in_array( $status, Products::$disabled_module_statuses, true ) ) {
|
||||
$historically_active_modules = array_values( array_diff( $historically_active_modules, array( $product_slug ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
\Jetpack_Options::update_option( 'historically_active_modules', array_unique( $historically_active_modules ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Site full-data endpoint.
|
||||
*
|
||||
@ -503,7 +674,7 @@ class Initializer {
|
||||
return new WP_Error( 'site_data_fetch_failed', 'Site data fetch failed', array( 'status' => $response_code ) );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $body, 200 );
|
||||
return rest_ensure_response( $body );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -537,7 +708,7 @@ class Initializer {
|
||||
/**
|
||||
* Returns whether a site has been determined "commercial" or not.
|
||||
*
|
||||
* @return bool
|
||||
* @return bool|null
|
||||
*/
|
||||
public static function is_commercial_site() {
|
||||
if ( is_wp_error( self::$site_info ) ) {
|
||||
@ -563,13 +734,13 @@ class Initializer {
|
||||
*/
|
||||
public static function dismiss_welcome_banner() {
|
||||
\Jetpack_Options::update_option( 'dismissed_welcome_banner', true );
|
||||
return rest_ensure_response( array( 'success' => true ), 200 );
|
||||
return rest_ensure_response( array( 'success' => true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the site has file write access to the plugins folder, false otherwise.
|
||||
*
|
||||
* @return bool
|
||||
* @return string
|
||||
**/
|
||||
public static function has_file_system_write_access() {
|
||||
|
||||
@ -625,7 +796,13 @@ class Initializer {
|
||||
global $menu;
|
||||
// filters for the items in this file
|
||||
add_filter( 'my_jetpack_red_bubble_notification_slugs', array( __CLASS__, 'add_red_bubble_alerts' ) );
|
||||
$red_bubble_alerts = self::get_red_bubble_alerts();
|
||||
$red_bubble_alerts = array_filter(
|
||||
self::get_red_bubble_alerts(),
|
||||
function ( $alert ) {
|
||||
// We don't want to show silent alerts
|
||||
return empty( $alert['is_silent'] );
|
||||
}
|
||||
);
|
||||
|
||||
// The Jetpack menu item should be on index 3
|
||||
if (
|
||||
@ -657,6 +834,63 @@ class Initializer {
|
||||
return $red_bubble_alerts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of module names sorted by their recommendation score
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public static function get_recommended_modules() {
|
||||
$recommendations_evaluation = \Jetpack_Options::get_option( 'recommendations_evaluation', null );
|
||||
|
||||
if ( ! $recommendations_evaluation ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
arsort( $recommendations_evaluation ); // Sort by scores in descending order
|
||||
|
||||
return array_keys( $recommendations_evaluation ); // Get only module names
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for features broken by a disconnected user or site
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function check_for_broken_modules() {
|
||||
$connection = new Connection_Manager();
|
||||
$is_user_connected = $connection->is_user_connected() || $connection->has_connected_owner();
|
||||
$is_site_connected = $connection->is_connected();
|
||||
$broken_modules = array(
|
||||
'needs_site_connection' => array(),
|
||||
'needs_user_connection' => array(),
|
||||
);
|
||||
|
||||
if ( $is_user_connected && $is_site_connected ) {
|
||||
return $broken_modules;
|
||||
}
|
||||
|
||||
$products = Products::get_products_classes();
|
||||
$historically_active_modules = \Jetpack_Options::get_option( 'historically_active_modules', array() );
|
||||
|
||||
foreach ( $products as $product ) {
|
||||
if ( ! in_array( $product::$slug, $historically_active_modules, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $product::$requires_user_connection && ! $is_user_connected ) {
|
||||
if ( ! in_array( $product::$slug, $broken_modules['needs_user_connection'], true ) ) {
|
||||
$broken_modules['needs_user_connection'][] = $product::$slug;
|
||||
}
|
||||
} elseif ( ! $is_site_connected ) {
|
||||
if ( ! in_array( $product::$slug, $broken_modules['needs_site_connection'], true ) ) {
|
||||
$broken_modules['needs_site_connection'][] = $product::$slug;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $broken_modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add relevant red bubble notifications
|
||||
*
|
||||
@ -664,12 +898,18 @@ class Initializer {
|
||||
* @return array
|
||||
*/
|
||||
public static function add_red_bubble_alerts( array $red_bubble_slugs ) {
|
||||
if ( wp_doing_ajax() ) {
|
||||
return array();
|
||||
}
|
||||
$connection = new Connection_Manager();
|
||||
$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;
|
||||
$red_bubble_slugs['welcome-banner-active'] = array(
|
||||
'is_silent' => $connection->is_connected(), // we don't display the red bubble if the user is connected
|
||||
);
|
||||
return $red_bubble_slugs;
|
||||
} else {
|
||||
return self::alert_if_missing_site_connection( $red_bubble_slugs );
|
||||
return self::alert_if_missing_connection( $red_bubble_slugs );
|
||||
}
|
||||
}
|
||||
|
||||
@ -679,9 +919,40 @@ class Initializer {
|
||||
* @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 ] = null;
|
||||
public static function alert_if_missing_connection( array $red_bubble_slugs ) {
|
||||
$broken_modules = self::check_for_broken_modules();
|
||||
$connection = new Connection_Manager();
|
||||
|
||||
if ( ! empty( $broken_modules['needs_user_connection'] ) ) {
|
||||
$red_bubble_slugs[ self::MISSING_CONNECTION_NOTIFICATION_KEY ] = array(
|
||||
'type' => 'user',
|
||||
'is_error' => true,
|
||||
);
|
||||
return $red_bubble_slugs;
|
||||
}
|
||||
|
||||
if ( ! empty( $broken_modules['needs_site_connection'] ) ) {
|
||||
$red_bubble_slugs[ self::MISSING_CONNECTION_NOTIFICATION_KEY ] = array(
|
||||
'type' => 'site',
|
||||
'is_error' => true,
|
||||
);
|
||||
return $red_bubble_slugs;
|
||||
}
|
||||
|
||||
if ( ! $connection->is_user_connected() && ! $connection->has_connected_owner() ) {
|
||||
$red_bubble_slugs[ self::MISSING_CONNECTION_NOTIFICATION_KEY ] = array(
|
||||
'type' => 'user',
|
||||
'is_error' => false,
|
||||
);
|
||||
return $red_bubble_slugs;
|
||||
}
|
||||
|
||||
if ( ! $connection->is_connected() ) {
|
||||
$red_bubble_slugs[ self::MISSING_CONNECTION_NOTIFICATION_KEY ] = array(
|
||||
'type' => 'site',
|
||||
'is_error' => false,
|
||||
);
|
||||
return $red_bubble_slugs;
|
||||
}
|
||||
|
||||
return $red_bubble_slugs;
|
||||
|
@ -49,7 +49,7 @@ class Jetpack_Manage {
|
||||
'manage_options',
|
||||
esc_url( Redirect::get_url( 'cloud-manage-dashboard-wp-menu', $args ) ),
|
||||
null,
|
||||
100
|
||||
15
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,89 @@ namespace Automattic\Jetpack\My_Jetpack;
|
||||
* A class for everything related to product handling in My Jetpack
|
||||
*/
|
||||
class Products {
|
||||
/**
|
||||
* Constants for the status of a product on a site
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const STATUS_SITE_CONNECTION_ERROR = 'site_connection_error';
|
||||
const STATUS_USER_CONNECTION_ERROR = 'user_connection_error';
|
||||
const STATUS_ACTIVE = 'active';
|
||||
const STATUS_CAN_UPGRADE = 'can_upgrade';
|
||||
const STATUS_INACTIVE = 'inactive';
|
||||
const STATUS_MODULE_DISABLED = 'module_disabled';
|
||||
const STATUS_PLUGIN_ABSENT = 'plugin_absent';
|
||||
const STATUS_PLUGIN_ABSENT_WITH_PLAN = 'plugin_absent_with_plan';
|
||||
const STATUS_NEEDS_PLAN = 'needs_plan';
|
||||
const STATUS_NEEDS_ACTIVATION = 'needs_activation';
|
||||
const STATUS_NEEDS_FIRST_SITE_CONNECTION = 'needs_first_site_connection';
|
||||
|
||||
/**
|
||||
* List of statuses that display the module as disabled
|
||||
* This is defined as the statuses in which the user willingly has the module disabled whether it be by
|
||||
* default, uninstalling the plugin, disabling the module, or not renewing their plan.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $disabled_module_statuses = array(
|
||||
self::STATUS_INACTIVE,
|
||||
self::STATUS_MODULE_DISABLED,
|
||||
self::STATUS_PLUGIN_ABSENT,
|
||||
self::STATUS_PLUGIN_ABSENT_WITH_PLAN,
|
||||
self::STATUS_NEEDS_ACTIVATION,
|
||||
self::STATUS_NEEDS_FIRST_SITE_CONNECTION,
|
||||
);
|
||||
|
||||
/**
|
||||
* List of statuses that display the module as broken
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $broken_module_statuses = array(
|
||||
self::STATUS_SITE_CONNECTION_ERROR,
|
||||
self::STATUS_USER_CONNECTION_ERROR,
|
||||
);
|
||||
|
||||
/**
|
||||
* List of statuses that display the module as needing attention with a warning
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $warning_module_statuses = array(
|
||||
self::STATUS_SITE_CONNECTION_ERROR,
|
||||
self::STATUS_USER_CONNECTION_ERROR,
|
||||
self::STATUS_PLUGIN_ABSENT_WITH_PLAN,
|
||||
self::STATUS_NEEDS_PLAN,
|
||||
);
|
||||
|
||||
/**
|
||||
* List of statuses that display the module as active
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $active_module_statuses = array(
|
||||
self::STATUS_ACTIVE,
|
||||
self::STATUS_CAN_UPGRADE,
|
||||
);
|
||||
|
||||
/**
|
||||
* List of all statuses that a product can have
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $all_statuses = array(
|
||||
self::STATUS_SITE_CONNECTION_ERROR,
|
||||
self::STATUS_USER_CONNECTION_ERROR,
|
||||
self::STATUS_ACTIVE,
|
||||
self::STATUS_CAN_UPGRADE,
|
||||
self::STATUS_INACTIVE,
|
||||
self::STATUS_MODULE_DISABLED,
|
||||
self::STATUS_PLUGIN_ABSENT,
|
||||
self::STATUS_PLUGIN_ABSENT_WITH_PLAN,
|
||||
self::STATUS_NEEDS_PLAN,
|
||||
self::STATUS_NEEDS_ACTIVATION,
|
||||
self::STATUS_NEEDS_FIRST_SITE_CONNECTION,
|
||||
);
|
||||
|
||||
/**
|
||||
* Get the list of Products classes
|
||||
@ -80,6 +163,84 @@ class Products {
|
||||
return $products;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of products sorted by whether or not the user owns them
|
||||
* An owned product is defined as a product that is any of the following
|
||||
* - Active
|
||||
* - Has historically been active
|
||||
* - The user has a plan that includes the product
|
||||
* - The user has the standalone plugin for the product installed
|
||||
*
|
||||
* @param string $type The type of ownership to return ('owned' or 'unowned').
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_products_by_ownership( $type ) {
|
||||
$owned_active_products = array();
|
||||
$owned_warning_products = array();
|
||||
$owned_inactive_products = array();
|
||||
$unowned_products = array();
|
||||
|
||||
foreach ( self::get_products_classes() as $class ) {
|
||||
$product_slug = $class::$slug;
|
||||
$status = $class::get_status();
|
||||
|
||||
if ( $class::is_owned() ) {
|
||||
// This sorts the the products in the order of active -> warning -> inactive.
|
||||
// This enables the frontend to display them in that order.
|
||||
// This is not needed for unowned products as those will always have a status of 'inactive'
|
||||
if ( in_array( $status, self::$active_module_statuses, true ) ) {
|
||||
array_push( $owned_active_products, $product_slug );
|
||||
} elseif ( in_array( $status, self::$warning_module_statuses, true ) ) {
|
||||
array_push( $owned_warning_products, $product_slug );
|
||||
} else {
|
||||
array_push( $owned_inactive_products, $product_slug );
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
array_push( $unowned_products, $product_slug );
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'owned' => array_values(
|
||||
array_unique(
|
||||
array_merge(
|
||||
$owned_active_products,
|
||||
$owned_warning_products,
|
||||
$owned_inactive_products
|
||||
)
|
||||
)
|
||||
),
|
||||
'unowned' => array_values(
|
||||
array_unique( $unowned_products )
|
||||
),
|
||||
);
|
||||
|
||||
return $data[ $type ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all plugin filenames associated with the products.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_all_plugin_filenames() {
|
||||
$filenames = array();
|
||||
foreach ( self::get_products_classes() as $class ) {
|
||||
if ( ! isset( $class::$plugin_filename ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( is_array( $class::$plugin_filename ) ) {
|
||||
$filenames = array_merge( $filenames, $class::$plugin_filename );
|
||||
} else {
|
||||
$filenames[] = $class::$plugin_filename;
|
||||
}
|
||||
}
|
||||
return $filenames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get one product data by its slug
|
||||
*
|
||||
@ -156,7 +317,7 @@ class Products {
|
||||
'status' => array(
|
||||
'title' => 'The product status',
|
||||
'type' => 'string',
|
||||
'enum' => array( 'active', 'inactive', 'plugin_absent', 'needs_purchase', 'needs_purchase_or_free', 'needs_first_site_connection', 'user_connection_error', 'site_connection_error' ),
|
||||
'enum' => self::$all_statuses,
|
||||
),
|
||||
'class' => array(
|
||||
'title' => 'The product class handler',
|
||||
|
@ -107,14 +107,14 @@ class REST_Product_Data {
|
||||
}
|
||||
}
|
||||
|
||||
return rest_ensure_response( $undo_event, 200 );
|
||||
return rest_ensure_response( $undo_event );
|
||||
}
|
||||
|
||||
/**
|
||||
* This will collect a count of all the items that could be backed up
|
||||
* This is used to show what backup could be doing if it is not enabled
|
||||
*
|
||||
* @return array
|
||||
* @return WP_Error|\WP_REST_Response
|
||||
*/
|
||||
public static function count_things_that_can_be_backed_up() {
|
||||
$image_mime_type = 'image';
|
||||
@ -142,6 +142,6 @@ class REST_Product_Data {
|
||||
// Add all audio attachments together to get the total audio count
|
||||
$data['total_audio_count'] = array_sum( (array) wp_count_attachments( $audio_mime_type ) );
|
||||
|
||||
return rest_ensure_response( $data, 200 );
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,149 @@
|
||||
<?php
|
||||
/**
|
||||
* Sets up the Evaluation Recommendations REST API endpoints.
|
||||
*
|
||||
* @package automattic/my-jetpack
|
||||
*/
|
||||
|
||||
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 Evaluation Recommendations.
|
||||
*/
|
||||
class REST_Recommendations_Evaluation {
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
register_rest_route(
|
||||
'my-jetpack/v1',
|
||||
'/site/recommendations/evaluation/',
|
||||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::evaluate_site_recommendations',
|
||||
'permission_callback' => __CLASS__ . '::permissions_callback',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
'my-jetpack/v1',
|
||||
'/site/recommendations/evaluation/result/',
|
||||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::EDITABLE,
|
||||
'callback' => __CLASS__ . '::save_evaluation_recommendations',
|
||||
'permission_callback' => __CLASS__ . '::permissions_callback',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
'my-jetpack/v1',
|
||||
'/site/recommendations/evaluation/result/',
|
||||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::DELETABLE,
|
||||
'callback' => __CLASS__ . '::dismiss_evaluation_recommendations',
|
||||
'permission_callback' => __CLASS__ . '::permissions_callback',
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check user capability to access the endpoint.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public static function permissions_callback() {
|
||||
$connection = new Connection_Manager();
|
||||
$is_site_connected = $connection->is_connected();
|
||||
|
||||
if ( ! $is_site_connected ) {
|
||||
return new WP_Error(
|
||||
'not_connected',
|
||||
__( 'Your site is not connected to Jetpack.', 'jetpack-my-jetpack' ),
|
||||
array(
|
||||
'status' => 400,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return true; // We require site to be connected.
|
||||
}
|
||||
|
||||
/**
|
||||
* Recommendations Evaluation endpoint.
|
||||
*
|
||||
* @param \WP_REST_Request $request Query request.
|
||||
*
|
||||
* @return \WP_REST_Response|WP_Error of 3 product slugs (recommendations).
|
||||
*/
|
||||
public static function evaluate_site_recommendations( $request ) {
|
||||
$goals = $request->get_param( 'goals' );
|
||||
|
||||
if ( ! isset( $goals ) ) {
|
||||
return new WP_Error( 'missing_goals', 'Goals are required', array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
$site_id = \Jetpack_Options::get_option( 'id' );
|
||||
$wpcom_endpoint = sprintf( '/sites/%1$d/jetpack-recommendations/evaluation?goals=%2$s', $site_id, implode( ',', $goals ) );
|
||||
$response = Client::wpcom_json_api_request_as_blog( $wpcom_endpoint, '2', array(), null, 'wpcom' );
|
||||
$response_code = wp_remote_retrieve_response_code( $response );
|
||||
$body = json_decode( wp_remote_retrieve_body( $response ) );
|
||||
|
||||
if ( is_wp_error( $response ) || empty( $body ) || 200 !== $response_code ) {
|
||||
return new WP_Error( 'recommendations_evaluation_fetch_failed', 'Evaluation processing failed', array( 'status' => $response_code ? $response_code : 400 ) );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $body );
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint to save recommendations results.
|
||||
*
|
||||
* @param \WP_REST_Request $request Query request.
|
||||
*
|
||||
* @return \WP_REST_Response|WP_Error success response.
|
||||
*/
|
||||
public static function save_evaluation_recommendations( $request ) {
|
||||
$json = $request->get_json_params();
|
||||
|
||||
if ( ! isset( $json['recommendations'] ) ) {
|
||||
return new WP_Error( 'missing_recommendations', 'Recommendations are required', array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
\Jetpack_Options::update_option( 'recommendations_evaluation', $json['recommendations'] );
|
||||
\Jetpack_Options::delete_option( 'dismissed_recommendations' );
|
||||
|
||||
return rest_ensure_response( Initializer::get_recommended_modules() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint to dismiss the recommendation section
|
||||
*
|
||||
* @param \WP_REST_Request $request Query request.
|
||||
*
|
||||
* @return \WP_REST_Response|WP_Error success response.
|
||||
*/
|
||||
public static function dismiss_evaluation_recommendations( $request ) {
|
||||
$show_welcome_banner = $request->get_param( 'showWelcomeBanner' );
|
||||
|
||||
\Jetpack_Options::update_option( 'dismissed_recommendations', true );
|
||||
|
||||
if ( isset( $show_welcome_banner ) && $show_welcome_banner === 'true' ) {
|
||||
\Jetpack_Options::delete_option( 'dismissed_welcome_banner' );
|
||||
}
|
||||
|
||||
return rest_ensure_response( array() );
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@
|
||||
namespace Automattic\Jetpack\My_Jetpack;
|
||||
|
||||
use Automattic\Jetpack\Connection\Client;
|
||||
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
|
||||
use Automattic\Jetpack\Status\Visitor;
|
||||
use Jetpack_Options;
|
||||
use WP_Error;
|
||||
@ -30,25 +31,48 @@ class Wpcom_Products {
|
||||
*/
|
||||
const CACHE_META_NAME = 'my-jetpack-cache';
|
||||
|
||||
const CACHE_CHECK_HASH_NAME = 'my-jetpack-wpcom-product-check-hash';
|
||||
|
||||
const MY_JETPACK_PURCHASES_TRANSIENT_KEY = 'my-jetpack-purchases';
|
||||
|
||||
/**
|
||||
* Store the data on failed WPCOM requests.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $wpcom_request_failures = array();
|
||||
|
||||
/**
|
||||
* Fetches the list of products from WPCOM
|
||||
*
|
||||
* @return Object|WP_Error
|
||||
*/
|
||||
private static function get_products_from_wpcom() {
|
||||
$blog_id = \Jetpack_Options::get_option( 'id' );
|
||||
$ip = ( new Visitor() )->get_ip( true );
|
||||
$headers = array(
|
||||
$connection = new Connection_Manager();
|
||||
$blog_id = \Jetpack_Options::get_option( 'id' );
|
||||
$ip = ( new Visitor() )->get_ip( true );
|
||||
$headers = array(
|
||||
'X-Forwarded-For' => $ip,
|
||||
);
|
||||
|
||||
// If has a blog id, use connected endpoint.
|
||||
|
||||
if ( $blog_id ) {
|
||||
$request_label = 'get_products_from_wpcom_blog_' . $blog_id;
|
||||
$request_failure = static::get_request_failure( $request_label );
|
||||
if ( null !== $request_failure ) {
|
||||
return $request_failure;
|
||||
}
|
||||
|
||||
// If has a blog id, use connected endpoint.
|
||||
$endpoint = sprintf( '/sites/%d/products/?_locale=%s&type=jetpack', $blog_id, get_user_locale() );
|
||||
|
||||
// If available in the user data, set the user's currency as one of the params
|
||||
if ( $connection->is_user_connected() ) {
|
||||
$user_details = $connection->get_connected_user_data();
|
||||
if ( ! empty( $user_details['user_currency'] ) && $user_details['user_currency'] !== 'USD' ) {
|
||||
$endpoint .= sprintf( '¤cy=%s', $user_details['user_currency'] );
|
||||
}
|
||||
}
|
||||
|
||||
$wpcom_request = Client::wpcom_json_api_request_as_blog(
|
||||
$endpoint,
|
||||
'1.1',
|
||||
@ -58,6 +82,12 @@ class Wpcom_Products {
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$request_label = 'get_products_from_wpcom';
|
||||
$request_failure = static::get_request_failure( $request_label );
|
||||
if ( null !== $request_failure ) {
|
||||
return $request_failure;
|
||||
}
|
||||
|
||||
$endpoint = 'https://public-api.wordpress.com/rest/v1.1/products?locale=' . get_user_locale() . '&type=jetpack';
|
||||
|
||||
$wpcom_request = wp_remote_get(
|
||||
@ -73,14 +103,47 @@ class Wpcom_Products {
|
||||
if ( 200 === $response_code ) {
|
||||
return json_decode( wp_remote_retrieve_body( $wpcom_request ) );
|
||||
} else {
|
||||
return new WP_Error(
|
||||
$error = new WP_Error(
|
||||
'failed_to_fetch_wpcom_products',
|
||||
esc_html__( 'Unable to fetch the products list from WordPress.com', 'jetpack-my-jetpack' ),
|
||||
array( 'status' => $response_code )
|
||||
);
|
||||
static::set_request_failure( $request_label, $error );
|
||||
return $error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Super unintelligent hash string that can help us reset the cache after connection changes
|
||||
* This is important because the currency can change after a user connects depending on what is set in their profile
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function build_check_hash() {
|
||||
static $has_user_data_fetch_error = false;
|
||||
|
||||
$hash_string = 'check_hash_';
|
||||
$connection = new Connection_Manager();
|
||||
|
||||
if ( $connection->is_connected() ) {
|
||||
$hash_string .= 'site_connected_';
|
||||
}
|
||||
|
||||
if ( $connection->is_user_connected() ) {
|
||||
$hash_string .= 'user_connected';
|
||||
// Add the user's currency
|
||||
$user_details = $has_user_data_fetch_error ? false : $connection->get_connected_user_data();
|
||||
|
||||
if ( $user_details === false ) {
|
||||
$has_user_data_fetch_error = true;
|
||||
} elseif ( ! empty( $user_details['user_currency'] ) ) {
|
||||
$hash_string .= '_' . $user_details['user_currency'];
|
||||
}
|
||||
}
|
||||
|
||||
return md5( $hash_string );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the cache with new information retrieved from WPCOM
|
||||
*
|
||||
@ -92,6 +155,7 @@ class Wpcom_Products {
|
||||
*/
|
||||
private static function update_cache( $products_list ) {
|
||||
update_user_meta( get_current_user_id(), self::CACHE_DATE_META_NAME, time() );
|
||||
update_user_meta( get_current_user_id(), self::CACHE_CHECK_HASH_NAME, self::build_check_hash() );
|
||||
return update_user_meta( get_current_user_id(), self::CACHE_META_NAME, $products_list );
|
||||
}
|
||||
|
||||
@ -102,8 +166,15 @@ class Wpcom_Products {
|
||||
if ( empty( self::get_products_from_cache() ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// This allows the cache to reset after the site or user connects/ disconnects
|
||||
$check_hash = get_user_meta( get_current_user_id(), self::CACHE_CHECK_HASH_NAME, true );
|
||||
if ( $check_hash !== self::build_check_hash() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$cache_date = get_user_meta( get_current_user_id(), self::CACHE_DATE_META_NAME, true );
|
||||
return time() - (int) $cache_date > ( 7 * DAY_IN_SECONDS );
|
||||
return time() - (int) $cache_date > DAY_IN_SECONDS;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -250,6 +321,11 @@ class Wpcom_Products {
|
||||
return $stored_purchases;
|
||||
}
|
||||
|
||||
$request_failure = static::get_request_failure( 'get_site_current_purchases' );
|
||||
if ( null !== $request_failure ) {
|
||||
return $request_failure;
|
||||
}
|
||||
|
||||
$site_id = Jetpack_Options::get_option( 'id' );
|
||||
|
||||
$response = Client::wpcom_json_api_request_as_blog(
|
||||
@ -260,7 +336,9 @@ class Wpcom_Products {
|
||||
)
|
||||
);
|
||||
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
|
||||
return new WP_Error( 'purchases_state_fetch_failed' );
|
||||
$error = new WP_Error( 'purchases_state_fetch_failed' );
|
||||
static::set_request_failure( 'get_site_current_purchases', $error );
|
||||
return $error;
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
@ -270,4 +348,40 @@ class Wpcom_Products {
|
||||
|
||||
return $purchases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the request failures to retry the API requests.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function reset_request_failures() {
|
||||
static::$wpcom_request_failures = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the request failure to prevent repeated requests.
|
||||
*
|
||||
* @param string $request_label The request label.
|
||||
* @param WP_Error $error The error.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function set_request_failure( $request_label, WP_Error $error ) {
|
||||
static::$wpcom_request_failures[ $request_label ] = $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pre-saved request failure if exists.
|
||||
*
|
||||
* @param string $request_label The request label.
|
||||
*
|
||||
* @return null|WP_Error
|
||||
*/
|
||||
private static function get_request_failure( $request_label ) {
|
||||
if ( array_key_exists( $request_label, static::$wpcom_request_failures ) ) {
|
||||
return static::$wpcom_request_failures[ $request_label ];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,13 @@ class Anti_Spam extends Product {
|
||||
*/
|
||||
public static $has_free_offering = true;
|
||||
|
||||
/**
|
||||
* Akismet has a standalone plugin
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $has_standalone_plugin = true;
|
||||
|
||||
/**
|
||||
* Get the product name
|
||||
*
|
||||
@ -74,7 +81,7 @@ class Anti_Spam extends Product {
|
||||
* @return string
|
||||
*/
|
||||
public static function get_description() {
|
||||
return __( 'Stop comment and form spam', 'jetpack-my-jetpack' );
|
||||
return __( 'Keep your site free from spam and bots', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -106,6 +113,14 @@ class Anti_Spam extends Product {
|
||||
* @return bool - whether an API key was found
|
||||
*/
|
||||
public static function has_paid_plan_for_product() {
|
||||
$products_with_anti_spam = array(
|
||||
'jetpack_anti_spam',
|
||||
'jetpack_complete',
|
||||
'jetpack_security',
|
||||
'jetpack_personal',
|
||||
'jetpack_premium',
|
||||
'jetpack_business',
|
||||
);
|
||||
// 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 );
|
||||
@ -118,13 +133,10 @@ class Anti_Spam extends Product {
|
||||
|
||||
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
|
||||
foreach ( $purchases_data as $purchase ) {
|
||||
// Anti-spam is available as standalone bundle and as part of the Security and Complete plans.
|
||||
if (
|
||||
strpos( $purchase->product_slug, 'jetpack_anti_spam' ) !== false ||
|
||||
str_starts_with( $purchase->product_slug, 'jetpack_complete' ) ||
|
||||
str_starts_with( $purchase->product_slug, 'jetpack_security' )
|
||||
) {
|
||||
return true;
|
||||
foreach ( $products_with_anti_spam as $product ) {
|
||||
if ( strpos( $purchase->product_slug, $product ) !== false ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ class Backup extends Hybrid_Product {
|
||||
return __( 'Save every change', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
return __( 'Your site is not backed up', 'jetpack-my-jetpack' );
|
||||
return __( 'Secure your site with automatic backups and one-click restores', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -184,7 +184,8 @@ class Backup extends Hybrid_Product {
|
||||
$response = Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d/rewind', $site_id ) . '?force=wpcom', '2', array( 'timeout' => 2 ), null, 'wpcom' );
|
||||
|
||||
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
|
||||
return new WP_Error( 'rewind_state_fetch_failed' );
|
||||
$status = new WP_Error( 'rewind_state_fetch_failed' );
|
||||
return $status;
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
|
@ -44,6 +44,13 @@ class Boost extends Product {
|
||||
*/
|
||||
public static $plugin_slug = 'jetpack-boost';
|
||||
|
||||
/**
|
||||
* Boost has a standalone plugin
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $has_standalone_plugin = true;
|
||||
|
||||
/**
|
||||
* Whether this product requires a user connection
|
||||
*
|
||||
@ -82,7 +89,7 @@ class Boost extends Product {
|
||||
* @return string
|
||||
*/
|
||||
public static function get_description() {
|
||||
return __( 'Speed up your site in seconds', 'jetpack-my-jetpack' );
|
||||
return __( 'Speed up your site and improve SEO in seconds', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -91,7 +98,7 @@ class Boost extends Product {
|
||||
* @return string
|
||||
*/
|
||||
public static function get_long_description() {
|
||||
return __( 'Jetpack Boost gives your site the same performance advantages as the world’s leading websites, no developer required.', 'jetpack-my-jetpack' );
|
||||
return __( 'Fast sites get more page visits, more conversions, and better SEO rankings. Boost speeds up your site in seconds.', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -80,7 +80,7 @@ class Creator extends Product {
|
||||
* @return string
|
||||
*/
|
||||
public static function get_description() {
|
||||
return __( 'Create, grow, and monetize your audience', 'jetpack-my-jetpack' );
|
||||
return __( 'Get more subscribers and keep them engaged with our creator tools', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -89,7 +89,7 @@ class Creator extends Product {
|
||||
* @return string
|
||||
*/
|
||||
public static function get_long_description() {
|
||||
return __( 'Create, grow, and monetize your audience with powerful tools for creators.', 'jetpack-my-jetpack' );
|
||||
return __( 'Craft stunning content, boost your subscriber base, and monetize your audience with subscriptions.', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,6 +53,13 @@ class Crm extends Product {
|
||||
*/
|
||||
public static $has_free_offering = true;
|
||||
|
||||
/**
|
||||
* CRM has a standalone plugin
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $has_standalone_plugin = true;
|
||||
|
||||
/**
|
||||
* Get the product name
|
||||
*
|
||||
@ -77,7 +84,7 @@ class Crm extends Product {
|
||||
* @return string
|
||||
*/
|
||||
public static function get_description() {
|
||||
return __( 'Nurture your contacts to grow your business', 'jetpack-my-jetpack' );
|
||||
return __( 'Strengthen customer relationships and grow your business', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -86,7 +93,7 @@ class Crm extends Product {
|
||||
* @return string
|
||||
*/
|
||||
public static function get_long_description() {
|
||||
return __( 'All of your contacts in one place. Build better relationships with your customers and clients.', 'jetpack-my-jetpack' );
|
||||
return __( 'Build better relationships with your customers and grow your business.', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -81,7 +81,6 @@ class Extras extends Product {
|
||||
*/
|
||||
public static function get_features() {
|
||||
return array(
|
||||
__( 'Measure your impact with beautiful stats', 'jetpack-my-jetpack' ),
|
||||
__( 'Speed up your site with optimized images', 'jetpack-my-jetpack' ),
|
||||
__( 'Protect your site against bot attacks', 'jetpack-my-jetpack' ),
|
||||
__( 'Get notifications if your site goes offline', 'jetpack-my-jetpack' ),
|
||||
|
@ -93,6 +93,10 @@ class Jetpack_Ai extends Product {
|
||||
* @return string[] Slugs of the available tiers
|
||||
*/
|
||||
public static function get_tiers() {
|
||||
if ( ! self::are_tier_plans_enabled() ) {
|
||||
return parent::get_tiers();
|
||||
}
|
||||
|
||||
return array(
|
||||
self::UPGRADED_TIER_SLUG,
|
||||
self::CURRENT_TIER_SLUG,
|
||||
@ -105,6 +109,10 @@ class Jetpack_Ai extends Product {
|
||||
* @return array[] Protect features comparison
|
||||
*/
|
||||
public static function get_features_by_tier() {
|
||||
if ( ! self::are_tier_plans_enabled() ) {
|
||||
return parent::get_features_by_tier();
|
||||
}
|
||||
|
||||
$current_tier = self::get_current_usage_tier();
|
||||
$current_description = 0 === $current_tier
|
||||
? __( 'Up to 20 requests', 'jetpack-my-jetpack' )
|
||||
@ -194,13 +202,15 @@ class Jetpack_Ai extends Product {
|
||||
*/
|
||||
public static function get_next_usage_tier() {
|
||||
if ( ! self::is_site_connected() || ! self::has_paid_plan_for_product() ) {
|
||||
// without site connection we can't know if tiers are enabled or not,
|
||||
// hence we can't know if the next tier is 100 or 1 (unlimited).
|
||||
return 100;
|
||||
}
|
||||
|
||||
$info = self::get_ai_assistant_feature();
|
||||
|
||||
// Bail early if it's not possible to fetch the feature data.
|
||||
if ( is_wp_error( $info ) ) {
|
||||
// Bail early if it's not possible to fetch the feature data or if it's included in a plan.
|
||||
if ( is_wp_error( $info ) || empty( $info ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -216,7 +226,7 @@ class Jetpack_Ai extends Product {
|
||||
* @return string
|
||||
*/
|
||||
public static function get_description() {
|
||||
return __( 'The most powerful AI tool for WordPress', 'jetpack-my-jetpack' );
|
||||
return __( 'Enhance your writing and productivity with our AI suite', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -253,14 +263,21 @@ class Jetpack_Ai extends Product {
|
||||
* @return string
|
||||
*/
|
||||
public static function get_features_by_usage_tier( $tier ) {
|
||||
$is_tier_plan = $tier && intval( $tier ) > 1;
|
||||
|
||||
if ( $tier === 100 && ( ! self::is_site_connected() || ! self::has_paid_plan_for_product() ) ) {
|
||||
// in these cases, get_next_usage_tier() will return 100
|
||||
// 100 is fine as default when tiered plans are enabled, but not otherwise
|
||||
$is_tier_plan = false;
|
||||
}
|
||||
|
||||
$features = array(
|
||||
1 => array(
|
||||
__( 'Artificial intelligence chatbot', 'jetpack-my-jetpack' ),
|
||||
__( 'Generate text, tables, lists, and forms', 'jetpack-my-jetpack' ),
|
||||
__( 'Refine the tone and content to your liking', 'jetpack-my-jetpack' ),
|
||||
__( 'Get feedback about your post', 'jetpack-my-jetpack' ),
|
||||
__( 'Seamless WordPress editor integration', 'jetpack-my-jetpack' ),
|
||||
),
|
||||
__( 'Generate text, tables, lists, and forms', 'jetpack-my-jetpack' ),
|
||||
__( 'Easily refine content to your liking', 'jetpack-my-jetpack' ),
|
||||
__( 'Make your content easier to read', 'jetpack-my-jetpack' ),
|
||||
__( 'Generate images with one-click', 'jetpack-my-jetpack' ),
|
||||
__( 'Optimize your titles for better performance', 'jetpack-my-jetpack' ),
|
||||
__( 'Priority support', 'jetpack-my-jetpack' ),
|
||||
);
|
||||
|
||||
$tiered_features = array(
|
||||
@ -274,7 +291,7 @@ class Jetpack_Ai extends Product {
|
||||
sprintf( __( 'Up to %d requests per month', 'jetpack-my-jetpack' ), $tier ),
|
||||
);
|
||||
|
||||
return isset( $features[ $tier ] ) ? $features[ $tier ] : $tiered_features;
|
||||
return $is_tier_plan ? $tiered_features : $features;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -305,11 +322,7 @@ class Jetpack_Ai extends Product {
|
||||
return array();
|
||||
}
|
||||
|
||||
// get info about the feature.
|
||||
$info = self::get_ai_assistant_feature();
|
||||
|
||||
// flag to indicate if the tiers are enabled, case the info is available.
|
||||
$tier_plans_enabled = ( ! is_wp_error( $info ) && isset( $info['tier-plans-enabled'] ) ) ? boolval( $info['tier-plans-enabled'] ) : false;
|
||||
$tier_plans_enabled = self::are_tier_plans_enabled();
|
||||
|
||||
/*
|
||||
* when tiers are enabled and the price tier list is empty,
|
||||
@ -360,6 +373,18 @@ class Jetpack_Ai extends Product {
|
||||
* @return array Pricing details
|
||||
*/
|
||||
public static function get_pricing_for_ui() {
|
||||
// no tiers
|
||||
if ( ! self::are_tier_plans_enabled() ) {
|
||||
return array_merge(
|
||||
array(
|
||||
'available' => true,
|
||||
'wpcom_product_slug' => static::get_wpcom_product_slug(),
|
||||
),
|
||||
// hardcoding 1 as next tier if tiers are not enabled
|
||||
self::get_pricing_for_ui_by_usage_tier( 1 )
|
||||
);
|
||||
}
|
||||
|
||||
$next_tier = self::get_next_usage_tier();
|
||||
$current_tier = self::get_current_usage_tier();
|
||||
$current_call_to_action = $current_tier === 0
|
||||
@ -444,8 +469,21 @@ class Jetpack_Ai extends Product {
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_upgradable() {
|
||||
$has_ai_feature = static::does_site_have_feature( 'ai-assistant' );
|
||||
$current_tier = self::get_current_usage_tier();
|
||||
$has_ai_feature = static::does_site_have_feature( 'ai-assistant' );
|
||||
$tier_plans_enabled = self::are_tier_plans_enabled();
|
||||
$current_tier = self::get_current_usage_tier();
|
||||
|
||||
if ( $has_ai_feature && ! $tier_plans_enabled && $current_tier >= 1 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$next_tier = self::get_next_usage_tier();
|
||||
|
||||
// The check below is debatable, not having the feature should not flag as not upgradable.
|
||||
// If user is free (tier = 0), not unlimited (tier = 1) and has a next tier, then it's upgradable.
|
||||
if ( $current_tier !== null && $current_tier !== 1 && $next_tier ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Mark as not upgradable if user is on unlimited tier or does not have any plan.
|
||||
if ( ! $has_ai_feature || null === $current_tier || 1 === $current_tier ) {
|
||||
@ -461,7 +499,7 @@ class Jetpack_Ai extends Product {
|
||||
* @return ?string
|
||||
*/
|
||||
public static function get_post_checkout_url() {
|
||||
return '/wp-admin/admin.php?page=my-jetpack#/jetpack-ai';
|
||||
return 'admin.php?page=my-jetpack#/jetpack-ai';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -479,7 +517,7 @@ class Jetpack_Ai extends Product {
|
||||
* @return ?string
|
||||
*/
|
||||
public static function get_manage_url() {
|
||||
return '/wp-admin/admin.php?page=my-jetpack#/add-jetpack-ai';
|
||||
return '/wp-admin/admin.php?page=my-jetpack#/jetpack-ai';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -536,6 +574,25 @@ class Jetpack_Ai extends Product {
|
||||
return \Jetpack_AI_Helper::get_ai_assistance_feature();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the AI Assistant tiered plans status
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function are_tier_plans_enabled() {
|
||||
$info = self::get_ai_assistant_feature();
|
||||
if ( is_wp_error( $info ) ) {
|
||||
// this is another faulty default value, we'll assume disabled while
|
||||
// production is enabled
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! empty( $info ) && isset( $info['tier-plans-enabled'] ) ) {
|
||||
return boolval( $info['tier-plans-enabled'] );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the site is connected to WordPress.com.
|
||||
*
|
||||
|
@ -86,8 +86,8 @@ abstract class Module_Product extends Product {
|
||||
*/
|
||||
public static function get_status() {
|
||||
$status = parent::get_status();
|
||||
if ( 'inactive' === $status && ! static::is_module_active() ) {
|
||||
$status = 'module_disabled';
|
||||
if ( Products::STATUS_INACTIVE === $status && ! static::is_module_active() ) {
|
||||
$status = Products::STATUS_MODULE_DISABLED;
|
||||
}
|
||||
return $status;
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ abstract class Product {
|
||||
*
|
||||
* @var string|string[]
|
||||
*/
|
||||
protected static $plugin_filename = null;
|
||||
public static $plugin_filename = null;
|
||||
|
||||
/**
|
||||
* The slug of the plugin associated with this product. If not defined, it will default to the Jetpack plugin
|
||||
@ -150,35 +150,37 @@ 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' => 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_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_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(),
|
||||
'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' => 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_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_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(),
|
||||
'post_activation_urls_by_feature' => static::get_manage_urls_by_feature(),
|
||||
'standalone_plugin_info' => static::get_standalone_info(),
|
||||
'class' => static::class,
|
||||
'post_checkout_url' => static::get_post_checkout_url(),
|
||||
'post_checkout_urls_by_feature' => static::get_post_checkout_urls_by_feature(),
|
||||
);
|
||||
}
|
||||
|
||||
@ -198,7 +200,8 @@ abstract class Product {
|
||||
$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' );
|
||||
$features = new WP_Error( 'site_features_fetch_failed' );
|
||||
return $features;
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
@ -305,6 +308,15 @@ abstract class Product {
|
||||
*/
|
||||
abstract public static function get_manage_url();
|
||||
|
||||
/**
|
||||
* Get the URL where the user manages the product for each product feature
|
||||
*
|
||||
* @return ?array
|
||||
*/
|
||||
public static function get_manage_urls_by_feature() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL the user is taken after activating the product
|
||||
*
|
||||
@ -323,6 +335,15 @@ abstract class Product {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL the user is taken after purchasing the product through the checkout for each product feature
|
||||
*
|
||||
* @return ?array
|
||||
*/
|
||||
public static function get_post_checkout_urls_by_feature() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the WPCOM product slug used to make the purchase
|
||||
*
|
||||
@ -432,12 +453,36 @@ abstract class Product {
|
||||
* return all the products it contains.
|
||||
* Empty array by default.
|
||||
*
|
||||
* @return Array Product slugs
|
||||
* @return array Product slugs
|
||||
*/
|
||||
public static function get_supported_products() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the product is owned or not
|
||||
* An owned product is defined as a product that is any of the following
|
||||
* - Active
|
||||
* - Has historically been active
|
||||
* - The user has a plan that includes the product
|
||||
* - The user has the standalone plugin for the product installed
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_owned() {
|
||||
$historically_active_modules = Jetpack_Options::get_option( 'historically_active_modules', array() );
|
||||
$standalone_info = static::get_standalone_info();
|
||||
if ( ( static::is_active() && Jetpack_Options::get_option( 'id' ) ) ||
|
||||
$standalone_info['is_standalone_installed'] ||
|
||||
in_array( static::$slug, $historically_active_modules, true ) ||
|
||||
static::has_any_plan_for_product()
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
@ -445,48 +490,48 @@ abstract class Product {
|
||||
*/
|
||||
public static function get_status() {
|
||||
if ( ! static::is_plugin_installed() ) {
|
||||
$status = 'plugin_absent';
|
||||
$status = Products::STATUS_PLUGIN_ABSENT;
|
||||
if ( static::has_paid_plan_for_product() ) {
|
||||
$status = 'plugin_absent_with_plan';
|
||||
$status = Products::STATUS_PLUGIN_ABSENT_WITH_PLAN;
|
||||
}
|
||||
} elseif ( static::is_active() ) {
|
||||
$status = 'active';
|
||||
$status = Products::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() ) {
|
||||
// Site has never been connected before
|
||||
if ( ! \Jetpack_Options::get_option( 'id' ) ) {
|
||||
$status = 'needs_first_site_connection';
|
||||
if ( ! Jetpack_Options::get_option( 'id' ) && ! static::is_owned() ) {
|
||||
$status = Products::STATUS_NEEDS_FIRST_SITE_CONNECTION;
|
||||
} else {
|
||||
$status = 'site_connection_error';
|
||||
$status = Products::STATUS_SITE_CONNECTION_ERROR;
|
||||
}
|
||||
} elseif ( static::$requires_user_connection && ! ( new Connection_Manager() )->has_connected_owner() ) {
|
||||
$status = 'user_connection_error';
|
||||
$status = Products::STATUS_USER_CONNECTION_ERROR;
|
||||
} elseif ( static::is_upgradable() ) {
|
||||
$status = 'can_upgrade';
|
||||
$status = Products::STATUS_CAN_UPGRADE;
|
||||
}
|
||||
// Check specifically for inactive modules, which will prevent a product from being active
|
||||
} elseif ( static::$module_name && ! static::is_module_active() ) {
|
||||
$status = 'module_disabled';
|
||||
$status = Products::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';
|
||||
$status = static::is_owned() && static::$has_free_offering && ! static::$requires_plan ? Products::STATUS_NEEDS_ACTIVATION : Products::STATUS_NEEDS_PLAN;
|
||||
} 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';
|
||||
// Site has never been connected before and product is not owned
|
||||
if ( ! Jetpack_Options::get_option( 'id' ) && ! static::is_owned() ) {
|
||||
$status = Products::STATUS_NEEDS_FIRST_SITE_CONNECTION;
|
||||
} else {
|
||||
$status = 'site_connection_error';
|
||||
$status = Products::STATUS_SITE_CONNECTION_ERROR;
|
||||
}
|
||||
} elseif ( static::$requires_user_connection && ! ( new Connection_Manager() )->has_connected_owner() ) {
|
||||
$status = 'user_connection_error';
|
||||
$status = Products::STATUS_USER_CONNECTION_ERROR;
|
||||
}
|
||||
} elseif ( ! static::has_any_plan_for_product() ) {
|
||||
$status = static::$has_free_offering ? 'needs_purchase_or_free' : 'needs_purchase';
|
||||
$status = static::is_owned() && static::$has_free_offering && ! static::$requires_plan ? Products::STATUS_NEEDS_ACTIVATION : Products::STATUS_NEEDS_PLAN;
|
||||
} else {
|
||||
$status = 'inactive';
|
||||
$status = Products::STATUS_INACTIVE;
|
||||
}
|
||||
return $status;
|
||||
}
|
||||
|
@ -22,6 +22,9 @@ class Protect extends Product {
|
||||
const UPGRADED_TIER_SLUG = 'upgraded';
|
||||
const UPGRADED_TIER_PRODUCT_SLUG = 'jetpack_scan';
|
||||
|
||||
const SCAN_FEATURE_SLUG = 'scan';
|
||||
const FIREWALL_FEATURE_SLUG = 'firewall';
|
||||
|
||||
/**
|
||||
* The product slug
|
||||
*
|
||||
@ -61,6 +64,13 @@ class Protect extends Product {
|
||||
*/
|
||||
public static $has_free_offering = true;
|
||||
|
||||
/**
|
||||
* Protect has a standalone plugin
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $has_standalone_plugin = true;
|
||||
|
||||
/**
|
||||
* Get the product name
|
||||
*
|
||||
@ -85,7 +95,7 @@ class Protect extends Product {
|
||||
* @return string
|
||||
*/
|
||||
public static function get_description() {
|
||||
return __( 'Powerful, automated site security', 'jetpack-my-jetpack' );
|
||||
return __( 'Guard against malware and bad actors 24/7', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -94,7 +104,7 @@ class Protect extends Product {
|
||||
* @return string
|
||||
*/
|
||||
public static function get_long_description() {
|
||||
return __( 'Protect your site and scan for security vulnerabilities listed in our database.', 'jetpack-my-jetpack' );
|
||||
return __( 'Protect your site from bad actors and malware 24/7. Clean up security vulnerabilities with one click.', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -255,16 +265,42 @@ class Protect extends Product {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the site has a paid plan for the product
|
||||
* Checks whether the current plan (or purchases) of the site already supports the product
|
||||
*
|
||||
* @return bool
|
||||
* @return boolean
|
||||
*/
|
||||
public static function has_paid_plan_for_product() {
|
||||
$scan_data = static::get_state_from_wpcom();
|
||||
if ( is_wp_error( $scan_data ) ) {
|
||||
$plans_with_scan = array(
|
||||
'jetpack_scan',
|
||||
'jetpack_security',
|
||||
'jetpack_complete',
|
||||
'jetpack_premium',
|
||||
'jetpack_business',
|
||||
);
|
||||
|
||||
$purchases_data = Wpcom_Products::get_site_current_purchases();
|
||||
if ( is_wp_error( $purchases_data ) ) {
|
||||
return false;
|
||||
}
|
||||
return is_object( $scan_data ) && isset( $scan_data->state ) && 'unavailable' !== $scan_data->state;
|
||||
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
|
||||
foreach ( $purchases_data as $purchase ) {
|
||||
foreach ( $plans_with_scan as $plan ) {
|
||||
if ( strpos( $purchase->product_slug, $plan ) !== false ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the product can be upgraded - i.e. this shows the /#add-protect interstitial
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_upgradable() {
|
||||
return ! self::has_paid_plan_for_product();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -276,6 +312,18 @@ class Protect extends Product {
|
||||
return self::get_manage_url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL the user is taken after purchasing the product through the checkout for each product feature
|
||||
*
|
||||
* @return ?array
|
||||
*/
|
||||
public static function get_post_checkout_urls_by_feature() {
|
||||
return array(
|
||||
self::SCAN_FEATURE_SLUG => self::get_post_checkout_url(),
|
||||
self::FIREWALL_FEATURE_SLUG => admin_url( 'admin.php?page=jetpack-protect#/firewall' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL where the user manages the product
|
||||
*
|
||||
@ -285,6 +333,18 @@ class Protect extends Product {
|
||||
return admin_url( 'admin.php?page=jetpack-protect' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL where the user manages the product for each product feature
|
||||
*
|
||||
* @return ?array
|
||||
*/
|
||||
public static function get_manage_urls_by_feature() {
|
||||
return array(
|
||||
self::SCAN_FEATURE_SLUG => self::get_manage_url(),
|
||||
self::FIREWALL_FEATURE_SLUG => admin_url( 'admin.php?page=jetpack-protect#/firewall' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return product bundles list
|
||||
* that supports the product.
|
||||
@ -292,6 +352,6 @@ class Protect extends Product {
|
||||
* @return array Products bundle list.
|
||||
*/
|
||||
public static function is_upgradable_by_bundle() {
|
||||
return array( 'security' );
|
||||
return array( 'security', 'complete' );
|
||||
}
|
||||
}
|
||||
|
@ -126,7 +126,8 @@ class Scan extends Module_Product {
|
||||
$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' );
|
||||
$status = new WP_Error( 'scan_state_fetch_failed' );
|
||||
return $status;
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
|
@ -8,6 +8,7 @@
|
||||
namespace Automattic\Jetpack\My_Jetpack\Products;
|
||||
|
||||
use Automattic\Jetpack\Connection\Client;
|
||||
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\Jetpack\My_Jetpack\Hybrid_Product;
|
||||
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
|
||||
@ -103,7 +104,7 @@ class Search extends Hybrid_Product {
|
||||
* @return string
|
||||
*/
|
||||
public static function get_description() {
|
||||
return __( 'Custom instant site search', 'jetpack-my-jetpack' );
|
||||
return __( 'Help your visitors find what they are looking for with instant search results', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -204,7 +205,7 @@ class Search extends Hybrid_Product {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override status to `needs_purchase_or_free` when status is `needs_purchase`.
|
||||
* Override status to `needs_activation` when status is `needs_plan`.
|
||||
*/
|
||||
public static function get_status() {
|
||||
$status = parent::get_status();
|
||||
@ -222,22 +223,33 @@ class Search extends Hybrid_Product {
|
||||
*/
|
||||
public static function get_pricing_from_wpcom( $record_count ) {
|
||||
static $pricings = array();
|
||||
$connection = new Connection_Manager();
|
||||
$blog_id = \Jetpack_Options::get_option( 'id' );
|
||||
|
||||
if ( isset( $pricings[ $record_count ] ) ) {
|
||||
return $pricings[ $record_count ];
|
||||
}
|
||||
|
||||
if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
|
||||
// For simple sites fetch the response directly.
|
||||
// If the site is connected, request pricing with the blog token
|
||||
if ( $blog_id ) {
|
||||
$endpoint = sprintf( '/jetpack-search/pricing?record_count=%1$d&locale=%2$s', $record_count, get_user_locale() );
|
||||
|
||||
// If available in the user data, set the user's currency as one of the params
|
||||
if ( $connection->is_user_connected() ) {
|
||||
$user_details = $connection->get_connected_user_data();
|
||||
if ( ! empty( $user_details['user_currency'] ) && $user_details['user_currency'] !== 'USD' ) {
|
||||
$endpoint .= sprintf( '¤cy=%s', $user_details['user_currency'] );
|
||||
}
|
||||
}
|
||||
|
||||
$response = Client::wpcom_json_api_request_as_blog(
|
||||
sprintf( '/jetpack-search/pricing?record_count=%1$d&locale=%2$s', $record_count, get_user_locale() ),
|
||||
$endpoint,
|
||||
'2',
|
||||
array( 'timeout' => 5 ),
|
||||
null,
|
||||
'wpcom'
|
||||
);
|
||||
} else {
|
||||
// For non-simple sites we have to use the wp_remote_get, as connection might not be available.
|
||||
$response = wp_remote_get(
|
||||
sprintf( Constants::get_constant( 'JETPACK__WPCOM_JSON_API_BASE' ) . '/wpcom/v2/jetpack-search/pricing?record_count=%1$d&locale=%2$s', $record_count, get_user_locale() ),
|
||||
array( 'timeout' => 5 )
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* Search product
|
||||
* Jetpack Social product
|
||||
*
|
||||
* @package my-jetpack
|
||||
*/
|
||||
@ -86,7 +86,7 @@ class Social extends Hybrid_Product {
|
||||
* @return string
|
||||
*/
|
||||
public static function get_description() {
|
||||
return __( 'Auto-publish to social media', 'jetpack-my-jetpack' );
|
||||
return __( 'Effortlessly share content across social media. Right from within WordPress', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -95,7 +95,7 @@ class Social extends Hybrid_Product {
|
||||
* @return string
|
||||
*/
|
||||
public static function get_long_description() {
|
||||
return __( 'Promote your content on social media by automatically publishing when you publish on your site.', 'jetpack-my-jetpack' );
|
||||
return __( 'Grow your following by sharing your content across social media automatically.', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -141,7 +141,7 @@ class Social extends Hybrid_Product {
|
||||
* @return string
|
||||
*/
|
||||
public static function get_wpcom_product_slug() {
|
||||
return 'jetpack_social_basic_yearly';
|
||||
return 'jetpack_social_v1_yearly';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -150,6 +150,13 @@ class Social extends Hybrid_Product {
|
||||
* @return boolean
|
||||
*/
|
||||
public static function has_paid_plan_for_product() {
|
||||
$plans_with_social = array(
|
||||
'jetpack_social',
|
||||
'jetpack_complete',
|
||||
'jetpack_business',
|
||||
'jetpack_premium',
|
||||
'jetpack_personal',
|
||||
);
|
||||
// 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() ) {
|
||||
@ -160,11 +167,13 @@ class Social extends Hybrid_Product {
|
||||
if ( is_wp_error( $purchases_data ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
|
||||
foreach ( $purchases_data as $purchase ) {
|
||||
// Social is available as standalone bundle and as part of the Complete plan.
|
||||
if ( strpos( $purchase->product_slug, 'jetpack_social' ) !== false || str_starts_with( $purchase->product_slug, 'jetpack_complete' ) ) {
|
||||
return true;
|
||||
foreach ( $plans_with_social as $plan ) {
|
||||
if ( strpos( $purchase->product_slug, $plan ) !== false ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ namespace Automattic\Jetpack\My_Jetpack\Products;
|
||||
|
||||
use Automattic\Jetpack\My_Jetpack\Initializer;
|
||||
use Automattic\Jetpack\My_Jetpack\Module_Product;
|
||||
use Automattic\Jetpack\My_jetpack\Products;
|
||||
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
|
||||
use Automattic\Jetpack\Status\Host;
|
||||
use Jetpack_Options;
|
||||
@ -90,7 +91,7 @@ class Stats extends Module_Product {
|
||||
* @return string
|
||||
*/
|
||||
public static function get_description() {
|
||||
return __( 'Simple, yet powerful analytics', 'jetpack-my-jetpack' );
|
||||
return __( 'The simplest way to track visitor insights and unlock your site’s growth', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -99,7 +100,7 @@ class Stats extends Module_Product {
|
||||
* @return string
|
||||
*/
|
||||
public static function get_long_description() {
|
||||
return __( 'With Jetpack Stats, you don’t need to be a data scientist to see how your site is performing.', 'jetpack-my-jetpack' );
|
||||
return __( 'With Jetpack Stats, you don’t need to be a data scientist to see how your site is performing, understand your visitors, and grow your site.', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -169,10 +170,10 @@ class Stats extends Module_Product {
|
||||
*/
|
||||
public static function get_status() {
|
||||
$status = parent::get_status();
|
||||
if ( 'module_disabled' === $status && ! Initializer::is_registered() ) {
|
||||
if ( Products::STATUS_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';
|
||||
$status = Products::STATUS_NEEDS_FIRST_SITE_CONNECTION;
|
||||
}
|
||||
return $status;
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ class Videopress extends Hybrid_Product {
|
||||
* @return string
|
||||
*/
|
||||
public static function get_description() {
|
||||
return __( 'High quality, ad-free video', 'jetpack-my-jetpack' );
|
||||
return __( 'Stunning-quality, ad-free video in the WordPress Editor', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -101,7 +101,7 @@ class Videopress extends Hybrid_Product {
|
||||
* @return string
|
||||
*/
|
||||
public static function get_long_description() {
|
||||
return __( 'High-quality, ad-free video built specifically for WordPress.', 'jetpack-my-jetpack' );
|
||||
return __( 'Stunning-quality, ad-free video in the WordPress Editor', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -179,14 +179,22 @@ class Videopress extends Hybrid_Product {
|
||||
* @return boolean
|
||||
*/
|
||||
public static function has_paid_plan_for_product() {
|
||||
$purchases_data = Wpcom_Products::get_site_current_purchases();
|
||||
$plans_with_videopress = array(
|
||||
'jetpack_videopress',
|
||||
'jetpack_complete',
|
||||
'jetpack_business',
|
||||
'jetpack_premium',
|
||||
);
|
||||
$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;
|
||||
foreach ( $plans_with_videopress as $plan ) {
|
||||
if ( strpos( $purchase->product_slug, $plan ) !== false ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user