updated plugin Jetpack Protect version 2.1.0

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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