updated plugin Jetpack Protect version 1.4.2

This commit is contained in:
2023-10-22 22:21:06 +00:00
committed by Gitium
parent f512d25847
commit f07dfae114
242 changed files with 6494 additions and 1502 deletions

View File

@ -9,14 +9,16 @@ namespace Automattic\Jetpack\My_Jetpack;
use Automattic\Jetpack\Admin_UI\Admin_Menu;
use Automattic\Jetpack\Assets;
use Automattic\Jetpack\Connection\Client as Client;
use Automattic\Jetpack\Connection\Client;
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\JITMS\JITM as JITM;
use Automattic\Jetpack\Constants as Jetpack_Constants;
use Automattic\Jetpack\JITMS\JITM;
use Automattic\Jetpack\Licensing;
use Automattic\Jetpack\Modules;
use Automattic\Jetpack\Plugins_Installer;
use Automattic\Jetpack\Status as Status;
use Automattic\Jetpack\Status;
use Automattic\Jetpack\Terms_Of_Service;
use Automattic\Jetpack\Tracking;
@ -30,7 +32,12 @@ class Initializer {
*
* @var string
*/
const PACKAGE_VERSION = '2.9.1';
const PACKAGE_VERSION = '3.9.1';
/**
* HTML container ID for the IDC screen on My Jetpack page.
*/
const IDC_CONTAINER_ID = 'my-jetpack-identity-crisis-container';
/**
* Initialize My Jetpack
@ -58,10 +65,10 @@ class Initializer {
$page_suffix = Admin_Menu::add_menu(
__( 'My Jetpack', 'jetpack-my-jetpack' ),
__( 'My Jetpack', 'jetpack-my-jetpack' ),
'manage_options',
'edit_posts',
'my-jetpack',
array( __CLASS__, 'admin_page' ),
999
-1
);
add_action( 'load-' . $page_suffix, array( __CLASS__, 'admin_init' ) );
@ -116,6 +123,7 @@ class Initializer {
* @return void
*/
public static function admin_init() {
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.
header( 'Cache-Control: no-cache, no-store, must-revalidate' );
@ -150,6 +158,7 @@ class Initializer {
'textdomain' => 'jetpack-my-jetpack',
)
);
$modules = new Modules();
wp_localize_script(
'my_jetpack_main_app',
'myJetpackInitialState',
@ -165,9 +174,13 @@ class Initializer {
'topJetpackMenuItemUrl' => Admin_Menu::get_top_level_menu_item_url(),
'siteSuffix' => ( new Status() )->get_site_suffix(),
'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' ),
'isStatsModuleActive' => $modules->is_active( 'stats' ),
)
);
@ -181,7 +194,7 @@ class Initializer {
);
// Connection Initial State.
wp_add_inline_script( 'my_jetpack_main_app', Connection_Initial_State::render(), 'before' );
Connection_Initial_State::render_script( 'my_jetpack_main_app' );
// Required for Analytics.
if ( self::can_use_analytics() ) {
@ -189,6 +202,20 @@ class Initializer {
}
}
/**
* Build flags for My Jetpack UI
*
* @return array
*/
public static function get_my_jetpack_flags() {
$flags = array(
'videoPressStats' => Jetpack_Constants::is_true( 'JETPACK_MY_JETPACK_VIDEOPRESS_STATS_ENABLED' ),
'showJetpackStatsCard' => class_exists( 'Jetpack' ),
);
return $flags;
}
/**
* Echoes the admin page content.
*
@ -206,6 +233,9 @@ class Initializer {
public static function register_rest_endpoints() {
new REST_Products();
new REST_Purchases();
new REST_Zendesk_Chat();
new REST_Product_Data();
new REST_AI();
register_rest_route(
'my-jetpack/v1',
@ -311,4 +341,12 @@ class Initializer {
return $write_access;
}
/**
* Get container IDC for the IDC screen.
*
* @return string
*/
public static function get_idc_container_id() {
return static::IDC_CONTAINER_ID;
}
}

View File

@ -27,12 +27,14 @@ class Products {
'boost' => Products\Boost::class,
'crm' => Products\Crm::class,
'extras' => Products\Extras::class,
'jetpack-ai' => Products\Jetpack_Ai::class,
'scan' => Products\Scan::class,
'search' => Products\Search::class,
'social' => Products\Social::class,
'security' => Products\Security::class,
'protect' => Products\Protect::class,
'videopress' => Products\Videopress::class,
'stats' => Products\Stats::class,
);
/**
@ -178,5 +180,4 @@ class Products {
$class_name::extend_plugin_action_links();
}
}
}

View File

@ -0,0 +1,90 @@
<?php
/**
* Sets up the AI 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 Jetpack_Options;
use WP_Error;
/**
* Registers the REST routes for AI.
*/
class REST_AI {
/**
* Constructor.
*/
public function __construct() {
/*
* Check if the `jetpack/v4/jetpack-ai-jwt` endpoint is registered
* by the Jetpack plugin to avoid registering it again.
* In case it's not registered, register it
* to make it available for Jetpack products that depend on it.
*/
if ( ! self::is_rest_endpoint_registered( 'jetpack/v4', '/jetpack-ai-jwt' ) ) {
register_rest_route(
'jetpack/v4',
'jetpack-ai-jwt',
array(
'methods' => \WP_REST_Server::EDITABLE,
'callback' => __CLASS__ . '::get_openai_jwt',
'permission_callback' => function () {
return ( new Connection_Manager( 'jetpack' ) )->is_user_connected() && current_user_can( 'edit_posts' );
},
)
);
}
}
/**
* Check if a specific REST endpoint is registered.
*
* @param string $namespace - The namespace of the endpoint.
* @param string $route - The route of the endpoint.
* @return bool True if the endpoint is registered, false otherwise.
*/
public static function is_rest_endpoint_registered( $namespace, $route ) {
$server = rest_get_server();
$routes = $server->get_routes();
$full_endpoint = '/' . trim( $namespace, '/' ) . $route;
return isset( $routes[ $full_endpoint ] );
}
/**
* Ask WPCOM for a JWT token to use for OpenAI completion.
*/
public static function get_openai_jwt() {
$blog_id = Jetpack_Options::get_option( 'id' );
$response = Client::wpcom_json_api_request_as_user(
"/sites/$blog_id/jetpack-openai-query/jwt",
'2',
array(
'method' => 'POST',
'headers' => array( 'Content-Type' => 'application/json; charset=utf-8' ),
),
wp_json_encode( array() ),
'wpcom'
);
if ( is_wp_error( $response ) ) {
return $response;
}
$json = json_decode( wp_remote_retrieve_body( $response ) );
if ( ! isset( $json->token ) ) {
return new WP_Error( 'no-token', 'No token returned from WPCOM' );
}
return array(
'token' => $json->token,
'blog_id' => $blog_id,
);
}
}

View File

@ -0,0 +1,58 @@
<?php
/**
* Sets up the Product Data REST API endpoints.
*
* @package automattic/my-jetpack
*/
namespace Automattic\Jetpack\My_Jetpack;
use Automattic\Jetpack\Connection\Client;
use WP_Error;
/**
* Registers the REST routes for Product Data
*/
class REST_Product_Data {
/**
* Constructor.
*/
public function __construct() {
register_rest_route(
'my-jetpack/v1',
'site/product-data',
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => __CLASS__ . '::get_all_product_data',
'permission_callback' => __CLASS__ . '::permissions_callback',
)
);
}
/**
* Checks if the user has the correct permissions
*/
public static function permissions_callback() {
return current_user_can( 'manage_options' );
}
/**
* Gets the product data for all products
*
* @return array|WP_Error
*/
public static function get_all_product_data() {
$site_id = \Jetpack_Options::get_option( 'id' );
$wpcom_endpoint = sprintf( 'sites/%d/jetpack-product-data?locale=%2$s&force=wpcom', $site_id, get_user_locale() );
$api_version = '2';
$response = Client::wpcom_json_api_request_as_blog( $wpcom_endpoint, $api_version, 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( $response['body'] ) || 200 !== $response_code ) {
return new WP_Error( 'site_products_data_fetch_failed', 'Site products data fetch failed', array( 'status' => $response_code ? $response_code : 400 ) );
}
return rest_ensure_response( $body, 200 );
}
}

View File

@ -68,6 +68,21 @@ class REST_Products {
),
)
);
register_rest_route(
'my-jetpack/v1',
'site/products/(?P<product>[a-z\-]+)/install-standalone',
array(
array(
'methods' => \WP_REST_Server::EDITABLE,
'callback' => __CLASS__ . '::install_standalone',
'permission_callback' => __CLASS__ . '::edit_permissions_callback',
'args' => array(
'product' => $product_arg,
),
),
)
);
}
/**
@ -205,4 +220,40 @@ class REST_Products {
return rest_ensure_response( Products::get_product( $product_slug ), 200 );
}
/**
* Callback for installing the standalone plugin on a Hybrid Product.
*
* @param \WP_REST_Request $request The request object.
* @return \WP_REST_Response
*/
public static function install_standalone( $request ) {
$product_slug = $request->get_param( 'product' );
$product = Products::get_product( $product_slug );
if ( ! isset( $product['class'] ) ) {
return new \WP_Error(
'not_implemented',
__( 'The product class handler is not implemented', 'jetpack-my-jetpack' ),
array( 'status' => 501 )
);
}
/**
* 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 ) );
return $install_product_result;
}
return rest_ensure_response( Products::get_product( $product_slug ), 200 );
}
}

View File

@ -7,7 +7,7 @@
namespace Automattic\Jetpack\My_Jetpack;
use Automattic\Jetpack\Connection\Client as Client;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
/**
@ -51,7 +51,7 @@ class REST_Purchases {
);
}
return current_user_can( 'manage_options' );
return current_user_can( 'edit_posts' );
}
/**

View File

@ -0,0 +1,120 @@
<?php
/**
* Sets up the Zendesk Chat REST API endpoints.
*
* @package automattic/my-jetpack
*/
namespace Automattic\Jetpack\My_Jetpack;
use Automattic\Jetpack\Connection\Client;
/**
* Registers the REST routes for Zendesk Chat.
*/
class REST_Zendesk_Chat {
const TRANSIENT_EXPIRY = 1 * MINUTE_IN_SECONDS * 60 * 24 * 7; // 1 week (JWT is actually 2 weeks, but lets be on the safe side)
const ZENDESK_AUTH_TOKEN = 'zendesk_auth_token';
/**
* Constructor.
*/
public function __construct() {
register_rest_route(
'my-jetpack/v1',
'chat/availability',
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => __CLASS__ . '::get_chat_availability',
'permission_callback' => __CLASS__ . '::chat_authentication_permissions_callback',
)
);
register_rest_route(
'my-jetpack/v1',
'chat/authentication',
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => __CLASS__ . '::get_chat_authentication',
'args' => array(
'type' => array(
'required' => false,
'type' => 'string',
),
'test_mode' => array(
'required' => false,
'type' => 'boolean',
),
),
'permission_callback' => __CLASS__ . '::chat_authentication_permissions_callback',
)
);
}
/**
* Ensure user is logged in if making an authentication request
*
* @access public
* @static
*
* @return \WP_Error|true
*/
public static function chat_authentication_permissions_callback() {
if ( ! get_current_user_id() ) {
return new \WP_Error( 'unauthorized', 'You must be logged in to access this resource.', array( 'status' => 401 ) );
}
return true;
}
/**
* Gets the chat authentication token.
*
* @return \WP_Error|object Object: { token: string }
*/
public static function get_chat_authentication() {
$authentication = get_transient( self::ZENDESK_AUTH_TOKEN );
if ( $authentication ) {
return rest_ensure_response( $authentication, 200 );
}
$proxied = function_exists( 'wpcom_is_proxied_request' ) ? wpcom_is_proxied_request() : false;
$wpcom_endpoint = 'help/authenticate/chat';
$wpcom_api_version = '2';
$body = array(
'type' => 'zendesk',
'test_mode' => $proxied ? true : false,
);
$response = Client::wpcom_json_api_request_as_user( $wpcom_endpoint, $wpcom_api_version, array( 'method' => 'POST' ), $body );
$response_code = wp_remote_retrieve_response_code( $response );
$body = json_decode( wp_remote_retrieve_body( $response ) );
if ( is_wp_error( $response ) || empty( $response['body'] ) ) {
return new \WP_Error( 'chat_authentication_failed', 'Chat authentication failed', array( 'status' => $response_code ) );
}
set_transient( self::ZENDESK_AUTH_TOKEN, $body, self::TRANSIENT_EXPIRY );
return rest_ensure_response( $body, 200 );
}
/**
* Calls `wpcom/v2/presales/chat?group=jp_presales` endpoint.
* This endpoint returns whether or not the Jetpack presales chat group is available
*
* @return \WP_Error/object Object: { is_available: bool }
*/
public static function get_chat_availability() {
$wpcom_endpoint = '/presales/chat?group=jp_presales';
$wpcom_api_version = '2';
$response = Client::wpcom_json_api_request_as_user( $wpcom_endpoint, $wpcom_api_version );
$response_code = wp_remote_retrieve_response_code( $response );
$body = json_decode( wp_remote_retrieve_body( $response ) );
if ( is_wp_error( $response ) || empty( $response['body'] ) ) {
return new \WP_Error( 'chat_config_data_fetch_failed', 'Chat config data fetch failed', array( 'status' => $response_code ) );
}
return rest_ensure_response( $body, 200 );
}
}

View File

@ -7,8 +7,9 @@
namespace Automattic\Jetpack\My_Jetpack;
use Automattic\Jetpack\Connection\Client as Client;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Status\Visitor;
use Jetpack_Options;
use WP_Error;
/**
* Stores the list of products available for purchase in WPCOM
@ -189,6 +190,7 @@ class Wpcom_Products {
'discount_price' => $discount_price,
'is_introductory_offer' => $is_introductory_offer,
'introductory_offer' => $introductory_offer,
'product_term' => $product->product_term,
);
return self::populate_with_discount( $product, $pricing, $discount_price );
@ -226,4 +228,37 @@ class Wpcom_Products {
return $pricing;
}
/**
* Gets the site purchases from WPCOM.
*
* @todo Maybe add caching.
*
* @return Object|WP_Error
*/
public static function get_site_current_purchases() {
// TODO: Add a short-lived cache (less than a minute) to accommodate repeated invocation of this function.
static $purchases = null;
if ( $purchases !== null ) {
return $purchases;
}
$site_id = Jetpack_Options::get_option( 'id' );
$response = Client::wpcom_json_api_request_as_blog(
sprintf( '/sites/%d/purchases', $site_id ),
'1.1',
array(
'method' => 'GET',
)
);
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
return new WP_Error( 'purchases_state_fetch_failed' );
}
$body = wp_remote_retrieve_body( $response );
$purchases = json_decode( $body );
return $purchases;
}
}

View File

@ -87,7 +87,6 @@ class Anti_Spam extends Product {
public static function get_features() {
return array(
_x( 'Comment and form spam protection', 'Anti-Spam Product Feature', 'jetpack-my-jetpack' ),
_x( 'Powered by Akismet', 'Anti-Spam Product Feature', 'jetpack-my-jetpack' ),
_x( 'Block spam without CAPTCHAs', 'Anti-Spam Product Feature', 'jetpack-my-jetpack' ),
_x( 'Advanced stats', 'Anti-Spam Product Feature', 'jetpack-my-jetpack' ),
);

View File

@ -8,7 +8,7 @@
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\My_Jetpack\Product;
use Automattic\Jetpack\My_Jetpack\Hybrid_Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
use Automattic\Jetpack\Redirect;
use Jetpack_Options;
@ -17,7 +17,7 @@ use WP_Error;
/**
* Class responsible for handling the Backup product
*/
class Backup extends Product {
class Backup extends Hybrid_Product {
/**
* The product slug
@ -44,6 +44,13 @@ class Backup extends Product {
*/
public static $plugin_slug = 'jetpack-backup';
/**
* Backup has a standalone plugin
*
* @var bool
*/
public static $has_standalone_plugin = true;
/**
* Get the internationalized product name
*
@ -198,7 +205,9 @@ class Backup extends Product {
* @return ?string
*/
public static function get_manage_url() {
if ( static::is_plugin_active() ) {
if ( static::is_jetpack_plugin_active() ) {
return Redirect::get_url( 'my-jetpack-manage-backup' );
} elseif ( static::is_plugin_active() ) {
return admin_url( 'admin.php?page=jetpack-backup' );
}
}
@ -211,4 +220,15 @@ class Backup extends 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_url( 'admin.php?page=jetpack#/recommendations' );
} elseif ( static::is_plugin_active() ) {
return admin_url( 'admin.php?page=jetpack-backup' );
}
}
}

View File

@ -8,12 +8,17 @@
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\My_Jetpack\Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
/**
* Class responsible for handling the Boost product
*/
class Boost extends Product {
const FREE_TIER_SLUG = 'free';
const UPGRADED_TIER_SLUG = 'upgraded';
const UPGRADED_TIER_PRODUCT_SLUG = 'jetpack_boost_yearly';
/**
* The product slug
*
@ -69,7 +74,7 @@ class Boost extends Product {
* @return string
*/
public static function get_description() {
return __( 'Instant speed and SEO', 'jetpack-my-jetpack' );
return __( 'The easiest speed optimization plugin for WordPress', 'jetpack-my-jetpack' );
}
/**
@ -94,6 +99,144 @@ class Boost extends Product {
);
}
/**
* Get the product's available tiers
*
* @return string[] Slugs of the available tiers
*/
public static function get_tiers() {
return array(
self::UPGRADED_TIER_SLUG,
self::FREE_TIER_SLUG,
);
}
/**
* Get the internationalized comparison of free vs upgraded features
*
* @return array[] Protect features comparison
*/
public static function get_features_by_tier() {
return array(
array(
'name' => __( 'Optimize CSS Loading', 'jetpack-my-jetpack' ),
'info' => array(
'content' => __(
'Move important styling information to the start of the page, which helps pages display your content sooner, so your users dont have to wait for the entire page to load. Commonly referred to as Critical CSS.',
'jetpack-my-jetpack'
),
),
'tiers' => array(
self::FREE_TIER_SLUG => array(
'included' => true,
'description' => __( 'Must be done manually', 'jetpack-my-jetpack' ),
'info' => array(
'title' => __( 'Manual Critical CSS regeneration', 'jetpack-my-jetpack' ),
'content' => __(
'<p>To enhance the speed of your site, with this plan you will need to optimize CSS by using the Manual Critical CSS generation feature whenever you:</p>
<ul>
<li>Make theme changes.</li>
<li>Write a new post/page.</li>
<li>Edit a post/page.</li>
<li>Activate, deactivate, or update plugins that impact your site layout or HTML structure.</li>
<li>Change settings of plugins that impact your site layout or HTML structure.</li>
<li>Upgrade your WordPress version if the new release includes core CSS changes.</li>
</ul>',
'jetpack-my-jetpack'
),
),
),
self::UPGRADED_TIER_SLUG => array(
'included' => true,
'description' => __( 'Automatically updated', 'jetpack-my-jetpack' ),
'info' => array(
'title' => __( 'Automatic Critical CSS regeneration', 'jetpack-my-jetpack' ),
'content' => __(
'<p>Its essential to regenerate Critical CSS to optimize your site speed whenever your HTML or CSS structure changes. Being on top of this can be tedious and time-consuming.</p>
<p>Boosts cloud service can automatically detect when your site needs the Critical CSS regenerated, and perform this function behind the scenes without requiring you to monitor it manually.</p>',
'jetpack-my-jetpack'
),
),
),
),
),
array(
'name' => __( 'Defer non-essential JavaScript', 'jetpack-my-jetpack' ),
'info' => array(
'content' => __(
'Run non-essential JavaScript after the page has loaded so that styles and images can load more quickly.',
'jetpack-my-jetpack'
),
'link' => array(
'id' => 'jetpack-boost-defer-js',
'title' => 'web.dev',
),
),
'tiers' => array(
self::FREE_TIER_SLUG => array( 'included' => true ),
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
),
),
array(
'name' => __( 'Lazy image loading', 'jetpack-my-jetpack' ),
'info' => array(
'content' => __(
'Improve page loading speed by only loading images when they are required.',
'jetpack-my-jetpack'
),
'link' => array(
'id' => 'jetpack-boost-lazy-load',
'title' => 'web.dev',
),
),
'tiers' => array(
self::FREE_TIER_SLUG => array( 'included' => true ),
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
),
),
array(
'name' => __( 'Image guide', 'jetpack-my-jetpack' ),
'info' => array(
'content' => __(
'Discover and fix images with a suboptimal resolution, aspect ratio, or file size, improving user experience and page speed.',
'jetpack-my-jetpack'
),
),
'tiers' => array(
self::FREE_TIER_SLUG => array( 'included' => true ),
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
),
),
array(
'name' => __( 'Image CDN', 'jetpack-my-jetpack' ),
'info' => array(
'content' => __(
'Deliver images from Jetpack\'s Content Delivery Network. Automatically resizes your images to an appropriate size, converts them to modern efficient formats like WebP, and serves them from a worldwide network of servers.',
'jetpack-my-jetpack'
),
),
'tiers' => array(
self::FREE_TIER_SLUG => array( 'included' => true ),
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
),
),
array(
'name' => __( 'Dedicated email support', 'jetpack-my-jetpack' ),
'info' => array(
'content' => __(
'<p>Paid customers get dedicated email support from our world-class Happiness Engineers to help with any issue.</p>
<p>All other questions are handled by our team as quickly as we are able to go through the WordPress support forum.</p>',
'jetpack-my-jetpack'
),
),
'tiers' => array(
self::FREE_TIER_SLUG => array( 'included' => false ),
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
),
),
);
}
/**
* Get the product princing details
*
@ -101,8 +244,19 @@ class Boost extends Product {
*/
public static function get_pricing_for_ui() {
return array(
'available' => true,
'is_free' => true,
'tiers' => array(
self::FREE_TIER_SLUG => array(
'available' => true,
'is_free' => true,
),
self::UPGRADED_TIER_SLUG => array_merge(
array(
'available' => true,
'wpcom_product_slug' => self::UPGRADED_TIER_PRODUCT_SLUG,
),
Wpcom_Products::get_product_pricing( self::UPGRADED_TIER_PRODUCT_SLUG )
),
),
);
}
@ -114,4 +268,25 @@ class Boost extends Product {
public static function get_manage_url() {
return admin_url( 'admin.php?page=jetpack-boost' );
}
/**
* Activates the product by installing and activating its plugin
*
* @param bool|WP_Error $current_result Is the result of the top level activation actions. You probably won't do anything if it is an WP_Error.
* @return boolean|\WP_Error
*/
public static function do_product_specific_activation( $current_result ) {
$product_activation = parent::do_product_specific_activation( $current_result );
if ( is_wp_error( $product_activation ) && 'module_activation_failed' === $product_activation->get_error_code() ) {
// A bundle is not a module. There's nothing in the plugin to be activated, so it's ok to fail to activate the module.
$product_activation = true;
}
// We just "got started" in My Jetpack, so skip the in-plugin experience.
update_option( 'jb_get_started', false );
return $product_activation;
}
}

View File

@ -12,13 +12,6 @@ use Automattic\Jetpack\Plugins_Installer;
use WP_Error;
/**
*
* DEPRECATED: This class is deprecated and will be removed in a future version.
*
* All product classes have been moved out of the hybrid class concept
*
* @deprecated 2.7.2
*
* 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.
@ -29,6 +22,13 @@ use WP_Error;
*/
abstract class Hybrid_Product extends Product {
/**
* All hybrid products have a standalone plugin
*
* @var bool
*/
public static $has_standalone_plugin = true;
/**
* Checks whether the Product is active
*
@ -38,6 +38,15 @@ abstract class Hybrid_Product extends Product {
return parent::is_plugin_active() || parent::is_jetpack_plugin_active();
}
/**
* Checks whether the standalone plugin for this product is active
*
* @return boolean
*/
public static function is_standalone_plugin_active() {
return parent::is_plugin_active();
}
/**
* Checks whether the plugin is installed
*
@ -129,4 +138,55 @@ abstract class Hybrid_Product extends Product {
return true;
}
/**
* Install and activate the standalone plugin in the case it's missing.
*
* @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();
if ( is_wp_error( $result ) ) {
return $result;
}
/**
* 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() ) {
$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' ) );
}
}
return true;
}
}

View File

@ -0,0 +1,205 @@
<?php
/**
* Boost product
*
* @package my-jetpack
*/
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\My_Jetpack\Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
/**
* Class responsible for handling the Jetpack AI product
*/
class Jetpack_Ai extends Product {
/**
* The product slug
*
* @var string
*/
public static $slug = 'jetpack-ai';
/**
* Get the Product info for the API
*
* @throws \Exception If required attribute is not declared in the child class.
* @return array
*/
public static function get_info() {
// Call parent method to get the default info.
$info = parent::get_info();
// Populate the product with the feature data.
$info['ai-assistant-feature'] = self::get_ai_assistant_feature();
return $info;
}
/**
* Get the plugin slug - ovewrite it and return Jetpack's
*
* @return ?string
*/
public static function get_plugin_slug() {
return self::JETPACK_PLUGIN_SLUG;
}
/**
* Get the plugin filename - ovewrite it and return Jetpack's
*
* @return ?string
*/
public static function get_plugin_filename() {
return self::JETPACK_PLUGIN_FILENAME;
}
/**
* Get the internationalized product name
*
* @return string
*/
public static function get_name() {
return __( 'Jetpack AI', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product title
*
* @return string
*/
public static function get_title() {
return __( 'Jetpack AI', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product description
*
* @return string
*/
public static function get_description() {
return __( 'Experimental tool to add AI to your editor', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product long description
*
* @return string
*/
public static function get_long_description() {
return __( 'Jetpack AI Assistant brings the power of AI right into your WordPress editor, letting your content creation soar to new heights.', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized features list
*
* @return array CRM features list
*/
public static function get_features() {
return 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' ),
);
}
/**
* Get the product princing details
*
* @return array Pricing details
*/
public static function get_pricing_for_ui() {
return array_merge(
array(
'available' => true,
'wpcom_product_slug' => static::get_wpcom_product_slug(),
),
Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() )
);
}
/**
* Get the WPCOM product slug used to make the purchase
*
* @return ?string
*/
public static function get_wpcom_product_slug() {
return 'jetpack_ai_yearly';
}
/**
* Get the WPCOM monthly product slug used to make the purchase
*
* @return ?string
*/
public static function get_wpcom_monthly_product_slug() {
return 'jetpack_ai_monthly';
}
/**
* Checks whether the current plan (or purchases) of the site already supports the 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 ( 0 === strpos( $purchase->product_slug, static::get_wpcom_product_slug() ) ) {
return true;
}
if ( 0 === strpos( $purchase->product_slug, static::get_wpcom_monthly_product_slug() ) ) {
return true;
}
}
}
return false;
}
/**
* Get the URL where the user manages the product
*
* @return ?string
*/
public static function get_manage_url() {
return '';
}
/**
* Get data about the AI Assistant feature
*
* @return array
*/
public static function get_ai_assistant_feature() {
// Bail early if the plugin is not active.
if ( ! self::is_jetpack_plugin_installed() ) {
return array();
}
// Check if the global constant is defined.
if ( ! defined( 'JETPACK__PLUGIN_DIR' ) ) {
return array();
}
// Check if class exists. If not, try to require it once.
if ( ! class_exists( 'Jetpack_AI_Helper' ) ) {
$class_file_path = JETPACK__PLUGIN_DIR . '_inc/lib/class-jetpack-ai-helper.php';
// Check whether the file exists
if ( ! file_exists( $class_file_path ) ) {
return array();
}
require_once $class_file_path;
}
return \Jetpack_AI_Helper::get_ai_assistance_feature();
}
}

View File

@ -130,5 +130,4 @@ abstract class Module_Product extends Product {
}
return Jetpack::deactivate_module( static::$module_name );
}
}

View File

@ -61,6 +61,13 @@ abstract class Product {
*/
public static $requires_user_connection = true;
/**
* Whether this product has a standalone plugin
*
* @var bool
*/
public static $has_standalone_plugin = false;
/**
* Get the plugin slug
*
@ -117,19 +124,26 @@ abstract class Product {
'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(),
'class' => get_called_class(),
'standalone_plugin_info' => static::get_standalone_info(),
'class' => static::class,
'post_checkout_url' => static::get_post_checkout_url(),
);
}
@ -161,6 +175,15 @@ abstract class Product {
*/
abstract public static function get_long_description();
/**
* Get the tiers for the product
*
* @return boolean|string[] The slugs of the tiers (i.e. [ "free", "basic", "advanced" ]), or False if the product has no tiers.
*/
public static function get_tiers() {
return array();
}
/**
* Get the internationalized features list
*
@ -168,6 +191,15 @@ abstract class Product {
*/
abstract public static function get_features();
/**
* Get the internationalized comparison of features grouped by each tier
*
* @return array
*/
public static function get_features_by_tier() {
return array();
}
/**
* Get the product pricing
*
@ -175,6 +207,16 @@ abstract class Product {
*/
abstract public static function get_pricing_for_ui();
/**
* Get the URL where the user can purchase the product iff it doesn't have an interstitial page in My Jetpack.
*
* @return ?string
*/
public static function get_purchase_url() {
// Declare as concrete method as most Jetpack products use an interstitial page within My Jetpack.
return null;
}
/**
* Get the URL where the user manages the product
*
@ -191,6 +233,15 @@ abstract class Product {
return static::get_manage_url();
}
/**
* Get the URL the user is taken after purchasing the product through the checkout
*
* @return ?string
*/
public static function get_post_checkout_url() {
return null;
}
/**
* Get the WPCOM product slug used to make the purchase
*
@ -209,6 +260,22 @@ abstract class Product {
return array();
}
/**
* Get the standalone plugin related info
*
* @return array
*/
public static function get_standalone_info() {
$is_standalone_installed = static::$has_standalone_plugin && self::is_plugin_installed();
$is_standalone_active = static::$has_standalone_plugin && self::is_plugin_active();
return array(
'has_standalone_plugin' => static::$has_standalone_plugin,
'is_standalone_installed' => $is_standalone_installed,
'is_standalone_active' => $is_standalone_active,
);
}
/**
* Checks whether the current plan (or purchases) of the site already supports the product
*
@ -222,6 +289,15 @@ abstract class Product {
return true;
}
/**
* Checks whether the current plan (or purchases) of the site already supports the tiers
*
* @return array Key/value pairs of tier slugs and whether they are supported or not.
*/
public static function has_required_tier() {
return array();
}
/**
* Checks whether the product supports trial or not
*
@ -235,6 +311,15 @@ abstract class Product {
return false;
}
/**
* Checks whether the product can be upgraded to a different product.
*
* @return boolean
*/
public static function is_upgradable() {
return false;
}
/**
* Checks whether product is a bundle.
*
@ -271,14 +356,19 @@ abstract class Product {
* @return string
*/
public static function get_status() {
if ( ! static::is_plugin_installed() ) {
$status = 'plugin_absent';
if ( static::has_required_plan() ) {
$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() ) {
$status = 'error';
} elseif ( static::is_upgradable() ) {
// Upgradable plans should ignore whether or not they have the required plan.
$status = 'can_upgrade';
} elseif ( ! static::has_required_plan() ) { // We need needs_purchase here as well because some products we consider active without the required plan.
if ( static::has_trial_support() ) {
$status = 'needs_purchase_or_free';
@ -464,5 +554,4 @@ abstract class Product {
}
}
}
}

View File

@ -8,12 +8,17 @@
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\My_Jetpack\Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
/**
* Class responsible for handling the Protect product
*/
class Protect extends Product {
const FREE_TIER_SLUG = 'free';
const UPGRADED_TIER_SLUG = 'upgraded';
const UPGRADED_TIER_PRODUCT_SLUG = 'jetpack_scan';
/**
* The product slug
*
@ -70,7 +75,7 @@ class Protect extends Product {
* @return string
*/
public static function get_description() {
return __( 'Protect your site and scan for security vulnerabilities.', 'jetpack-my-jetpack' );
return __( 'Stay one step ahead of threats', 'jetpack-my-jetpack' );
}
/**
@ -97,14 +102,118 @@ class Protect extends Product {
}
/**
* Get the product princing details
* Get the product's available tiers
*
* @return string[] Slugs of the available tiers
*/
public static function get_tiers() {
return array(
self::UPGRADED_TIER_SLUG,
self::FREE_TIER_SLUG,
);
}
/**
* Get the internationalized comparison of free vs upgraded features
*
* @return array[] Protect features comparison
*/
public static function get_features_by_tier() {
return array(
array(
'name' => __( 'Scan for threats and vulnerabilities', 'jetpack-my-jetpack' ),
'tiers' => array(
self::FREE_TIER_SLUG => array(
'included' => true,
'description' => __( 'Check items against database', 'jetpack-my-jetpack' ),
),
self::UPGRADED_TIER_SLUG => array(
'included' => true,
'description' => __( 'Line by line malware scanning', 'jetpack-my-jetpack' ),
),
),
),
array(
'name' => __( 'Daily automated scans', 'jetpack-my-jetpack' ),
'tiers' => array(
self::FREE_TIER_SLUG => array( 'included' => true ),
self::UPGRADED_TIER_SLUG => array(
'included' => true,
'description' => __( 'Plus on-demand manual scans', 'jetpack-my-jetpack' ),
),
),
),
array(
'name' => __( 'Web Application Firewall', 'jetpack-my-jetpack' ),
'tiers' => array(
self::FREE_TIER_SLUG => array(
'included' => false,
'description' => __( 'Manual rules only', 'jetpack-my-jetpack' ),
),
self::UPGRADED_TIER_SLUG => array(
'included' => true,
'description' => __( 'Automatic protection and rule updates', 'jetpack-my-jetpack' ),
),
),
),
array(
'name' => __( 'Brute force protection', 'jetpack-my-jetpack' ),
'tiers' => array(
self::FREE_TIER_SLUG => array( 'included' => true ),
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
),
),
array(
'name' => __( 'Access to scan on Cloud', 'jetpack-my-jetpack' ),
'tiers' => array(
self::FREE_TIER_SLUG => array( 'included' => false ),
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
),
),
array(
'name' => __( 'One-click auto fixes', 'jetpack-my-jetpack' ),
'tiers' => array(
self::FREE_TIER_SLUG => array( 'included' => false ),
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
),
),
array(
'name' => __( 'Notifications', 'jetpack-my-jetpack' ),
'tiers' => array(
self::FREE_TIER_SLUG => array( 'included' => false ),
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
),
),
array(
'name' => __( 'Severity labels', 'jetpack-my-jetpack' ),
'tiers' => array(
self::FREE_TIER_SLUG => array( 'included' => false ),
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
),
),
);
}
/**
* Get the product pricing details
*
* @return array Pricing details
*/
public static function get_pricing_for_ui() {
return array(
'available' => true,
'is_free' => true,
'tiers' => array(
self::FREE_TIER_SLUG => array(
'available' => true,
'is_free' => true,
),
self::UPGRADED_TIER_SLUG => array_merge(
array(
'available' => true,
'wpcom_product_slug' => self::UPGRADED_TIER_PRODUCT_SLUG,
),
Wpcom_Products::get_product_pricing( self::UPGRADED_TIER_PRODUCT_SLUG )
),
),
);
}
@ -116,4 +225,14 @@ class Protect extends Product {
public static function get_manage_url() {
return admin_url( 'admin.php?page=jetpack-protect' );
}
/**
* Return product bundles list
* that supports the product.
*
* @return array Products bundle list.
*/
public static function is_upgradable_by_bundle() {
return array( 'security' );
}
}

View File

@ -214,4 +214,18 @@ class Scan extends Module_Product {
public static function get_manage_url() {
return Redirect::get_url( 'my-jetpack-manage-scan' );
}
/**
* 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_url( 'admin.php?page=jetpack#/recommendations' );
}
// If Jetpack is not active, it means that the user has another standalone plugin active
// and it follows the `Protect` plugin flow instead of `Scan` so for now it would be safe
// to return null and let the user go back to the My Jetpack page.
return null;
}
}

View File

@ -9,7 +9,7 @@ namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Constants;
use Automattic\Jetpack\My_Jetpack\Product;
use Automattic\Jetpack\My_Jetpack\Hybrid_Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
use Automattic\Jetpack\Search\Module_Control as Search_Module_Control;
use Jetpack_Options;
@ -18,7 +18,7 @@ use WP_Error;
/**
* Class responsible for handling the Search product
*/
class Search extends Product {
class Search extends Hybrid_Product {
/**
* The product slug
*
@ -40,6 +40,13 @@ class Search extends Product {
*/
public static $plugin_slug = 'jetpack-search';
/**
* Search has a standalone plugin
*
* @var bool
*/
public static $has_standalone_plugin = true;
/**
* The filename (id) of the plugin associated with this product.
*
@ -320,5 +327,4 @@ class Search extends Product {
public static function get_manage_url() {
return admin_url( 'admin.php?page=jetpack-search' );
}
}

View File

@ -7,10 +7,8 @@
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\My_Jetpack\Module_Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
use Jetpack_Options;
use WP_Error;
/**
@ -162,45 +160,13 @@ class Security extends Module_Product {
return static::is_jetpack_plugin_active() && static::has_required_plan();
}
/**
* 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/purchases', $site_id ),
'1.1',
array(
'method' => 'GET',
)
);
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
return new WP_Error( 'purchases_state_fetch_failed' );
}
$body = wp_remote_retrieve_body( $response );
$status = json_decode( $body );
return $status;
}
/**
* Checks whether the current plan (or purchases) of the site already supports the product
*
* @return boolean
*/
public static function has_required_plan() {
$purchases_data = static::get_state_from_wpcom();
$purchases_data = Wpcom_Products::get_site_current_purchases();
if ( is_wp_error( $purchases_data ) ) {
return false;
}

View File

@ -7,13 +7,13 @@
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\My_Jetpack\Product;
use Automattic\Jetpack\My_Jetpack\Hybrid_Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
/**
* Class responsible for handling the Social product
*/
class Social extends Product {
class Social extends Hybrid_Product {
/**
* The product slug
@ -36,6 +36,13 @@ class Social extends Product {
*/
public static $plugin_slug = 'jetpack-social';
/**
* Social has a standalone plugin
*
* @var bool
*/
public static $has_standalone_plugin = true;
/**
* The filename (id) of the plugin associated with this product.
*
@ -121,13 +128,19 @@ class Social extends Product {
}
/**
* Get the URL where the user manages the product
* Get the URL where the user manages the product.
*
* If the standalone plugin is active,
* it will redirect to the standalone plugin settings page.
* Otherwise, it will redirect to the Jetpack settings page.
*
* @return string
*/
public static function get_manage_url() {
if ( static::is_plugin_active() ) {
if ( static::is_standalone_plugin_active() ) {
return admin_url( 'admin.php?page=jetpack-social' );
}
return admin_url( 'admin.php?page=jetpack#/settings?term=publicize' );
}
}

View File

@ -0,0 +1,203 @@
<?php
/**
* Starter plan
*
* @package my-jetpack
*/
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\My_Jetpack\Module_Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
use WP_Error;
/**
* Class responsible for handling the Starter plan
*/
class Starter extends Module_Product {
/**
* The product slug
*
* @var string
*/
public static $slug = 'starter';
/**
* The Jetpack module name
*
* @var string
*/
public static $module_name = 'starter';
/**
* Get the internationalized product name
*
* @return string
*/
public static function get_name() {
return _x( 'Starter', 'Jetpack product name', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product title
*
* @return string
*/
public static function get_title() {
return _x( 'Jetpack Starter', 'Jetpack product name', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product description
*
* @return string
*/
public static function get_description() {
return __( 'Essential security tools: real-time backups and comment spam protection.', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product long description
*
* @return string
*/
public static function get_long_description() {
return __( 'Essential security tools: real-time backups and comment spam protection.', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized features list
*
* @return array Starter features list
*/
public static function get_features() {
return array(
_x( 'Real-time cloud backups with 1GB storage', 'Starter Product Feature', 'jetpack-my-jetpack' ),
_x( 'One-click fixes for most threats', 'Starter Product Feature', 'jetpack-my-jetpack' ),
_x( 'Comment & form spam protection', 'Starter Product Feature', 'jetpack-my-jetpack' ),
);
}
/**
* Get the product princing details
*
* @return array Pricing details
*/
public static function get_pricing_for_ui() {
return array_merge(
array(
'available' => true,
'wpcom_product_slug' => static::get_wpcom_product_slug(),
),
Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() )
);
}
/**
* Get the WPCOM product slug used to make the purchase
*
* @return ?string
*/
public static function get_wpcom_product_slug() {
return 'jetpack_starter_yearly';
}
/**
* Checks whether the Jetpack module is active
*
* This is a bundle and not a product. We should not use this information for anything
*
* @return bool
*/
public static function is_module_active() {
return false;
}
/**
* Activates the product by installing and activating its plugin
*
* @param bool|WP_Error $current_result Is the result of the top level activation actions. You probably won't do anything if it is an WP_Error.
* @return boolean|\WP_Error
*/
public static function do_product_specific_activation( $current_result ) {
$product_activation = parent::do_product_specific_activation( $current_result );
if ( is_wp_error( $product_activation ) && 'module_activation_failed' === $product_activation->get_error_code() ) {
// A bundle is not a module. There's nothing in the plugin to be activated, so it's ok to fail to activate the module.
$product_activation = true;
}
// At this point, Jetpack plugin is installed. Let's activate each individual product.
$activation = Anti_Spam::activate();
if ( is_wp_error( $activation ) ) {
return $activation;
}
$activation = Backup::activate();
if ( is_wp_error( $activation ) ) {
return $activation;
}
return $activation;
}
/**
* Checks whether the Product is active
*
* Jetpack Starter is a bundle and not a module. Activation takes place on WPCOM. So lets consider it active if jetpack is active and has the plan.
*
* @return boolean
*/
public static function is_active() {
return static::is_jetpack_plugin_active() && static::has_required_plan();
}
/**
* Checks whether the current plan (or purchases) of the site already supports the 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 ( 0 === strpos( $purchase->product_slug, 'jetpack_starter' ) ) {
return true;
}
}
}
return false;
}
/**
* Checks whether product is a bundle.
*
* @return boolean True
*/
public static function is_bundle_product() {
return true;
}
/**
* Return all the products it contains.
*
* @return Array Product slugs
*/
public static function get_supported_products() {
return array( 'backup', 'anti-spam' );
}
/**
* Get the URL where the user manages the product
*
* @return ?string
*/
public static function get_manage_url() {
return '';
}
}

View File

@ -0,0 +1,241 @@
<?php
/**
* Jetpack Stats product
*
* @package my-jetpack
*/
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\My_Jetpack\Module_Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
use Jetpack_Options;
/**
* Class responsible for handling the Jetpack Stats product
*/
class Stats extends Module_Product {
/**
* The product slug
*
* @var string
*/
public static $slug = 'stats';
/**
* The Jetpack module name associated with this product
*
* @var string|null
*/
public static $module_name = 'stats';
/**
* Get the internationalized product name
*
* @return string
*/
public static function get_name() {
return __( 'Stats', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product title
*
* @return string
*/
public static function get_title() {
return __( 'Jetpack Stats', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product description
*
* @return string
*/
public static function get_description() {
return __( 'Simple, yet powerful analytics', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized product long description
*
* @return string
*/
public static function get_long_description() {
return __( 'With Jetpack Stats, you dont need to be a data scientist to see how your site is performing.', 'jetpack-my-jetpack' );
}
/**
* Get the internationalized features list
*
* @return array CRM features list
*/
public static function get_features() {
return array(
__( 'Instant access to upcoming features', 'jetpack-my-jetpack' ),
__( 'Priority support', 'jetpack-my-jetpack' ),
);
}
/**
* Get the product princing details
*
* @return array Pricing details
*/
public static function get_pricing_for_ui() {
return array_merge(
array(
'available' => true,
'wpcom_product_slug' => static::get_wpcom_product_slug(),
'wpcom_free_product_slug' => static::get_wpcom_free_product_slug(),
'wpcom_pwyw_product_slug' => static::get_wpcom_pwyw_product_slug(),
),
// TODO: replace with `Wpcom_Products::get_product_pricing` once available.
// This is not yet used anywhere, so it's fine to leave it as is for now.
array(
'currency_code' => 'USD',
'full_price' => 10,
'discount_price' => 10,
'product_term' => 'month',
)
);
}
/**
* Get the WPCOM product slug used to make the purchase
*
* @return ?string
*/
public static function get_wpcom_product_slug() {
return 'jetpack_stats_monthly';
}
/**
* Get the WPCOM Pay Whatever You Want product slug used to make the purchase
*
* @return ?string
*/
public static function get_wpcom_pwyw_product_slug() {
return 'jetpack_stats_pwyw_yearly';
}
/**
* Get the WPCOM free product slug
*
* @return ?string
*/
public static function get_wpcom_free_product_slug() {
return 'jetpack_stats_free_yearly';
}
/**
* Checks whether the site already supports this product through an existing plan or purchase
*
* @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 ( 0 === strpos( $purchase->product_slug, 'jetpack_stats' ) ) {
return true;
}
if ( 0 === strpos( $purchase->product_slug, 'jetpack_complete' ) ) {
return true;
}
}
}
return false;
}
/**
* 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 ) ) {
foreach ( $purchases_data as $purchase ) {
if (
(
// Purchase is Jetpack Stats...
0 === strpos( $purchase->product_slug, 'jetpack_stats' ) &&
// but not Jetpack Stats Free...
false === strpos( $purchase->product_slug, 'free' )
) || 0 === strpos( $purchase->product_slug, 'jetpack_complete' )
) {
// Only Jetpack Stats paid plans should be eligible for this conditional.
// Sample product slugs: jetpack_stats_monthly
return false;
}
}
}
return true;
}
/**
* Returns a redirect parameter for an upgrade URL if current purchase license is a free license
* or an empty string otherwise.
*
* @return string
*/
public static function get_url_redirect_string() {
$purchases_data = Wpcom_Products::get_site_current_purchases();
if ( is_wp_error( $purchases_data ) ) {
return '';
}
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
foreach ( $purchases_data as $purchase ) {
if (
0 === strpos( $purchase->product_slug, static::get_wpcom_free_product_slug() )
) {
return '&productType=personal';
}
}
}
return '';
}
/**
* Checks whether the product supports trial or not.
* Since Jetpack Stats has been widely available as a free product in the past, it "supports" a trial.
*
* @return boolean
*/
public static function has_trial_support() {
return true;
}
/**
* Get the WordPress.com URL for purchasing Jetpack Stats for the current site.
*
* @return ?string
*/
public static function get_purchase_url() {
// The returning URL could be customized by changing the `redirect_uri` param with relative path.
return sprintf(
'%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(),
rawurlencode( 'admin.php?page=stats' )
);
}
/**
* Get the URL where the user manages the product
*
* @return ?string
*/
public static function get_manage_url() {
return admin_url( 'admin.php?page=stats' );
}
}

View File

@ -7,13 +7,13 @@
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\My_Jetpack\Product;
use Automattic\Jetpack\My_Jetpack\Hybrid_Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
/**
* Class responsible for handling the VideoPress product
*/
class Videopress extends Product {
class Videopress extends Hybrid_Product {
/**
* The product slug
@ -54,6 +54,13 @@ class Videopress extends Product {
*/
public static $requires_user_connection = true;
/**
* VideoPress has a standalone plugin
*
* @var bool
*/
public static $has_standalone_plugin = true;
/**
* Get the internationalized product name
*
@ -145,7 +152,18 @@ class Videopress extends Product {
public static function get_manage_url() {
if ( method_exists( 'Automattic\Jetpack\VideoPress\Initializer', 'should_initialize_admin_ui' ) && \Automattic\Jetpack\VideoPress\Initializer::should_initialize_admin_ui() ) {
return \Automattic\Jetpack\VideoPress\Admin_UI::get_admin_page_url();
} else {
return admin_url( 'admin.php?page=jetpack#/settings?term=videopress' );
}
}
/**
* Checks whether the current plan (or purchases) of the site already supports the product
*
* @return boolean
*/
public static function has_required_plan() {
// TODO: import and perform a proper check with Current_Plan. See #33410.
return true;
}
}