updated plugin Jetpack Protect
version 2.0.0
This commit is contained in:
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* Manage the display of an "Activity Log" menu item.
|
||||
*
|
||||
* @package automattic/my-jetpack
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\My_Jetpack;
|
||||
|
||||
use Automattic\Jetpack\Admin_UI\Admin_Menu;
|
||||
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
|
||||
use Automattic\Jetpack\Redirect;
|
||||
|
||||
/**
|
||||
* Activity Log features in My Jetpack.
|
||||
*/
|
||||
class Activitylog {
|
||||
/**
|
||||
* Initialize the class and hooks needed.
|
||||
*/
|
||||
public static function init() {
|
||||
add_action( 'admin_menu', array( self::class, 'add_submenu_jetpack' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* The page to be added to submenu
|
||||
*
|
||||
* @return void|null|string The resulting page's hook_suffix
|
||||
*/
|
||||
public static function add_submenu_jetpack() {
|
||||
// Only proceed if the user is connected to WordPress.com.
|
||||
if ( ! ( new Connection_Manager() )->is_user_connected() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not display the menu on Multisite.
|
||||
if ( is_multisite() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$args = array();
|
||||
|
||||
$blog_id = Connection_Manager::get_site_id( true );
|
||||
if ( $blog_id ) {
|
||||
$args = array( 'site' => $blog_id );
|
||||
}
|
||||
|
||||
return Admin_Menu::add_menu(
|
||||
__( 'Activity Log', 'jetpack-my-jetpack' ),
|
||||
_x( 'Activity Log', 'product name shown in menu', 'jetpack-my-jetpack' ) . ' <span class="dashicons dashicons-external"></span>',
|
||||
'manage_options',
|
||||
esc_url( Redirect::get_url( 'cloud-activity-log-wp-menu', $args ) ),
|
||||
null,
|
||||
1
|
||||
);
|
||||
}
|
||||
}
|
@ -19,8 +19,10 @@ use Automattic\Jetpack\Licensing;
|
||||
use Automattic\Jetpack\Modules;
|
||||
use Automattic\Jetpack\Plugins_Installer;
|
||||
use Automattic\Jetpack\Status;
|
||||
use Automattic\Jetpack\Status\Host as Status_Host;
|
||||
use Automattic\Jetpack\Terms_Of_Service;
|
||||
use Automattic\Jetpack\Tracking;
|
||||
use Jetpack;
|
||||
|
||||
/**
|
||||
* The main Initializer class that registers the admin menu and eneuque the assets.
|
||||
@ -32,13 +34,24 @@ class Initializer {
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PACKAGE_VERSION = '3.9.1';
|
||||
const PACKAGE_VERSION = '4.6.0';
|
||||
|
||||
/**
|
||||
* HTML container ID for the IDC screen on My Jetpack page.
|
||||
*/
|
||||
const IDC_CONTAINER_ID = 'my-jetpack-identity-crisis-container';
|
||||
|
||||
const JETPACK_PLUGIN_SLUGS = array(
|
||||
'jetpack-backup',
|
||||
'jetpack-boost',
|
||||
'zerobscrm',
|
||||
'jetpack',
|
||||
'jetpack-protect',
|
||||
'jetpack-social',
|
||||
'jetpack-videopress',
|
||||
'jetpack-search',
|
||||
);
|
||||
|
||||
/**
|
||||
* Initialize My Jetpack
|
||||
*
|
||||
@ -76,6 +89,12 @@ class Initializer {
|
||||
// Sets up JITMS.
|
||||
JITM::configure();
|
||||
|
||||
// Add "Activity Log" menu item.
|
||||
Activitylog::init();
|
||||
|
||||
// Add "Jetpack Manage" menu item.
|
||||
Jetpack_Manage::init();
|
||||
|
||||
/**
|
||||
* Fires after the My Jetpack package is initialized
|
||||
*
|
||||
@ -171,8 +190,10 @@ class Initializer {
|
||||
),
|
||||
'plugins' => Plugins_Installer::get_plugins(),
|
||||
'myJetpackUrl' => admin_url( 'admin.php?page=my-jetpack' ),
|
||||
'myJetpackCheckoutUri' => 'admin.php?page=my-jetpack',
|
||||
'topJetpackMenuItemUrl' => Admin_Menu::get_top_level_menu_item_url(),
|
||||
'siteSuffix' => ( new Status() )->get_site_suffix(),
|
||||
'blogID' => Connection_Manager::get_site_id( true ),
|
||||
'myJetpackVersion' => self::PACKAGE_VERSION,
|
||||
'myJetpackFlags' => self::get_my_jetpack_flags(),
|
||||
'fileSystemWriteAccess' => self::has_file_system_write_access(),
|
||||
@ -180,7 +201,16 @@ class Initializer {
|
||||
'adminUrl' => esc_url( admin_url() ),
|
||||
'IDCContainerID' => static::get_idc_container_id(),
|
||||
'userIsAdmin' => current_user_can( 'manage_options' ),
|
||||
'userIsNewToJetpack' => self::is_jetpack_user_new(),
|
||||
'isStatsModuleActive' => $modules->is_active( 'stats' ),
|
||||
'isUserFromKnownHost' => self::is_user_from_known_host(),
|
||||
'welcomeBanner' => array(
|
||||
'hasBeenDismissed' => \Jetpack_Options::get_option( 'dismissed_welcome_banner', false ),
|
||||
),
|
||||
'jetpackManage' => array(
|
||||
'isEnabled' => Jetpack_Manage::could_use_jp_manage(),
|
||||
'isAgencyAccount' => Jetpack_Manage::is_agency_account(),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
@ -202,6 +232,72 @@ class Initializer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current user is "new" to Jetpack
|
||||
* This is used to vary some messaging in My Jetpack
|
||||
*
|
||||
* On the front-end, purchases are also taken into account
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_jetpack_user_new() {
|
||||
// is the user connected?
|
||||
$connection = new Connection_Manager();
|
||||
if ( $connection->is_user_connected() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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() );
|
||||
}
|
||||
if ( ! empty( $active_modules ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check for other Jetpack plugins that are installed on the site (active or not)
|
||||
// If there's more than one Jetpack plugin active, this user is not "new"
|
||||
$plugin_slugs = array_keys( Plugins_Installer::get_plugins() );
|
||||
$plugin_slugs = array_map(
|
||||
static function ( $slug ) {
|
||||
$parts = explode( '/', $slug );
|
||||
if ( empty( $parts ) ) {
|
||||
return '';
|
||||
}
|
||||
// Return the last segment of the filepath without the PHP extension
|
||||
return str_replace( '.php', '', $parts[ count( $parts ) - 1 ] );
|
||||
},
|
||||
$plugin_slugs
|
||||
);
|
||||
$installed_jetpack_plugins = array_intersect( self::JETPACK_PLUGIN_SLUGS, $plugin_slugs );
|
||||
if ( is_countable( $installed_jetpack_plugins ) && count( $installed_jetpack_plugins ) >= 2 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Does the site have any purchases?
|
||||
$purchases = Wpcom_Products::get_site_current_purchases();
|
||||
if ( ! empty( $purchases ) && ! is_wp_error( $purchases ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the user has come from a host we can recognize.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function is_user_from_known_host() {
|
||||
// Known (external) host is the one that has been determined and is not dotcom.
|
||||
return ! in_array( ( new Status_Host() )->get_known_host_guess(), array( 'unknown', 'wpcom' ), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Build flags for My Jetpack UI
|
||||
*
|
||||
@ -246,6 +342,16 @@ class Initializer {
|
||||
'permission_callback' => __CLASS__ . '::permissions_callback',
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
'my-jetpack/v1',
|
||||
'site/dismiss-welcome-banner',
|
||||
array(
|
||||
'methods' => \WP_REST_Server::EDITABLE,
|
||||
'callback' => __CLASS__ . '::dismiss_welcome_banner',
|
||||
'permission_callback' => __CLASS__ . '::permissions_callback',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -300,6 +406,16 @@ class Initializer {
|
||||
return rest_ensure_response( $body, 200 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss the welcome banner.
|
||||
*
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
public static function dismiss_welcome_banner() {
|
||||
\Jetpack_Options::update_option( 'dismissed_welcome_banner', true );
|
||||
return rest_ensure_response( array( 'success' => true ), 200 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the site has file write access to the plugins folder, false otherwise.
|
||||
*
|
||||
|
@ -0,0 +1,124 @@
|
||||
<?php
|
||||
/**
|
||||
* Tools to manage things related to "Jetpack Manage"
|
||||
* - Add Jetpack Manage menu item.
|
||||
* - Check if user is an agency (used by the Jetpack Manage banner)
|
||||
*
|
||||
* @package automattic/my-jetpack
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\My_Jetpack;
|
||||
|
||||
use Automattic\Jetpack\Admin_UI\Admin_Menu;
|
||||
use Automattic\Jetpack\Connection\Client;
|
||||
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
|
||||
use Automattic\Jetpack\Redirect;
|
||||
|
||||
/**
|
||||
* Jetpack Manage features in My Jetpack.
|
||||
*/
|
||||
class Jetpack_Manage {
|
||||
/**
|
||||
* Initialize the class and hooks needed.
|
||||
*/
|
||||
public static function init() {
|
||||
add_action( 'admin_menu', array( self::class, 'add_submenu_jetpack' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* The page to be added to submenu
|
||||
*
|
||||
* @return void|null|string The resulting page's hook_suffix
|
||||
*/
|
||||
public static function add_submenu_jetpack() {
|
||||
// Do not display the menu if the user has < 2 sites.
|
||||
if ( ! self::could_use_jp_manage( 2 ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$args = array();
|
||||
|
||||
$blog_id = Connection_Manager::get_site_id( true );
|
||||
if ( $blog_id ) {
|
||||
$args = array( 'site' => $blog_id );
|
||||
}
|
||||
|
||||
return Admin_Menu::add_menu(
|
||||
__( 'Jetpack Manage', 'jetpack-my-jetpack' ),
|
||||
_x( 'Jetpack Manage', 'product name shown in menu', 'jetpack-my-jetpack' ) . ' <span class="dashicons dashicons-external"></span>',
|
||||
'manage_options',
|
||||
esc_url( Redirect::get_url( 'cloud-manage-dashboard-wp-menu', $args ) ),
|
||||
null,
|
||||
100
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user has enough sites to be able to use Jetpack Manage.
|
||||
*
|
||||
* @param int $min_sites Minimum number of sites to be able to use Jetpack Manage.
|
||||
*
|
||||
* @return bool Return true if the user has enough sites to be able to use Jetpack Manage.
|
||||
*/
|
||||
public static function could_use_jp_manage( $min_sites = 2 ) {
|
||||
// Only proceed if the user is connected to WordPress.com.
|
||||
if ( ! ( new Connection_Manager() )->is_user_connected() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do not display the menu if Jetpack plugin is not installed.
|
||||
if ( ! class_exists( 'Jetpack' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do not display the menu on Multisite.
|
||||
if ( is_multisite() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the user has the minimum number of sites.
|
||||
$user_data = ( new Connection_Manager() )->get_connected_user_data( get_current_user_id() );
|
||||
if ( ! isset( $user_data['site_count'] ) || $user_data['site_count'] < $min_sites ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user is a partner/agency.
|
||||
*
|
||||
* @return bool Return true if the user is a partner/agency, otherwise false.
|
||||
*/
|
||||
public static function is_agency_account() {
|
||||
// Only proceed if the user is connected to WordPress.com.
|
||||
if ( ! ( new Connection_Manager() )->is_user_connected() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the cached partner data.
|
||||
$partner = get_transient( 'jetpack_partner_data' );
|
||||
|
||||
if ( $partner === false ) {
|
||||
$wpcom_response = Client::wpcom_json_api_request_as_user( '/jetpack-partners' );
|
||||
|
||||
if ( 200 !== wp_remote_retrieve_response_code( $wpcom_response ) || is_wp_error( $wpcom_response ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$partner_data = json_decode( wp_remote_retrieve_body( $wpcom_response ) );
|
||||
|
||||
// The jetpack-partners endpoint will return only one partner data into an array, it uses Jetpack_Partner::find_by_owner.
|
||||
if ( ! is_array( $partner_data ) || count( $partner_data ) !== 1 || ! is_object( $partner_data[0] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$partner = $partner_data[0];
|
||||
|
||||
// Cache the partner data for 1 hour.
|
||||
set_transient( 'jetpack_partner_data', $partner, HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
return $partner->partner_type === 'agency';
|
||||
}
|
||||
}
|
@ -26,6 +26,7 @@ class Products {
|
||||
'backup' => Products\Backup::class,
|
||||
'boost' => Products\Boost::class,
|
||||
'crm' => Products\Crm::class,
|
||||
'creator' => Products\Creator::class,
|
||||
'extras' => Products\Extras::class,
|
||||
'jetpack-ai' => Products\Jetpack_Ai::class,
|
||||
'scan' => Products\Scan::class,
|
||||
|
@ -18,12 +18,23 @@ class REST_Product_Data {
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
// Get backup undo event
|
||||
register_rest_route(
|
||||
'my-jetpack/v1',
|
||||
'site/product-data',
|
||||
'/site/backup/undo-event',
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::get_all_product_data',
|
||||
'callback' => __CLASS__ . '::get_site_backup_undo_event',
|
||||
'permission_callback' => __CLASS__ . '::permissions_callback',
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
'my-jetpack/v1',
|
||||
'/site/backup/count-items',
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::count_things_that_can_be_backed_up',
|
||||
'permission_callback' => __CLASS__ . '::permissions_callback',
|
||||
)
|
||||
);
|
||||
@ -37,22 +48,100 @@ class REST_Product_Data {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the product data for all products
|
||||
* This will fetch the last rewindable event from the Activity Log and
|
||||
* the last rewind_id prior to that.
|
||||
*
|
||||
* @return array|WP_Error
|
||||
* @return array|WP_Error|null
|
||||
*/
|
||||
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 ) );
|
||||
public static function get_site_backup_undo_event() {
|
||||
$blog_id = \Jetpack_Options::get_option( 'id' );
|
||||
|
||||
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 ) );
|
||||
$response = Client::wpcom_json_api_request_as_user(
|
||||
'/sites/' . $blog_id . '/activity/rewindable?force=wpcom',
|
||||
'v2',
|
||||
array(),
|
||||
null,
|
||||
'wpcom'
|
||||
);
|
||||
|
||||
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return rest_ensure_response( $body, 200 );
|
||||
$body = json_decode( $response['body'], true );
|
||||
|
||||
if ( ! isset( $body['current'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Preparing the response structure
|
||||
$undo_event = array(
|
||||
'last_rewindable_event' => null,
|
||||
'undo_backup_id' => null,
|
||||
);
|
||||
|
||||
// List of events that will not be considered to be undo.
|
||||
// Basically we should not `undo` a full backup event, but we could
|
||||
// use them to undo any other action like plugin updates.
|
||||
$last_event_exceptions = array(
|
||||
'rewind__backup_only_complete_full',
|
||||
'rewind__backup_only_complete_initial',
|
||||
'rewind__backup_only_complete',
|
||||
'rewind__backup_complete_full',
|
||||
'rewind__backup_complete_initial',
|
||||
'rewind__backup_complete',
|
||||
);
|
||||
|
||||
// Looping through the events to find the last rewindable event and the last backup_id.
|
||||
// The idea is to find the last rewindable event and then the last rewind_id before that.
|
||||
$found_last_event = false;
|
||||
foreach ( $body['current']['orderedItems'] as $event ) {
|
||||
if ( $event['is_rewindable'] ) {
|
||||
if ( ! $found_last_event && ! in_array( $event['name'], $last_event_exceptions, true ) ) {
|
||||
$undo_event['last_rewindable_event'] = $event;
|
||||
$found_last_event = true;
|
||||
} elseif ( $found_last_event ) {
|
||||
$undo_event['undo_backup_id'] = $event['rewind_id'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rest_ensure_response( $undo_event, 200 );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public static function count_things_that_can_be_backed_up() {
|
||||
$image_mime_type = 'image';
|
||||
$video_mime_type = 'video';
|
||||
$audio_mime_type = 'audio';
|
||||
|
||||
$data = array();
|
||||
|
||||
// Add all post types together to get the total post count
|
||||
$data['total_post_count'] = array_sum( (array) wp_count_posts( 'post' ) );
|
||||
|
||||
// Add all page types together to get the total page count
|
||||
$data['total_page_count'] = array_sum( (array) wp_count_posts( 'page' ) );
|
||||
|
||||
// Add all comments together to get the total comment count
|
||||
$comments = (array) wp_count_comments();
|
||||
$data['total_comment_count'] = $comments ? $comments['total_comments'] : 0;
|
||||
|
||||
// Add all image attachments together to get the total image count
|
||||
$data['total_image_count'] = array_sum( (array) wp_count_attachments( $image_mime_type ) );
|
||||
|
||||
// Add all video attachments together to get the total video count
|
||||
$data['total_video_count'] = array_sum( (array) wp_count_attachments( $video_mime_type ) );
|
||||
|
||||
// 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 );
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,8 @@ class Wpcom_Products {
|
||||
*/
|
||||
const CACHE_META_NAME = 'my-jetpack-cache';
|
||||
|
||||
const MY_JETPACK_PURCHASES_TRANSIENT_KEY = 'my-jetpack-purchases';
|
||||
|
||||
/**
|
||||
* Fetches the list of products from WPCOM
|
||||
*
|
||||
@ -149,11 +151,12 @@ class Wpcom_Products {
|
||||
* Get one product
|
||||
*
|
||||
* @param string $product_slug The product slug.
|
||||
* @param bool $renew_cache A flag to force the cache to be renewed.
|
||||
*
|
||||
* @return ?Object The product details if found
|
||||
*/
|
||||
public static function get_product( $product_slug ) {
|
||||
$products = self::get_products();
|
||||
public static function get_product( $product_slug, $renew_cache = false ) {
|
||||
$products = self::get_products( $renew_cache );
|
||||
if ( ! empty( $products->$product_slug ) ) {
|
||||
return $products->$product_slug;
|
||||
}
|
||||
@ -232,18 +235,21 @@ class Wpcom_Products {
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
// Check for a cached value before doing lookup
|
||||
$stored_purchases = get_transient( self::MY_JETPACK_PURCHASES_TRANSIENT_KEY );
|
||||
if ( $stored_purchases !== false ) {
|
||||
return $stored_purchases;
|
||||
}
|
||||
|
||||
$site_id = Jetpack_Options::get_option( 'id' );
|
||||
|
||||
$response = Client::wpcom_json_api_request_as_blog(
|
||||
@ -259,6 +265,9 @@ class Wpcom_Products {
|
||||
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
$purchases = json_decode( $body );
|
||||
// Set short transient to help with repeated lookups on the same page load
|
||||
set_transient( self::MY_JETPACK_PURCHASES_TRANSIENT_KEY, $purchases, 5 );
|
||||
|
||||
return $purchases;
|
||||
}
|
||||
}
|
||||
|
@ -92,6 +92,38 @@ class Anti_Spam extends Product {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the site has an Akismet plan by checking for an API key
|
||||
*
|
||||
* @return bool - whether an API key was found
|
||||
*/
|
||||
public static function has_required_plan() {
|
||||
// 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 );
|
||||
|
||||
// Check for existing plans
|
||||
$purchases_data = Wpcom_Products::get_site_current_purchases();
|
||||
if ( is_wp_error( $purchases_data ) ) {
|
||||
return $fallback;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product princing details
|
||||
*
|
||||
|
@ -75,7 +75,11 @@ class Backup extends Hybrid_Product {
|
||||
* @return string
|
||||
*/
|
||||
public static function get_description() {
|
||||
return __( 'Save every change', 'jetpack-my-jetpack' );
|
||||
if ( static::is_active() ) {
|
||||
return __( 'Save every change', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
return __( 'Your site is not backed up', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -226,9 +230,9 @@ class Backup extends Hybrid_Product {
|
||||
*/
|
||||
public static function get_post_checkout_url() {
|
||||
if ( static::is_jetpack_plugin_active() ) {
|
||||
return admin_url( 'admin.php?page=jetpack#/recommendations' );
|
||||
return 'admin.php?page=jetpack#/recommendations';
|
||||
} elseif ( static::is_plugin_active() ) {
|
||||
return admin_url( 'admin.php?page=jetpack-backup' );
|
||||
return 'admin.php?page=jetpack-backup';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,358 @@
|
||||
<?php
|
||||
/**
|
||||
* Creator 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 Creator product
|
||||
*/
|
||||
class Creator extends Product {
|
||||
|
||||
const FREE_TIER_SLUG = 'free';
|
||||
const UPGRADED_TIER_SLUG = 'upgraded';
|
||||
const UPGRADED_TIER_PRODUCT_SLUG = 'jetpack_creator_yearly';
|
||||
|
||||
/**
|
||||
* The product slug
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $slug = 'creator';
|
||||
|
||||
/**
|
||||
* The slug of the plugin associated with this product - Creator functionalities are part of Jetpack's main plugin
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $plugin_slug = 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this product requires a user connection
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $requires_user_connection = false;
|
||||
|
||||
/**
|
||||
* Get the internationalized product name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return __( 'Creator', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internationalized product title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_title() {
|
||||
return __( 'Jetpack Creator', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internationalized product description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_description() {
|
||||
return __( 'Create, grow, and monetize your audience', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internationalized product long description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_long_description() {
|
||||
return __( 'Create, grow, and monetize your audience with powerful tools for creators.', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internationalized features list
|
||||
*
|
||||
* @return array Features list
|
||||
*/
|
||||
public static function get_features() {
|
||||
return array(
|
||||
__( 'Create content that stands out', 'jetpack-my-jetpack' ),
|
||||
__( 'Grow your subscribers through our creator network and tools', 'jetpack-my-jetpack' ),
|
||||
__( 'Monetize your online presence and earn from your website', 'jetpack-my-jetpack' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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' => __( 'Import subscribers', 'jetpack-my-jetpack' ),
|
||||
'info' => array(
|
||||
'content' => __(
|
||||
'Import a CSV file of your existing subscribers to be sent your Newsletter.',
|
||||
'jetpack-my-jetpack'
|
||||
),
|
||||
),
|
||||
'tiers' => array(
|
||||
self::FREE_TIER_SLUG => array(
|
||||
'included' => true,
|
||||
'description' => __( '100 subscribers', 'jetpack-my-jetpack' ),
|
||||
),
|
||||
self::UPGRADED_TIER_SLUG => array(
|
||||
'included' => true,
|
||||
'description' => __( 'Unlimited subscribers', 'jetpack-my-jetpack' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'name' => __( 'Transaction fees', 'jetpack-my-jetpack' ),
|
||||
'info' => array(
|
||||
'content' => __(
|
||||
'<p>Fees are only collected when you accept payments.</p>
|
||||
<p>Fees are based on the Jetpack plan you have and are calculated as a percentage of your revenue from 10% on the Free plan to 2% on the Creator plan (plus Stripe fees).</p>',
|
||||
'jetpack-my-jetpack'
|
||||
),
|
||||
),
|
||||
'tiers' => array(
|
||||
self::FREE_TIER_SLUG => array(
|
||||
'included' => true,
|
||||
'description' => __( '10%', 'jetpack-my-jetpack' ),
|
||||
),
|
||||
self::UPGRADED_TIER_SLUG => array(
|
||||
'included' => true,
|
||||
'description' => __( '2%', 'jetpack-my-jetpack' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'name' => __( 'Creator network', 'jetpack-my-jetpack' ),
|
||||
'info' => array(
|
||||
'content' => __(
|
||||
'<p>The creator network is the network of websites either hosted with WordPress.com or self-hosted and connected with Jetpack.</p>
|
||||
<p>Sites that are part of the creator network can gain exposure to new readers. Sites on the Creator plan have enhanced distribution to more areas of the Reader.</p>',
|
||||
'jetpack-my-jetpack'
|
||||
),
|
||||
),
|
||||
'tiers' => array(
|
||||
self::FREE_TIER_SLUG => array( 'included' => true ),
|
||||
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'name' => __( 'Jetpack Blocks', 'jetpack-my-jetpack' ),
|
||||
'info' => array(
|
||||
'content' => __(
|
||||
'Jetpack has over 40 Gutenberg blocks to help you with your content creation, such as displaying your podcasts, showing different content to repeat visitors, creating contact forms and many more.',
|
||||
'jetpack-my-jetpack'
|
||||
),
|
||||
),
|
||||
'tiers' => array(
|
||||
self::FREE_TIER_SLUG => array( 'included' => true ),
|
||||
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'name' => __( 'Paid content gating', 'jetpack-my-jetpack' ),
|
||||
'info' => array(
|
||||
'content' => __(
|
||||
'Lock your content behind a paid content block. To access the content, readers will need to pay a one-time fee or a recurring subscription.',
|
||||
'jetpack-my-jetpack'
|
||||
),
|
||||
),
|
||||
'tiers' => array(
|
||||
self::FREE_TIER_SLUG => array( 'included' => true ),
|
||||
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'name' => __( 'Paywall access', 'jetpack-my-jetpack' ),
|
||||
'info' => array(
|
||||
'content' => __(
|
||||
'Add a Paywall to your content which lets your visitors read a section of your content before being asked to subscribe to continue reading.',
|
||||
'jetpack-my-jetpack'
|
||||
),
|
||||
),
|
||||
'tiers' => array(
|
||||
self::FREE_TIER_SLUG => array( 'included' => true ),
|
||||
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'name' => __( 'Newsletter', 'jetpack-my-jetpack' ),
|
||||
'info' => array(
|
||||
'content' => __(
|
||||
'Start a Newsletter by sending your content as an email newsletter direct to your fans email inboxes.',
|
||||
'jetpack-my-jetpack'
|
||||
),
|
||||
),
|
||||
'tiers' => array(
|
||||
self::FREE_TIER_SLUG => array( 'included' => true ),
|
||||
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'name' => __( 'Pay with PayPal', 'jetpack-my-jetpack' ),
|
||||
'info' => array(
|
||||
'content' => __(
|
||||
'Accept payment with PayPal for simple payments like eBooks, courses and more.',
|
||||
'jetpack-my-jetpack'
|
||||
),
|
||||
),
|
||||
'tiers' => array(
|
||||
self::FREE_TIER_SLUG => array( 'included' => false ),
|
||||
self::UPGRADED_TIER_SLUG => array( 'included' => true ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'name' => __( 'WordAds', 'jetpack-my-jetpack' ),
|
||||
'info' => array(
|
||||
'content' => __(
|
||||
'<p>WordAds adds advertisements to your website. Start earning from your website traffic.</p>
|
||||
<p>Over 50 internet advertisers — including Google AdSense & Adx, AppNexus, Amazon A9, AOL Marketplace, Yahoo, Criteo, and more — bid to display ads in WordAds spots.</p>',
|
||||
'jetpack-my-jetpack'
|
||||
),
|
||||
),
|
||||
'tiers' => array(
|
||||
self::FREE_TIER_SLUG => array( 'included' => false ),
|
||||
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
|
||||
*
|
||||
* @return array Pricing details
|
||||
*/
|
||||
public static function get_pricing_for_ui() {
|
||||
return array(
|
||||
'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 )
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL where the user manages the product
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public static function get_manage_url() {
|
||||
return admin_url( 'admin.php?page=jetpack#/settings?term=creator' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the WPCOM product slug used to make the purchase
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public static function get_wpcom_product_slug() {
|
||||
return 'jetpack_creator_yearly';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the WPCOM product slug used to make the purchase
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public static function get_wpcom_biyearly_product_slug() {
|
||||
return 'jetpack_creator_bi_yearly';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the WPCOM monthly product slug used to make the purchase
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public static function get_wpcom_monthly_product_slug() {
|
||||
return 'jetpack_creator_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 ) {
|
||||
// Creator is available as standalone bundle and as part of the Complete plan.
|
||||
if ( strpos( $purchase->product_slug, 'jetpack_creator' ) !== false || str_starts_with( $purchase->product_slug, 'jetpack_complete' ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the product can be upgraded - i.e. this shows the /#add-creator interstitial
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_upgradable() {
|
||||
$has_required_plan = self::has_required_plan();
|
||||
return ! $has_required_plan;
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@
|
||||
|
||||
namespace Automattic\Jetpack\My_Jetpack\Products;
|
||||
|
||||
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
|
||||
use Automattic\Jetpack\My_Jetpack\Product;
|
||||
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
|
||||
|
||||
@ -74,6 +75,51 @@ class Jetpack_Ai extends Product {
|
||||
return __( 'Jetpack AI', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current usage tier
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function get_current_usage_tier() {
|
||||
if ( ! self::is_site_connected() ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$info = self::get_ai_assistant_feature();
|
||||
|
||||
// Bail early if it's not possible to fetch the feature data.
|
||||
if ( is_wp_error( $info ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$current_tier = isset( $info['current-tier']['value'] ) ? $info['current-tier']['value'] : null;
|
||||
|
||||
return $current_tier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next usage tier
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function get_next_usage_tier() {
|
||||
if ( ! self::is_site_connected() ) {
|
||||
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 ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Trust the next tier provided by the feature data.
|
||||
$next_tier = isset( $info['next-tier']['value'] ) ? $info['next-tier']['value'] : null;
|
||||
|
||||
return $next_tier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internationalized product description
|
||||
*
|
||||
@ -83,49 +129,164 @@ class Jetpack_Ai extends Product {
|
||||
return __( 'Experimental tool to add AI to your editor', 'jetpack-my-jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internationalized usage tier long description by tier
|
||||
*
|
||||
* @param int $tier The usage tier.
|
||||
* @return string
|
||||
*/
|
||||
public static function get_long_description_by_usage_tier( $tier ) {
|
||||
$long_descriptions = array(
|
||||
1 => __( 'Jetpack AI Assistant brings the power of AI right into your WordPress editor, letting your content creation soar to new heights.', 'jetpack-my-jetpack' ),
|
||||
100 => __( 'The most advanced AI technology Jetpack has to offer.', 'jetpack-my-jetpack' ),
|
||||
);
|
||||
$tiered_description = __( 'Upgrade and increase the amount of your available monthly requests to continue using the most advanced AI technology Jetpack has to offer.', 'jetpack-my-jetpack' );
|
||||
|
||||
return isset( $long_descriptions[ $tier ] ) ? $long_descriptions[ $tier ] : $tiered_description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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' );
|
||||
$next_tier = self::get_next_usage_tier();
|
||||
|
||||
return self::get_long_description_by_usage_tier( $next_tier );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internationalized usage tier features by tier
|
||||
*
|
||||
* @param int $tier The usage tier.
|
||||
* @return string
|
||||
*/
|
||||
public static function get_features_by_usage_tier( $tier ) {
|
||||
$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' ),
|
||||
),
|
||||
);
|
||||
|
||||
$tiered_features = array(
|
||||
__( 'Prompt based content generation', 'jetpack-my-jetpack' ),
|
||||
__( 'Generate text, tables, and lists', 'jetpack-my-jetpack' ),
|
||||
__( 'Adaptive tone adjustment', 'jetpack-my-jetpack' ),
|
||||
__( 'Superior spelling and grammar correction', 'jetpack-my-jetpack' ),
|
||||
__( 'Title & summary generation', 'jetpack-my-jetpack' ),
|
||||
__( 'Priority support', 'jetpack-my-jetpack' ),
|
||||
/* translators: %d is the number of requests. */
|
||||
sprintf( __( 'Up to %d requests per month', 'jetpack-my-jetpack' ), $tier ),
|
||||
);
|
||||
|
||||
return isset( $features[ $tier ] ) ? $features[ $tier ] : $tiered_features;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internationalized features list
|
||||
*
|
||||
* @return array CRM features list
|
||||
* @return array Jetpack AI 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' ),
|
||||
);
|
||||
$next_tier = self::get_next_usage_tier();
|
||||
|
||||
return self::get_features_by_usage_tier( $next_tier );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product princing details
|
||||
* Get the product pricing details by tier
|
||||
*
|
||||
* @param int $tier The usage tier.
|
||||
* @return array Pricing details
|
||||
*/
|
||||
public static function get_pricing_for_ui_by_usage_tier( $tier ) {
|
||||
|
||||
// Bail early if the site is not connected.
|
||||
if ( ! self::is_site_connected() ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$product = Wpcom_Products::get_product( static::get_wpcom_product_slug() );
|
||||
|
||||
if ( empty( $product ) ) {
|
||||
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;
|
||||
|
||||
/*
|
||||
* when tiers are enabled and the price tier list is empty,
|
||||
* we may need to renew the cache for the product data so
|
||||
* we get the new price tier list.
|
||||
*
|
||||
* if the list is still empty after the fresh data, we will
|
||||
* default to empty pricing (by returning an empty array).
|
||||
*/
|
||||
if ( empty( $product->price_tier_list ) && $tier_plans_enabled ) {
|
||||
$product = Wpcom_Products::get_product( static::get_wpcom_product_slug(), true );
|
||||
}
|
||||
|
||||
// get the base pricing for the unlimited plan, for compatibility
|
||||
$base_pricing = Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() );
|
||||
|
||||
$price_tier_list = $product->price_tier_list;
|
||||
$yearly_prices = array();
|
||||
|
||||
foreach ( $price_tier_list as $price_tier ) {
|
||||
if ( isset( $price_tier->maximum_units ) && isset( $price_tier->maximum_price ) ) {
|
||||
// The prices are in cents
|
||||
$yearly_prices[ $price_tier->maximum_units ] = $price_tier->maximum_price / 100;
|
||||
}
|
||||
}
|
||||
|
||||
// add the base pricing to the list
|
||||
$prices = array( 1 => $base_pricing );
|
||||
|
||||
foreach ( $yearly_prices as $units => $price ) {
|
||||
$prices[ $units ] = array_merge(
|
||||
$base_pricing,
|
||||
array(
|
||||
'full_price' => $price,
|
||||
'discount_price' => $price,
|
||||
'is_introductory_offer' => false,
|
||||
'introductory_offer' => null,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return isset( $prices[ $tier ] ) ? $prices[ $tier ] : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product pricing details
|
||||
*
|
||||
* @return array Pricing details
|
||||
*/
|
||||
public static function get_pricing_for_ui() {
|
||||
$next_tier = self::get_next_usage_tier();
|
||||
|
||||
return array_merge(
|
||||
array(
|
||||
'available' => true,
|
||||
'wpcom_product_slug' => static::get_wpcom_product_slug(),
|
||||
),
|
||||
Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() )
|
||||
self::get_pricing_for_ui_by_usage_tier( $next_tier )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the WPCOM product slug used to make the purchase
|
||||
*
|
||||
* @return ?string
|
||||
* @return string
|
||||
*/
|
||||
public static function get_wpcom_product_slug() {
|
||||
return 'jetpack_ai_yearly';
|
||||
@ -134,12 +295,21 @@ class Jetpack_Ai extends Product {
|
||||
/**
|
||||
* Get the WPCOM monthly product slug used to make the purchase
|
||||
*
|
||||
* @return ?string
|
||||
* @return string
|
||||
*/
|
||||
public static function get_wpcom_monthly_product_slug() {
|
||||
return 'jetpack_ai_monthly';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the WPCOM bi-yearly product slug used to make the purchase
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_wpcom_bi_yearly_product_slug() {
|
||||
return 'jetpack_ai_bi_yearly';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current plan (or purchases) of the site already supports the product
|
||||
*
|
||||
@ -152,10 +322,13 @@ class Jetpack_Ai extends Product {
|
||||
}
|
||||
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
|
||||
foreach ( $purchases_data as $purchase ) {
|
||||
if ( 0 === strpos( $purchase->product_slug, static::get_wpcom_product_slug() ) ) {
|
||||
if ( str_starts_with( $purchase->product_slug, static::get_wpcom_product_slug() ) ) {
|
||||
return true;
|
||||
}
|
||||
if ( 0 === strpos( $purchase->product_slug, static::get_wpcom_monthly_product_slug() ) ) {
|
||||
if ( str_starts_with( $purchase->product_slug, static::get_wpcom_monthly_product_slug() ) ) {
|
||||
return true;
|
||||
}
|
||||
if ( str_starts_with( $purchase->product_slug, static::get_wpcom_bi_yearly_product_slug() ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -163,6 +336,23 @@ class Jetpack_Ai extends Product {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the product can be upgraded to a different product.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_upgradable() {
|
||||
$has_required_plan = self::has_required_plan();
|
||||
$current_tier = self::get_current_usage_tier();
|
||||
|
||||
// Mark as not upgradable if user is on unlimited tier or does not have any plan.
|
||||
if ( ! $has_required_plan || null === $current_tier || 1 === $current_tier ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL where the user manages the product
|
||||
*
|
||||
@ -188,6 +378,11 @@ class Jetpack_Ai extends Product {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Bail early if the site is not connected.
|
||||
if ( ! self::is_site_connected() ) {
|
||||
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';
|
||||
@ -202,4 +397,13 @@ class Jetpack_Ai extends Product {
|
||||
|
||||
return \Jetpack_AI_Helper::get_ai_assistance_feature();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the site is connected to WordPress.com.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private static function is_site_connected() {
|
||||
return ( new Connection_Manager() )->is_connected();
|
||||
}
|
||||
}
|
||||
|
@ -220,7 +220,7 @@ class Scan extends Module_Product {
|
||||
*/
|
||||
public static function get_post_checkout_url() {
|
||||
if ( static::is_jetpack_plugin_active() ) {
|
||||
return admin_url( 'admin.php?page=jetpack#/recommendations' );
|
||||
return 'admin.php?page=jetpack#/recommendations';
|
||||
}
|
||||
|
||||
// If Jetpack is not active, it means that the user has another standalone plugin active
|
||||
|
@ -173,8 +173,8 @@ class Security extends Module_Product {
|
||||
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
|
||||
foreach ( $purchases_data as $purchase ) {
|
||||
if (
|
||||
0 === strpos( $purchase->product_slug, 'jetpack_security' ) ||
|
||||
0 === strpos( $purchase->product_slug, 'jetpack_complete' )
|
||||
str_starts_with( $purchase->product_slug, 'jetpack_security' ) ||
|
||||
str_starts_with( $purchase->product_slug, 'jetpack_complete' )
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
@ -124,7 +124,28 @@ class Social extends Hybrid_Product {
|
||||
* @return string
|
||||
*/
|
||||
public static function get_wpcom_product_slug() {
|
||||
return 'jetpack_social';
|
||||
return 'jetpack_social_basic_yearly';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -166,7 +166,7 @@ class Starter extends Module_Product {
|
||||
}
|
||||
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
|
||||
foreach ( $purchases_data as $purchase ) {
|
||||
if ( 0 === strpos( $purchase->product_slug, 'jetpack_starter' ) ) {
|
||||
if ( str_starts_with( $purchase->product_slug, 'jetpack_starter' ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,20 @@ class Stats extends Module_Product {
|
||||
*/
|
||||
public static $module_name = 'stats';
|
||||
|
||||
/**
|
||||
* The Plugin slug associated with stats
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public static $plugin_slug = self::JETPACK_PLUGIN_SLUG;
|
||||
|
||||
/**
|
||||
* The Plugin file associated with stats
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public static $plugin_filename = self::JETPACK_PLUGIN_FILENAME;
|
||||
|
||||
/**
|
||||
* Get the internationalized product name
|
||||
*
|
||||
@ -72,32 +86,29 @@ class Stats extends Module_Product {
|
||||
*/
|
||||
public static function get_features() {
|
||||
return array(
|
||||
__( 'Instant access to upcoming features', 'jetpack-my-jetpack' ),
|
||||
__( 'Real-time data on visitors', 'jetpack-my-jetpack' ),
|
||||
__( 'Traffic stats and trends for post and pages', 'jetpack-my-jetpack' ),
|
||||
__( 'Detailed statistics about links leading to your site', 'jetpack-my-jetpack' ),
|
||||
__( 'GDPR compliant', 'jetpack-my-jetpack' ),
|
||||
__( 'Access to upcoming advanced features', 'jetpack-my-jetpack' ),
|
||||
__( 'Priority support', 'jetpack-my-jetpack' ),
|
||||
__( 'Commercial use', 'jetpack-my-jetpack' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product princing details
|
||||
* Get the product pricing details
|
||||
* Only showing the pricing details for the commercial product
|
||||
*
|
||||
* @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(),
|
||||
'available' => true,
|
||||
'wpcom_product_slug' => static::get_wpcom_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',
|
||||
)
|
||||
Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() )
|
||||
);
|
||||
}
|
||||
|
||||
@ -107,7 +118,7 @@ class Stats extends Module_Product {
|
||||
* @return ?string
|
||||
*/
|
||||
public static function get_wpcom_product_slug() {
|
||||
return 'jetpack_stats_monthly';
|
||||
return 'jetpack_stats_yearly';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -140,10 +151,10 @@ class Stats extends Module_Product {
|
||||
}
|
||||
if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
|
||||
foreach ( $purchases_data as $purchase ) {
|
||||
if ( 0 === strpos( $purchase->product_slug, 'jetpack_stats' ) ) {
|
||||
if ( str_starts_with( $purchase->product_slug, 'jetpack_stats' ) ) {
|
||||
return true;
|
||||
}
|
||||
if ( 0 === strpos( $purchase->product_slug, 'jetpack_complete' ) ) {
|
||||
if ( str_starts_with( $purchase->product_slug, 'jetpack_complete' ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -162,23 +173,36 @@ class Stats extends Module_Product {
|
||||
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
|
||||
// For now, only the free and commercial tiered subs show as upgradable
|
||||
$upgradeable_stats_purchases = array_filter(
|
||||
$purchases_data,
|
||||
static function ( $purchase ) {
|
||||
// Free plan is upgradeable
|
||||
if ( $purchase->product_slug === 'jetpack_stats_free_yearly' ) {
|
||||
return true;
|
||||
// Commercial plans are upgradeable if they have a tier
|
||||
} elseif (
|
||||
in_array(
|
||||
$purchase->product_slug,
|
||||
array( 'jetpack_stats_yearly', 'jetpack_stats_monthly', 'jetpack_stats_bi_yearly' ),
|
||||
true
|
||||
) &&
|
||||
! empty( $purchase->current_price_tier_slug )
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return ! empty( $upgradeable_stats_purchases );
|
||||
}
|
||||
return true;
|
||||
|
||||
// If there are no plans found, don't consider the product as upgradeable
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -195,9 +219,18 @@ class Stats extends Module_Product {
|
||||
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() )
|
||||
str_starts_with( $purchase->product_slug, static::get_wpcom_free_product_slug() )
|
||||
) {
|
||||
return '&productType=personal';
|
||||
} elseif (
|
||||
in_array(
|
||||
$purchase->product_slug,
|
||||
array( 'jetpack_stats_yearly', 'jetpack_stats_monthly', 'jetpack_stats_bi_yearly' ),
|
||||
true
|
||||
) &&
|
||||
! empty( $purchase->current_price_tier_slug )
|
||||
) {
|
||||
return '&productType=commercial';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
namespace Automattic\Jetpack\My_Jetpack\Products;
|
||||
|
||||
use Automattic\Jetpack\Current_Plan;
|
||||
use Automattic\Jetpack\My_Jetpack\Hybrid_Product;
|
||||
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
|
||||
|
||||
@ -163,7 +164,7 @@ class Videopress extends Hybrid_Product {
|
||||
* @return boolean
|
||||
*/
|
||||
public static function has_required_plan() {
|
||||
// TODO: import and perform a proper check with Current_Plan. See #33410.
|
||||
return true;
|
||||
// using second argument `true` to force fetching from wpcom
|
||||
return Current_Plan::supports( 'videopress-1tb-storage', true );
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user