updated plugin Jetpack Protect version 1.1.1

This commit is contained in:
2022-11-24 13:40:35 +00:00
committed by Gitium
parent 9bf96ad952
commit 5284e32c67
126 changed files with 6115 additions and 1644 deletions

View File

@ -15,6 +15,7 @@ use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Connection\Rest_Authentication as Connection_Rest_Authentication;
use Automattic\Jetpack\JITMS\JITM as JITM;
use Automattic\Jetpack\Licensing;
use Automattic\Jetpack\Plugins_Installer;
use Automattic\Jetpack\Status as Status;
use Automattic\Jetpack\Terms_Of_Service;
use Automattic\Jetpack\Tracking;
@ -29,7 +30,7 @@ class Initializer {
*
* @var string
*/
const PACKAGE_VERSION = '2.0.3';
const PACKAGE_VERSION = '2.4.0';
/**
* Initialize My Jetapack
@ -159,6 +160,7 @@ class Initializer {
'purchases' => array(
'items' => array(),
),
'plugins' => Plugins_Installer::get_plugins(),
'myJetpackUrl' => admin_url( 'admin.php?page=my-jetpack' ),
'topJetpackMenuItemUrl' => Admin_Menu::get_top_level_menu_item_url(),
'siteSuffix' => ( new Status() )->get_site_suffix(),

View File

@ -17,22 +17,49 @@ class Products {
*
* Here's where all the existing Products are registered
*
* @throws \Exception If the result of a filter has invalid classes.
* @return array List of class names
*/
public static function get_products_classes() {
return array(
Products\Anti_Spam::class,
Products\Backup::class,
Products\Boost::class,
Products\Crm::class,
Products\Extras::class,
Products\Scan::class,
Products\Search::class,
Products\Social::class,
Products\Security::class,
Products\Protect::class,
Products\Videopress::class,
$classes = array(
'anti-spam' => Products\Anti_Spam::class,
'backup' => Products\Backup::class,
'boost' => Products\Boost::class,
'crm' => Products\Crm::class,
'extras' => Products\Extras::class,
'scan' => Products\Scan::class,
'search' => Products\Search::class,
'social' => Products\Social::class,
'security' => Products\Security::class,
'protect' => Products\Protect::class,
'videopress' => Products\Videopress::class,
);
/**
* This filter allows plugin to override the Product class of a given product. The new class must be a child class of the default one declared in My Jetpack
*
* For example, a stand-alone plugin could overwrite its product class to control specific behavior of the product in the My Jetpack page after it is active without having to commit changes to the My Jetpack package:
*
* add_filter( 'my_jetpack_products_classes', function( $classes ) {
* $classes['my_plugin'] = 'My_Plugin'; // a class that extends the original one declared in the My Jetpack package.
* return $classes
* } );
*
* @param array $classes An array where the keys are the product slugs and the values are the class names.
*/
$final_classes = apply_filters( 'my_jetpack_products_classes', $classes );
// Check that the classes are still child of the same original classes.
foreach ( (array) $final_classes as $slug => $final_class ) {
if ( $final_class === $classes[ $slug ] ) {
continue;
}
if ( ! class_exists( $final_class ) || ! is_subclass_of( $final_class, $classes[ $slug ] ) ) {
throw new \Exception( 'You can only overwrite a Product class with a child of the original class.' );
}
}
return $final_classes;
}
/**
@ -57,13 +84,24 @@ class Products {
* @return ?array
*/
public static function get_product( $product_slug ) {
foreach ( self::get_products_classes() as $class ) {
$p_slug = $class::$slug;
if ( $p_slug === $product_slug ) {
return $class::get_info();
}
$classes = self::get_products_classes();
if ( isset( $classes[ $product_slug ] ) ) {
return $classes[ $product_slug ]::get_info();
}
}
/**
* Get one product Class name
*
* @param string $product_slug The product slug.
*
* @return ?string
*/
public static function get_product_class( $product_slug ) {
$classes = self::get_products_classes();
if ( isset( $classes[ $product_slug ] ) ) {
return $classes[ $product_slug ];
}
return null;
}
/**
@ -72,11 +110,7 @@ class Products {
* @return array Product slugs array.
*/
public static function get_products_slugs() {
$slugs = array();
foreach ( self::get_products_classes() as $class ) {
$slugs[] = $class::$slug;
}
return $slugs;
return array_keys( self::get_products_classes() );
}
/**
@ -118,7 +152,7 @@ class Products {
'status' => array(
'title' => 'The product status',
'type' => 'string',
'enum' => array( 'active', 'inactive', 'plugin_absent', 'needs_purchase', 'error' ),
'enum' => array( 'active', 'inactive', 'plugin_absent', 'needs_purchase', 'needs_purchase_or_free', 'error' ),
),
'class' => array(
'title' => 'The product class handler',
@ -133,12 +167,16 @@ class Products {
* tied to the Products.
*/
public static function extend_plugins_action_links() {
Products\Backup::extend_plugin_action_links();
Products\Boost::extend_plugin_action_links();
Products\Crm::extend_plugin_action_links();
// Extend Jetpack plugin using Videopress instance.
Products\Videopress::extend_plugin_action_links();
$products = array(
'backup',
'boost',
'crm',
'videopress', // we use videopress here to add the plugin action to the Jetpack plugin itself
);
foreach ( $products as $product ) {
$class_name = self::get_product_class( $product );
$class_name::extend_plugin_action_links();
}
}
}

View File

@ -157,18 +157,21 @@ class Wpcom_Products {
return array();
}
$cost = $product->cost;
$discount_price = $cost;
$cost = $product->cost;
$discount_price = $cost;
$is_introductory_offer = false;
// Get/compute the discounted price.
if ( isset( $product->introductory_offer->cost_per_interval ) ) {
$discount_price = $product->introductory_offer->cost_per_interval;
$discount_price = $product->introductory_offer->cost_per_interval;
$is_introductory_offer = true;
}
$pricing = array(
'currency_code' => $product->currency_code,
'full_price' => $cost,
'discount_price' => $discount_price,
'currency_code' => $product->currency_code,
'full_price' => $cost,
'discount_price' => $discount_price,
'is_introductory_offer' => $is_introductory_offer,
);
return self::populate_with_discount( $product, $pricing, $discount_price );

View File

@ -76,7 +76,6 @@ abstract class Hybrid_Product extends Product {
/*
* Otherwise, activate Jetpack plugin.
* Silent mode True to avoid redirects.
*/
if ( static::is_jetpack_plugin_installed() ) {
return activate_plugin( static::get_installed_plugin_filename( 'jetpack' ) );
@ -95,7 +94,7 @@ abstract class Hybrid_Product extends Product {
if ( is_wp_error( $product_activation ) ) {
// If we failed to install the stand-alone plugin because the package was not found, let's try and install Jetpack plugin instead.
// This might happens, for example, while the stand-alone plugin was not released to the WP.org repository yet.
// This might happen, for example, while the stand-alone plugin was not released to the WP.org repository yet.
if ( 'no_package' === $product_activation->get_error_code() ) {
$product_activation = Plugins_Installer::install_plugin( self::JETPACK_PLUGIN_SLUG );
if ( ! is_wp_error( $product_activation ) ) {

View File

@ -222,6 +222,19 @@ abstract class Product {
return true;
}
/**
* Checks whether the product supports trial or not
*
* Returns true if it supports. Return false otherwise.
*
* Free products will always return false.
*
* @return boolean
*/
public static function has_trial_support() {
return false;
}
/**
* Checks whether product is a bundle.
*
@ -266,11 +279,19 @@ abstract class Product {
// We only consider missing user connection an error when the Product is active.
if ( static::$requires_user_connection && ! ( new Connection_Manager() )->has_connected_owner() ) {
$status = 'error';
} elseif ( ! static::has_required_plan() ) {
$status = 'needs_purchase'; // We need needs_purchase here as well because some products we consider active without the required plan.
} elseif ( ! static::has_required_plan() ) { // We need needs_purchase here as well because some products we consider active without the required plan.
if ( static::has_trial_support() ) {
$status = 'needs_purchase_or_free';
} else {
$status = 'needs_purchase';
}
}
} elseif ( ! static::has_required_plan() ) {
$status = 'needs_purchase';
if ( static::has_trial_support() ) {
$status = 'needs_purchase_or_free';
} else {
$status = 'needs_purchase';
}
} else {
$status = 'inactive';
}

View File

@ -14,9 +14,28 @@ use Jetpack_Options;
* Search stats (e.g. post count, post type breakdown)
*/
class Search_Stats {
const CACHE_EXPIRY = 5 * MINUTE_IN_SECONDS;
const CACHE_GROUP = 'jetpack_search';
const COUNT_ESTIMATE_CACHE_KEY = 'count_estimate';
const EXCLUDED_POST_TYPES = array(
'elementor_library', // Used by Elementor.
'jp_sitemap', // Used by Jetpack.
'revision',
'vip-legacy-redirect',
'scheduled-action',
'nbcs_video_lookup',
'reply', // bbpress, these get included in the topic
'product_variation', // woocommerce, not really public
'nav_menu_item',
'shop_order', // woocommerce, not really public
'redirect_rule', // Used by the Safe Redirect plugin.
);
const DO_NOT_EXCLUDE_POST_TYPES = array(
'topic', // bbpress
'forum', // bbpress
);
const CACHE_EXPIRY = 1 * MINUTE_IN_SECONDS;
const CACHE_GROUP = 'jetpack_search';
const POST_TYPE_BREAKDOWN_CACHE_KEY = 'post_type_break_down';
/**
* Get stats from the WordPress.com API for the current blog ID.
@ -43,47 +62,89 @@ class Search_Stats {
* Estimate record counts via a local database query.
*/
public static function estimate_count() {
$cached_value = wp_cache_get( self::COUNT_ESTIMATE_CACHE_KEY, self::CACHE_GROUP );
if ( false !== $cached_value ) {
return $cached_value;
}
return array_sum( static::get_post_type_breakdown() );
}
global $wpdb;
$indexable_statuses = get_post_stati( array( 'public' => true ) );
$unindexable_post_types = array_merge(
// Explicitly exclude various post types registered by plugins.
/**
* Calculate breakdown of post types for the site.
*/
public static function get_post_type_breakdown() {
$indexable_post_types = get_post_types(
array(
'elementor_library', // Used by Elementor.
'jp_sitemap', // Used by Jetpack.
'product_variation', // Used by Woocommerce.
'redirect_rule', // Used by the Safe Redirect plugin.
'reply', // Used by bbpress.
'scheduled-action', // Used by Woocommerce.
),
get_post_types(
array(
'exclude_from_search' => true,
'public' => false,
),
'names',
'or'
'public' => true,
'exclude_from_search' => false,
)
);
$prep_for_query = function ( $string ) use ( $wpdb ) {
// phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.QuotedSimplePlaceholder -- This is used to sanitize post type names.
return $wpdb->prepare( "'%s'", $string );
};
$statuses_list = implode( ',', array_map( $prep_for_query, $indexable_statuses ) );
$post_types_list = implode( ',', array_map( $prep_for_query, $unindexable_post_types ) );
$count = (int) $wpdb->get_var(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- This is properly prepared, but the query is constructed using variables.
"SELECT COUNT(ID) FROM {$wpdb->posts} WHERE post_status IN ($statuses_list) AND post_type NOT IN ($post_types_list)"
$indexable_status_array = get_post_stati(
array(
'public' => true,
'exclude_from_search' => false,
)
);
$raw_posts_counts = static::get_raw_post_type_breakdown();
if ( ! $raw_posts_counts || is_wp_error( $raw_posts_counts ) ) {
return array();
}
$posts_counts = static::get_post_type_breakdown_with( $raw_posts_counts, $indexable_post_types, $indexable_status_array );
wp_cache_set( self::COUNT_ESTIMATE_CACHE_KEY, $count, self::CACHE_GROUP, self::CACHE_EXPIRY );
return $count;
return $posts_counts;
}
/**
* Calculate breakdown of post types with passed in indexable post types and statuses.
* The function is going to be used from WPCOM as well for consistency.
*
* @param array $raw_posts_counts Array of post types with counts as value.
* @param array $indexable_post_types Array of indexable post types.
* @param array $indexable_status_array Array of indexable post statuses.
*/
public static function get_post_type_breakdown_with( $raw_posts_counts, $indexable_post_types, $indexable_status_array ) {
$posts_counts = array();
foreach ( $raw_posts_counts as $row ) {
// ignore if status is not public.
if ( ! in_array( $row['post_status'], $indexable_status_array, true ) ) {
continue;
}
// ignore if post type is in excluded post types.
if ( in_array( $row['post_type'], self::EXCLUDED_POST_TYPES, true ) ) {
continue;
}
// ignore if post type is not public and is not explicitly included.
if ( ! in_array( $row['post_type'], $indexable_post_types, true ) &&
! in_array( $row['post_type'], self::DO_NOT_EXCLUDE_POST_TYPES, true )
) {
continue;
}
// add up post type counts of potentially multiple post_status.
if ( ! isset( $posts_counts[ $row['post_type'] ] ) ) {
$posts_counts[ $row['post_type'] ] = 0;
}
$posts_counts[ $row['post_type'] ] += intval( $row['num_posts'] );
}
arsort( $posts_counts, SORT_NUMERIC );
return $posts_counts;
}
/**
* Get raw post type breakdown from the database.
*/
protected static function get_raw_post_type_breakdown() {
global $wpdb;
$results = wp_cache_get( self::POST_TYPE_BREAKDOWN_CACHE_KEY, self::CACHE_GROUP );
if ( false !== $results ) {
return $results;
}
$query = "SELECT post_type, post_status, COUNT( * ) AS num_posts
FROM {$wpdb->posts}
WHERE post_password = ''
GROUP BY post_type, post_status";
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery
$results = $wpdb->get_results( $query, ARRAY_A );
wp_cache_set( self::POST_TYPE_BREAKDOWN_CACHE_KEY, $results, self::CACHE_GROUP, self::CACHE_EXPIRY );
return $results;
}
}

View File

@ -103,7 +103,7 @@ class Search extends Hybrid_Product {
return array(
__( 'Instant search and indexing', 'jetpack-my-jetpack' ),
__( 'Powerful filtering', 'jetpack-my-jetpack' ),
__( 'Supports 29 languages', 'jetpack-my-jetpack' ),
__( 'Supports 38 languages', 'jetpack-my-jetpack' ),
__( 'Spelling correction', 'jetpack-my-jetpack' ),
);
}
@ -117,8 +117,10 @@ class Search extends Hybrid_Product {
// Basic pricing info.
$pricing = array_merge(
array(
'available' => true,
'wpcom_product_slug' => static::get_wpcom_product_slug(),
'available' => true,
'trial_available' => static::has_trial_support(),
'wpcom_product_slug' => static::get_wpcom_product_slug(),
'wpcom_free_product_slug' => static::get_wpcom_free_product_slug(),
),
Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() )
);
@ -130,6 +132,8 @@ class Search extends Hybrid_Product {
return $pricing;
}
$pricing['estimated_record_count'] = $record_count;
return array_merge( $pricing, $search_pricing );
}
@ -142,6 +146,41 @@ class Search extends Hybrid_Product {
return 'jetpack_search';
}
/**
* Get the WPCOM free product slug
*
* @return ?string
*/
public static function get_wpcom_free_product_slug() {
return 'jetpack_search_free';
}
/**
* Returns true if the new_pricing_202208 is set to not empty in URL for testing purpose, or it's active.
*/
public static function is_new_pricing_202208() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
if ( isset( $_GET['new_pricing_202208'] ) && $_GET['new_pricing_202208'] ) {
return true;
}
$record_count = intval( Search_Stats::estimate_count() );
$search_pricing = static::get_pricing_from_wpcom( $record_count );
if ( is_wp_error( $search_pricing ) ) {
return false;
}
return '202208' === $search_pricing['pricing_version'];
}
/**
* Override status to `needs_purchase_or_free` when status is `needs_purchase`.
*/
public static function get_status() {
$status = parent::get_status();
return $status;
}
/**
* Use centralized Search pricing API.
*
@ -158,10 +197,22 @@ class Search extends Hybrid_Product {
return $pricings[ $record_count ];
}
$response = wp_remote_get(
sprintf( Constants::get_constant( 'JETPACK__WPCOM_JSON_API_BASE' ) . '/wpcom/v2/jetpack-search/pricing?record_count=%1$d&locale=%2$s', $record_count, get_user_locale() ),
array( 'timeout' => 5 )
);
if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
// For simple sites fetch the response directly.
$response = Client::wpcom_json_api_request_as_blog(
sprintf( '/jetpack-search/pricing?record_count=%1$d&locale=%2$s', $record_count, get_user_locale() ),
'2',
array( 'timeout' => 5 ),
null,
'wpcom'
);
} else {
// For non-simple sites we have to use the wp_remote_get, as connection might not be available.
$response = wp_remote_get(
sprintf( Constants::get_constant( 'JETPACK__WPCOM_JSON_API_BASE' ) . '/wpcom/v2/jetpack-search/pricing?record_count=%1$d&locale=%2$s', $record_count, get_user_locale() ),
array( 'timeout' => 5 )
);
}
if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
return new WP_Error( 'search_pricing_fetch_failed' );
@ -205,6 +256,19 @@ class Search extends Hybrid_Product {
return $status;
}
/**
* Checks whether the product supports trial or not
*
* Returns true if it supports. Return false otherwise.
*
* Free products will always return false.
*
* @return boolean
*/
public static function has_trial_support() {
return static::is_new_pricing_202208();
}
/**
* Checks whether the current plan of the site already supports the product
*

View File

@ -7,13 +7,13 @@
namespace Automattic\Jetpack\My_Jetpack\Products;
use Automattic\Jetpack\My_Jetpack\Module_Product;
use Automattic\Jetpack\My_Jetpack\Hybrid_Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
/**
* Class responsible for handling the VideoPress product
*/
class Videopress extends Module_Product {
class Videopress extends Hybrid_Product {
/**
* The product slug
@ -29,6 +29,31 @@ class Videopress extends Module_Product {
*/
public static $module_name = 'videopress';
/**
* The slug of the plugin associated with this product.
*
* @var string
*/
public static $plugin_slug = 'jetpack-videopress';
/**
* The filename (id) of the plugin associated with this product.
*
* @var string
*/
public static $plugin_filename = array(
'jetpack-videopress/jetpack-videopress.php',
'videopress/jetpack-videopress.php',
'jetpack-videopress-dev/jetpack-videopress.php',
);
/**
* Search only requires site connection
*
* @var boolean
*/
public static $requires_user_connection = true;
/**
* Get the internationalized product name
*
@ -118,8 +143,11 @@ class Videopress extends Module_Product {
* @return ?string
*/
public static function get_manage_url() {
if ( static::is_active() ) {
if ( method_exists( 'Automattic\Jetpack\VideoPress\Initializer', 'should_initialize_admin_ui' ) && \Automattic\Jetpack\VideoPress\Initializer::should_initialize_admin_ui() ) {
return \Automattic\Jetpack\VideoPress\Admin_UI::get_admin_page_url();
} else {
return admin_url( 'admin.php?page=jetpack#/settings?term=videopress' );
}
}
}