diff options
Diffstat (limited to 'plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src')
20 files changed, 3457 insertions, 11 deletions
diff --git a/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/class-initializer.php b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/class-initializer.php index 94f18ca4..03dac442 100644 --- a/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/class-initializer.php +++ b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/class-initializer.php @@ -9,7 +9,14 @@ namespace Automattic\Jetpack\My_Jetpack; use Automattic\Jetpack\Admin_UI\Admin_Menu; use Automattic\Jetpack\Assets; +use Automattic\Jetpack\Connection\Client as Client; use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State; +use Automattic\Jetpack\Connection\Manager as Connection_Manager; +use Automattic\Jetpack\Connection\Rest_Authentication as Connection_Rest_Authentication; +use Automattic\Jetpack\Licensing; +use Automattic\Jetpack\Status as Status; +use Automattic\Jetpack\Terms_Of_Service; +use Automattic\Jetpack\Tracking; /** * The main Initializer class that registers the admin menu and eneuque the assets. @@ -17,20 +24,35 @@ use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State; class Initializer { /** + * My Jetpack package version + * + * @var string + */ + const PACKAGE_VERSION = '1.6.0'; + + /** * Initialize My Jetapack * * @return void */ public static function init() { - if ( did_action( 'my_jetpack_init' ) ) { + if ( ! self::should_initialize() || did_action( 'my_jetpack_init' ) ) { return; } - // Feature flag while we are developing it. - if ( ! defined( 'JETPACK_ENABLE_MY_JETPACK' ) || ! JETPACK_ENABLE_MY_JETPACK ) { - return; + // Extend jetpack plugins action links. + Products::extend_plugins_action_links(); + + // Set up the REST authentication hooks. + Connection_Rest_Authentication::init(); + + if ( self::is_licensing_ui_enabled() ) { + Licensing::instance()->initialize(); } + // Add custom WP REST API endoints. + add_action( 'rest_api_init', array( __CLASS__, 'register_rest_endpoints' ) ); + $page_suffix = Admin_Menu::add_menu( __( 'My Jetpack', 'jetpack-my-jetpack' ), __( 'My Jetpack', 'jetpack-my-jetpack' ), @@ -45,26 +67,63 @@ class Initializer { /** * Fires after the My Jetpack package is initialized * - * @since $$next_version$$ + * @since 0.1.0 */ do_action( 'my_jetpack_init' ); } /** + * Acts as a feature flag, returning a boolean for whether we should show the licensing UI. + * + * @since 1.2.0 + * + * @return boolean + */ + public static function is_licensing_ui_enabled() { + /** + * Acts as a feature flag, returning a boolean for whether we should show the licensing UI. + * + * @param bool $is_enabled Defaults to true. + * + * @since 1.2.0 + * @since 1.5.0 Update default value to true. + */ + return apply_filters( + 'jetpack_my_jetpack_should_enable_add_license_screen', + true + ); + } + + /** * Callback for the load my jetpack page hook. * * @return void */ public static function admin_init() { - add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_scritps' ) ); + add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ) ); + // Product statuses are constantly changing, so we never want to cache the page. + header( 'Cache-Control: no-cache, no-store, must-revalidate' ); + header( 'Pragma: no-cache' ); + header( 'Expires: 0' ); } /** + * Returns whether we are in condition to track to use + * Analytics functionality like Tracks, MC, or GA. + */ + public static function can_use_analytics() { + $status = new Status(); + $connection = new Connection_Manager(); + $tracking = new Tracking( 'jetpack', $connection ); + + return $tracking->should_enable_tracking( new Terms_Of_Service(), $status ); + } + /** * Enqueue admin page assets. * * @return void */ - public static function enqueue_scritps() { + public static function enqueue_scripts() { Assets::register_script( 'my_jetpack_main_app', '../build/index.js', @@ -79,23 +138,165 @@ class Initializer { 'my_jetpack_main_app', 'myJetpackInitialState', array( - 'apiRoot' => esc_url_raw( rest_url() ), - 'apiNonce' => wp_create_nonce( 'wp_rest' ), - 'redirectUrl' => admin_url( '?page=my-jetpack' ), + 'products' => array( + 'items' => Products::get_products(), + ), + 'purchases' => array( + 'items' => array(), + ), + 'redirectUrl' => admin_url( 'admin.php?page=my-jetpack' ), 'topJetpackMenuItemUrl' => Admin_Menu::get_top_level_menu_item_url(), + 'siteSuffix' => ( new Status() )->get_site_suffix(), + 'myJetpackVersion' => self::PACKAGE_VERSION, + 'fileSystemWriteAccess' => self::has_file_system_write_access(), + 'loadAddLicenseScreen' => self::is_licensing_ui_enabled(), + 'adminUrl' => esc_url( admin_url() ), + ) + ); + + wp_localize_script( + 'my_jetpack_main_app', + 'myJetpackRest', + array( + 'apiRoot' => esc_url_raw( rest_url() ), + 'apiNonce' => wp_create_nonce( 'wp_rest' ), ) ); // Connection Initial State. wp_add_inline_script( 'my_jetpack_main_app', Connection_Initial_State::render(), 'before' ); + + // Required for Analytics. + if ( self::can_use_analytics() ) { + Tracking::register_tracks_functions_scripts( true ); + } } /** - * Echos the admin page content. + * Echoes the admin page content. * * @return void */ public static function admin_page() { echo '<div id="my-jetpack-container"></div>'; } + + /** + * Register the REST API routes. + * + * @return void + */ + public static function register_rest_endpoints() { + new REST_Products(); + new REST_Purchases(); + + register_rest_route( + 'my-jetpack/v1', + 'site', + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::get_site', + 'permission_callback' => __CLASS__ . '::permissions_callback', + ) + ); + } + + /** + * Check user capability to access the endpoint. + * + * @access public + * @static + * + * @return true|WP_Error + */ + public static function permissions_callback() { + return current_user_can( 'manage_options' ); + } + + /** + * Return true if we should initialize the My Jetpack admin page. + */ + public static function should_initialize() { + $should = true; + + if ( is_multisite() ) { + $should = false; + } + + // Do not initialize My Jetpack if site is not connected. + if ( ! ( new Connection_Manager() )->is_connected() ) { + $should = false; + } + + /** + * Allows filtering whether My Jetpack should be initialized. + * + * @since 0.5.0-alpha + * + * @param bool $shoud_initialize Should we initialize My Jetpack? + */ + return apply_filters( 'jetpack_my_jetpack_should_initialize', $should ); + } + + /** + * Site full-data endpoint. + * + * @return object Site data. + */ + public static function get_site() { + $site_id = \Jetpack_Options::get_option( 'id' ); + $wpcom_endpoint = sprintf( '/sites/%d?force=wpcom', $site_id ); + $wpcom_api_version = '1.1'; + $response = Client::wpcom_json_api_request_as_blog( $wpcom_endpoint, $wpcom_api_version ); + $response_code = wp_remote_retrieve_response_code( $response ); + $body = json_decode( wp_remote_retrieve_body( $response ) ); + + if ( is_wp_error( $response ) || empty( $response['body'] ) ) { + return new \WP_Error( 'site_data_fetch_failed', 'Site data fetch failed', array( 'status' => $response_code ) ); + } + + return rest_ensure_response( $body, 200 ); + } + + /** + * Returns true if the site has file write access to the plugins folder, false otherwise. + * + * @return bool + **/ + public static function has_file_system_write_access() { + + $cache = get_transient( 'my_jetpack_write_access' ); + + if ( false !== $cache ) { + return $cache; + } + + if ( ! function_exists( 'get_filesystem_method' ) ) { + require_once ABSPATH . 'wp-admin/includes/file.php'; + } + + require_once ABSPATH . 'wp-admin/includes/template.php'; + + $write_access = 'no'; + + $filesystem_method = get_filesystem_method( array(), WP_PLUGIN_DIR ); + if ( 'direct' === $filesystem_method ) { + $write_access = 'yes'; + } + + if ( ! $write_access ) { + ob_start(); + $filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() ); + ob_end_clean(); + + if ( $filesystem_credentials_are_stored ) { + $write_access = 'yes'; + } + } + + set_transient( 'my_jetpack_write_access', $write_access, 30 * MINUTE_IN_SECONDS ); + + return $write_access; + } + } diff --git a/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/class-products.php b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/class-products.php new file mode 100644 index 00000000..d02e1581 --- /dev/null +++ b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/class-products.php @@ -0,0 +1,144 @@ +<?php +/** + * Class for manipulating products + * + * @package automattic/my-jetpack + */ + +namespace Automattic\Jetpack\My_Jetpack; + +/** + * A class for everything related to product handling in My Jetpack + */ +class Products { + + /** + * Get the list of Products classes + * + * Here's where all the existing Products are registered + * + * @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, + ); + } + + /** + * Product data + * + * @return array Jetpack products on the site and their availability. + */ + public static function get_products() { + $products = array(); + foreach ( self::get_products_classes() as $class ) { + $product_slug = $class::$slug; + $products[ $product_slug ] = $class::get_info(); + } + return $products; + } + + /** + * Get one product data by its slug + * + * @param string $product_slug The product slug. + * + * @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(); + } + } + return null; + } + + /** + * Return product slugs list. + * + * @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; + } + + /** + * Gets the json schema for the product data + * + * @return array + */ + public static function get_product_data_schema() { + return array( + 'title' => 'The requested product data', + 'type' => 'object', + 'properties' => array( + 'product' => array( + 'description' => __( 'Product slug', 'jetpack-my-jetpack' ), + 'type' => 'string', + 'enum' => __CLASS__ . '::get_product_slugs', + 'required' => false, + 'validate_callback' => __CLASS__ . '::check_product_argument', + ), + 'action' => array( + 'description' => __( 'Production action to execute', 'jetpack-my-jetpack' ), + 'type' => 'string', + 'enum' => array( 'activate', 'deactivate' ), + 'required' => false, + 'validate_callback' => __CLASS__ . '::check_product_argument', + ), + 'slug' => array( + 'title' => 'The product slug', + 'type' => 'string', + ), + 'name' => array( + 'title' => 'The product name', + 'type' => 'string', + ), + 'description' => array( + 'title' => 'The product description', + 'type' => 'string', + ), + 'status' => array( + 'title' => 'The product status', + 'type' => 'string', + 'enum' => array( 'active', 'inactive', 'plugin_absent', 'needs_purchase', 'error' ), + ), + 'class' => array( + 'title' => 'The product class handler', + 'type' => 'string', + ), + ), + ); + } + + /** + * Extend actions links for plugins + * 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(); + } + +} diff --git a/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/class-rest-products.php b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/class-rest-products.php new file mode 100644 index 00000000..a0300a84 --- /dev/null +++ b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/class-rest-products.php @@ -0,0 +1,208 @@ +<?php +/** + * Sets up the Products REST API endpoints. + * + * @package automattic/my-jetpack + */ + +namespace Automattic\Jetpack\My_Jetpack; + +use WP_Error; + +/** + * Registers the REST routes for Products. + */ +class REST_Products { + /** + * Constructor. + */ + public function __construct() { + register_rest_route( + 'my-jetpack/v1', + 'site/products', + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::get_products', + 'permission_callback' => __CLASS__ . '::permissions_callback', + ), + 'schema' => array( $this, 'get_products_schema' ), + ) + ); + + $product_arg = array( + 'description' => __( 'Product slug', 'jetpack-my-jetpack' ), + 'type' => 'string', + 'enum' => Products::get_products_slugs(), + 'required' => true, + 'validate_callback' => __CLASS__ . '::check_product_argument', + ); + + register_rest_route( + 'my-jetpack/v1', + 'site/products/(?P<product>[a-z\-]+)', + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::get_product', + 'permission_callback' => __CLASS__ . '::permissions_callback', + 'args' => array( + 'product' => $product_arg, + ), + ), + array( + 'methods' => \WP_REST_Server::EDITABLE, + 'callback' => __CLASS__ . '::activate_product', + 'permission_callback' => __CLASS__ . '::edit_permissions_callback', + 'args' => array( + 'product' => $product_arg, + ), + ), + array( + 'methods' => \WP_REST_Server::DELETABLE, + 'callback' => __CLASS__ . '::deactivate_product', + 'permission_callback' => __CLASS__ . '::edit_permissions_callback', + 'args' => array( + 'product' => $product_arg, + ), + ), + ) + ); + } + + /** + * Get the schema for the products endpoint + * + * @return array + */ + public function get_products_schema() { + return array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'products', + 'type' => 'object', + 'properties' => Products::get_product_data_schema(), + ); + } + + /** + * Check user capability to access the endpoint. + * + * @access public + * @static + * + * @return true|WP_Error + */ + public static function permissions_callback() { + return current_user_can( 'manage_options' ); + } + + /** + * Check Product arguments. + * + * @access public + * @static + * + * @param mixed $value - Value of the 'product' argument. + * @return true|WP_Error True if the value is valid, WP_Error otherwise. + */ + public static function check_product_argument( $value ) { + if ( ! is_string( $value ) ) { + return new WP_Error( + 'rest_invalid_param', + esc_html__( 'The product argument must be a string.', 'jetpack-my-jetpack' ), + array( 'status' => 400 ) + ); + } + + return true; + } + + /** + * Site products endpoint. + * + * @return array of site products list. + */ + public static function get_products() { + $response = Products::get_products(); + return rest_ensure_response( $response, 200 ); + } + + /** + * Site single product endpoint. + * + * @param \WP_REST_Request $request The request object. + * @return array of site products list. + */ + public static function get_product( $request ) { + $product_slug = $request->get_param( 'product' ); + return rest_ensure_response( Products::get_product( $product_slug ), 200 ); + } + + /** + * Check permission to edit product + * + * @return bool + */ + public static function edit_permissions_callback() { + if ( ! current_user_can( 'activate_plugins' ) ) { + return false; + } + if ( is_multisite() && ! current_user_can( 'manage_network' ) ) { + return false; + } + return true; + } + + /** + * Callback for activating a product + * + * @param \WP_REST_Request $request The request object. + * @return \WP_REST_Response + */ + public static function activate_product( $request ) { + $product_slug = $request->get_param( 'product' ); + $product = Products::get_product( $product_slug ); + if ( ! isset( $product['class'] ) ) { + return new \WP_Error( + 'not_implemented', + esc_html__( 'The product class handler is not implemented', 'jetpack-my-jetpack' ), + array( 'status' => 501 ) + ); + } + + $activate_product_result = call_user_func( array( $product['class'], 'activate' ) ); + if ( is_wp_error( $activate_product_result ) ) { + $activate_product_result->add_data( array( 'status' => 400 ) ); + return $activate_product_result; + } + + return rest_ensure_response( Products::get_product( $product_slug ), 200 ); + } + + /** + * Callback for deactivating a product + * + * @param \WP_REST_Request $request The request object. + * @return \WP_REST_Response + */ + public static function deactivate_product( $request ) { + $product_slug = $request->get_param( 'product' ); + $product = Products::get_product( $product_slug ); + if ( ! isset( $product['class'] ) ) { + return new \WP_Error( + 'not_implemented', + esc_html__( 'The product class handler is not implemented', 'jetpack-my-jetpack' ), + array( 'status' => 501 ) + ); + } + + $deactivate_product_result = call_user_func( array( $product['class'], 'deactivate' ) ); + if ( is_wp_error( $deactivate_product_result ) ) { + $deactivate_product_result->add_data( array( 'status' => 400 ) ); + return $deactivate_product_result; + } + + return rest_ensure_response( Products::get_product( $product_slug ), 200 ); + } + +} diff --git a/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/class-rest-purchases.php b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/class-rest-purchases.php new file mode 100644 index 00000000..c90e1b43 --- /dev/null +++ b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/class-rest-purchases.php @@ -0,0 +1,76 @@ +<?php +/** + * Sets up the Purchases REST API endpoints. + * + * @package automattic/my-jetpack + */ + +namespace Automattic\Jetpack\My_Jetpack; + +use Automattic\Jetpack\Connection\Client as Client; +use Automattic\Jetpack\Connection\Manager as Connection_Manager; + +/** + * Registers the REST routes for Purchases. + */ +class REST_Purchases { + /** + * Constructor. + */ + public function __construct() { + register_rest_route( + 'my-jetpack/v1', + '/site/purchases', + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::get_site_current_purchases', + 'permission_callback' => __CLASS__ . '::permissions_callback', + ) + ); + } + + /** + * Check user capability to access the endpoint. + * + * @access public + * @static + * + * @return true|WP_Error + */ + public static function permissions_callback() { + $connection = new Connection_Manager(); + $is_site_connected = $connection->is_connected(); + + if ( ! $is_site_connected ) { + return new \WP_Error( + 'not_connected', + __( 'Your site is not connected to Jetpack.', 'jetpack-my-jetpack' ), + array( + 'status' => 400, + ) + ); + } + + return current_user_can( 'manage_options' ); + } + + /** + * Site purchases endpoint. + * + * @return array of site purchases. + */ + public static function get_site_current_purchases() { + $site_id = \Jetpack_Options::get_option( 'id' ); + $wpcom_endpoint = sprintf( '/sites/%1$d/purchases?locale=%2$s', $site_id, get_user_locale() ); + $wpcom_api_version = '1.1'; + $response = Client::wpcom_json_api_request_as_blog( $wpcom_endpoint, $wpcom_api_version ); + $response_code = wp_remote_retrieve_response_code( $response ); + $body = json_decode( wp_remote_retrieve_body( $response ) ); + + if ( is_wp_error( $response ) || empty( $response['body'] ) ) { + return new \WP_Error( 'site_data_fetch_failed', 'Site data fetch failed', array( 'status' => $response_code ? $response_code : 400 ) ); + } + + return rest_ensure_response( $body, 200 ); + } +} diff --git a/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/class-wpcom-products.php b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/class-wpcom-products.php new file mode 100644 index 00000000..cac879fe --- /dev/null +++ b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/class-wpcom-products.php @@ -0,0 +1,209 @@ +<?php +/** + * Fetches and store the list of Jetpack products available in WPCOM + * + * @package automattic/my-jetpack + */ + +namespace Automattic\Jetpack\My_Jetpack; + +use Automattic\Jetpack\Connection\Client as Client; +use Automattic\Jetpack\Status\Visitor; +use WP_Error; +/** + * Stores the list of products available for purchase in WPCOM + */ +class Wpcom_Products { + + /** + * The meta name used to store the cache date + * + * @var string + */ + const CACHE_DATE_META_NAME = 'my-jetpack-cache-date'; + + /** + * The meta name used to store the cache + * + * @var string + */ + const CACHE_META_NAME = 'my-jetpack-cache'; + + /** + * Fetches the list of products from WPCOM + * + * @return Object|WP_Error + */ + private static function get_products_from_wpcom() { + + $blog_id = \Jetpack_Options::get_option( 'id' ); + $endpoint = sprintf( '/sites/%d/products/?_locale=%s&type=jetpack', $blog_id, get_user_locale() ); + + $wpcom_request = Client::wpcom_json_api_request_as_blog( + $endpoint, + '1.1', + array( + 'method' => 'GET', + 'headers' => array( + 'X-Forwarded-For' => ( new Visitor() )->get_ip( true ), + ), + ) + ); + + $response_code = wp_remote_retrieve_response_code( $wpcom_request ); + + if ( 200 === $response_code ) { + return json_decode( wp_remote_retrieve_body( $wpcom_request ) ); + } else { + return new WP_Error( + 'failed_to_fetch_wpcom_products', + esc_html__( 'Unable to fetch the products list from WordPress.com', 'jetpack-my-jetpack' ), + array( 'status' => $response_code ) + ); + } + } + + /** + * Update the cache with new information retrieved from WPCOM + * + * We store one cache for each user, as the information is internationalized based on user preferences + * Also, the currency is based on the user IP address + * + * @param Object $products_list The products list as received from WPCOM. + * @return bool + */ + private static function update_cache( $products_list ) { + update_user_meta( get_current_user_id(), self::CACHE_DATE_META_NAME, time() ); + return update_user_meta( get_current_user_id(), self::CACHE_META_NAME, $products_list ); + } + + /** + * Checks if the cache is old, meaning we need to fetch new data from WPCOM + */ + private static function is_cache_old() { + if ( empty( self::get_products_from_cache() ) ) { + return true; + } + $cache_date = get_user_meta( get_current_user_id(), self::CACHE_DATE_META_NAME, true ); + return time() - (int) $cache_date > ( 7 * DAY_IN_SECONDS ); + } + + /** + * Gets the product list from the user cache + */ + private static function get_products_from_cache() { + return get_user_meta( get_current_user_id(), self::CACHE_META_NAME, true ); + } + + /** + * Gets the product list + * + * Attempts to retrieve the products list from the user cache if cache is not too old. + * If cache is old, it will attempt to fetch information from WPCOM. If it fails, we return what we have in cache, if anything, otherwise we return an error. + * + * @param bool $skip_cache If true it will ignore the cache and attempt to fetch fresh information from WPCOM. + * + * @return Object|WP_Error + */ + public static function get_products( $skip_cache = false ) { + // This is only available for logged in users. + if ( ! get_current_user_id() ) { + return null; + } + if ( ! self::is_cache_old() && ! $skip_cache ) { + return self::get_products_from_cache(); + } + + $products = self::get_products_from_wpcom(); + if ( is_wp_error( $products ) ) { + // Let's see if we have it cached. + $cached = self::get_products_from_cache(); + if ( ! empty( $cached ) ) { + return $cached; + } else { + return $products; + } + } + + self::update_cache( $products ); + return $products; + + } + + /** + * Get one product + * + * @param string $product_slug The product slug. + * + * @return ?Object The product details if found + */ + public static function get_product( $product_slug ) { + $products = self::get_products(); + if ( ! empty( $products->$product_slug ) ) { + return $products->$product_slug; + } + } + + /** + * Get only the product currency code and price in an array + * + * @param string $product_slug The product slug. + * + * @return array An array with currency_code and full_price. Empty array if product not found. + */ + public static function get_product_pricing( $product_slug ) { + $product = self::get_product( $product_slug ); + if ( empty( $product ) ) { + return array(); + } + + $cost = $product->cost; + $discount_price = $cost; + + // Get/compute the discounted price. + if ( isset( $product->introductory_offer->cost_per_interval ) ) { + $discount_price = $product->introductory_offer->cost_per_interval; + } + + $pricing = array( + 'currency_code' => $product->currency_code, + 'full_price' => $cost, + 'discount_price' => $discount_price, + ); + + return self::populate_with_discount( $product, $pricing, $discount_price ); + } + + /** + * Populate the pricing array with the discount information. + * + * @param {object} $product - The product object. + * @param {object} $pricing - The pricing array. + * @param {float} $price - The price to be discounted. + * @return {object} The pricing array with the discount information. + */ + public static function populate_with_discount( $product, $pricing, $price ) { + // Check whether the product has a coupon. + if ( ! isset( $product->sale_coupon ) ) { + return $pricing; + } + + // Check whether it is still valid. + $coupon = $product->sale_coupon; + $coupon_start_date = strtotime( $coupon->start_date ); + $coupon_expires = strtotime( $coupon->expires ); + if ( $coupon_start_date > time() || $coupon_expires < time() ) { + return $pricing; + } + + $coupon_discount = intval( $coupon->discount ); + + // Populate response with coupon discount. + $pricing['coupon_discount'] = $coupon_discount; + + // Apply coupon discount to the price. + $pricing['discount_price'] = $price * ( 100 - $coupon_discount ) / 100; + + return $pricing; + } +} diff --git a/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-anti-spam.php b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-anti-spam.php new file mode 100644 index 00000000..7c8820d0 --- /dev/null +++ b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-anti-spam.php @@ -0,0 +1,138 @@ +<?php +/** + * Anti_Spam 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 Anti_Spam product + */ +class Anti_Spam extends Product { + + /** + * The product slug + * + * @var string + */ + public static $slug = 'anti-spam'; + + /** + * The filename (id) of the plugin associated with this product. If not defined, it will default to the Jetpack plugin + * + * @var string + */ + public static $plugin_filename = 'akismet/akismet.php'; + + /** + * The slug of the plugin associated with this product. If not defined, it will default to the Jetpack plugin + * + * @var string + */ + public static $plugin_slug = 'akismet'; + + /** + * 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 __( 'Anti-Spam', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product title + * + * @return string + */ + public static function get_title() { + return __( 'Jetpack Anti-Spam', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product description + * + * @return string + */ + public static function get_description() { + return __( 'Stop comment and form spam', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product long description + * + * @return string + */ + public static function get_long_description() { + return __( 'Save time and get better responses by automatically blocking spam from your comments and forms.', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized features list + * + * @return array Boost features list + */ + public static function get_features() { + return array( + _x( 'Comment and form spam protection', 'Anti-Spam Product Feature', 'jetpack-my-jetpack' ), + _x( 'Powered by Akismet', 'Anti-Spam Product Feature', 'jetpack-my-jetpack' ), + _x( 'Block spam without CAPTCHAs', 'Anti-Spam Product Feature', 'jetpack-my-jetpack' ), + _x( 'Advanced stats', 'Anti-Spam Product Feature', 'jetpack-my-jetpack' ), + ); + } + + /** + * Get the product princing details + * + * @return array Pricing details + */ + public static function get_pricing_for_ui() { + return array_merge( + array( + 'available' => true, + 'wpcom_product_slug' => static::get_wpcom_product_slug(), + ), + Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() ) + ); + } + + /** + * Get the WPCOM product slug used to make the purchase + * + * @return ?string + */ + public static function get_wpcom_product_slug() { + return 'jetpack_anti_spam'; + } + + /** + * Return product bundles list + * that supports the product. + * + * @return boolean|array Products bundle list. + */ + public static function is_upgradable_by_bundle() { + return array( 'security' ); + } + + /** + * Get the URL where the user manages the product + * + * @return ?string + */ + public static function get_manage_url() { + return admin_url( 'admin.php?page=akismet-key-config' ); + } +} diff --git a/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-backup.php b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-backup.php new file mode 100644 index 00000000..37b45caf --- /dev/null +++ b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-backup.php @@ -0,0 +1,201 @@ +<?php +/** + * Boost product + * + * @package my-jetpack + */ + +namespace Automattic\Jetpack\My_Jetpack\Products; + +use Automattic\Jetpack\Connection\Client; +use Automattic\Jetpack\My_Jetpack\Hybrid_Product; +use Automattic\Jetpack\My_Jetpack\Wpcom_Products; +use Automattic\Jetpack\Redirect; +use Jetpack_Options; +use WP_Error; + +/** + * Class responsible for handling the Backup product + */ +class Backup extends Hybrid_Product { + + /** + * The product slug + * + * @var string + */ + public static $slug = 'backup'; + + /** + * The filename (id) of the plugin associated with this product. + * + * @var string + */ + public static $plugin_filename = array( + 'jetpack-backup/jetpack-backup.php', + 'backup/jetpack-backup.php', + 'jetpack-backup-dev/jetpack-backup.php', + ); + + /** + * The slug of the plugin associated with this product. + * + * @var string + */ + public static $plugin_slug = 'jetpack-backup'; + + /** + * Get the internationalized product name + * + * @return string + */ + public static function get_name() { + return __( 'Backup', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product title + * + * @return string + */ + public static function get_title() { + return __( 'Jetpack Backup', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product description + * + * @return string + */ + public static function get_description() { + return __( 'Save every change', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product long description + * + * @return string + */ + public static function get_long_description() { + return __( 'Never lose a word, image, page, or time worrying about your site with automated backups & one-click restores.', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized features list + * + * @return array Backup features list + */ + public static function get_features() { + return array( + _x( 'Real-time cloud backups', 'Backup Product Feature', 'jetpack-my-jetpack' ), + _x( '10GB of backup storage', 'Backup Product Feature', 'jetpack-my-jetpack' ), + _x( '30-day archive & activity log', 'Backup Product Feature', 'jetpack-my-jetpack' ), + _x( 'One-click restores', 'Backup Product Feature', 'jetpack-my-jetpack' ), + ); + } + + /** + * Get the WPCOM product slug used to make the purchase + * + * @return ?string + */ + public static function get_wpcom_product_slug() { + return 'jetpack_backup_t1_yearly'; + } + + /** + * Get the product princing details + * + * @return array Pricing details + */ + public static function get_pricing_for_ui() { + return array_merge( + array( + 'available' => true, + 'wpcom_product_slug' => static::get_wpcom_product_slug(), + ), + Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() ) + ); + } + + /** + * Hits the wpcom api to check rewind 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/rewind', $site_id ) . '?force=wpcom', '2', array( 'timeout' => 2 ), null, 'wpcom' ); + + if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { + return new WP_Error( 'rewind_state_fetch_failed' ); + } + + $body = wp_remote_retrieve_body( $response ); + $status = json_decode( $body ); + return $status; + } + + /** + * Checks whether the current plan (or purchases) of the site already supports the product + * + * @return boolean + */ + public static function has_required_plan() { + $rewind_data = static::get_state_from_wpcom(); + if ( is_wp_error( $rewind_data ) ) { + return false; + } + return is_object( $rewind_data ) && isset( $rewind_data->state ) && 'unavailable' !== $rewind_data->state; + } + + /** + * Return product bundles list + * that supports the product. + * + * @return boolean|array Products bundle list. + */ + public static function is_upgradable_by_bundle() { + return array( 'security' ); + } + + /** + * Get the URL the user is taken after activating the product + * + * @return ?string + */ + public static function get_post_activation_url() { + return ''; // stay in My Jetpack page or continue the purchase flow if needed. + } + + /** + * Get the URL where the user manages the 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() ) { + return admin_url( 'admin.php?page=jetpack-backup' ); + } + } + + /** + * Checks whether the Product is active + * + * @return boolean + */ + public static function is_active() { + return parent::is_active() && static::has_required_plan(); + } +} diff --git a/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-boost.php b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-boost.php new file mode 100644 index 00000000..7c159eb4 --- /dev/null +++ b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-boost.php @@ -0,0 +1,117 @@ +<?php +/** + * Boost product + * + * @package my-jetpack + */ + +namespace Automattic\Jetpack\My_Jetpack\Products; + +use Automattic\Jetpack\My_Jetpack\Product; + +/** + * Class responsible for handling the Boost product + */ +class Boost extends Product { + + /** + * The product slug + * + * @var string + */ + public static $slug = 'boost'; + + /** + * The filename (id) of the plugin associated with this product. + * + * @var string + */ + public static $plugin_filename = array( + 'jetpack-boost/jetpack-boost.php', + 'boost/jetpack-boost.php', + 'jetpack-boost-dev/jetpack-boost.php', + ); + /** + * The slug of the plugin associated with this product. + * + * @var string + */ + public static $plugin_slug = 'jetpack-boost'; + + /** + * 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 __( 'Boost', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product title + * + * @return string + */ + public static function get_title() { + return __( 'Jetpack Boost', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product description + * + * @return string + */ + public static function get_description() { + return __( 'Instant speed and SEO', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product long description + * + * @return string + */ + public static function get_long_description() { + return __( 'Jetpack Boost gives your site the same performance advantages as the world’s leading websites, no developer required.', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized features list + * + * @return array Boost features list + */ + public static function get_features() { + return array( + __( 'Check your site performance', 'jetpack-my-jetpack' ), + __( 'Enable improvements in one click', 'jetpack-my-jetpack' ), + __( 'Standalone free plugin for those focused on speed', 'jetpack-my-jetpack' ), + ); + } + + /** + * Get the product princing details + * + * @return array Pricing details + */ + public static function get_pricing_for_ui() { + return array( + 'available' => true, + 'is_free' => true, + ); + } + + /** + * Get the URL where the user manages the product + * + * @return ?string + */ + public static function get_manage_url() { + return admin_url( 'admin.php?page=jetpack-boost' ); + } +} diff --git a/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-crm.php b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-crm.php new file mode 100644 index 00000000..9fce5f92 --- /dev/null +++ b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-crm.php @@ -0,0 +1,124 @@ +<?php +/** + * Boost product + * + * @package my-jetpack + */ + +namespace Automattic\Jetpack\My_Jetpack\Products; + +use Automattic\Jetpack\My_Jetpack\Product; + +/** + * Class responsible for handling the CRM product + */ +class Crm extends Product { + + /** + * The product slug + * + * @var string + */ + public static $slug = 'crm'; + + /** + * The filename (id) of the plugin associated with this product. If not defined, it will default to the Jetpack plugin + * + * @var string + */ + public static $plugin_filename = 'zero-bs-crm/ZeroBSCRM.php'; + + /** + * The slug of the plugin associated with this product. If not defined, it will default to the Jetpack plugin + * + * @var string + */ + public static $plugin_slug = 'zero-bs-crm'; + + /** + * 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 __( 'CRM', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product title + * + * @return string + */ + public static function get_title() { + return __( 'Jetpack CRM', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product description + * + * @return string + */ + public static function get_description() { + return __( 'Connect with your people', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product long description + * + * @return string + */ + public static function get_long_description() { + return __( 'All of your contacts in one place. Build better relationships with your customers and clients.', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized features list + * + * @return array CRM features list + */ + public static function get_features() { + return array( + __( 'Manage unlimited contacts', 'jetpack-my-jetpack' ), + __( 'Manage billing and create invoices', 'jetpack-my-jetpack' ), + __( 'Fully integrated with WordPress & WooCommerce', 'jetpack-my-jetpack' ), + __( 'Infinitely customizable with integrations and extensions', 'jetpack-my-jetpack' ), + ); + } + + /** + * Get the product princing details + * + * @return array Pricing details + */ + public static function get_pricing_for_ui() { + return array( + 'available' => true, + 'is_free' => true, + ); + } + + /** + * Get the URL the user is taken after activating the product + * + * @return ?string + */ + public static function get_post_activation_url() { + return admin_url( 'admin.php?page=zerobscrm-plugin' ); // Welcome page. + } + + /** + * Get the URL where the user manages the product + * + * @return ?string + */ + public static function get_manage_url() { + return admin_url( 'admin.php?page=zerobscrm-dash' ); + } +} diff --git a/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-extras.php b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-extras.php new file mode 100644 index 00000000..d0ffaf8c --- /dev/null +++ b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-extras.php @@ -0,0 +1,143 @@ +<?php +/** + * Extras product + * + * @package my-jetpack + */ + +namespace Automattic\Jetpack\My_Jetpack\Products; + +use Automattic\Jetpack\My_Jetpack\Product; + +/** + * Class responsible for handling the Extras product. + * Extras, so far, could be considered as Jetpack plugin bridge. + */ +class Extras extends Product { + + /** + * The product slug + * + * @var string + */ + public static $slug = 'extras'; + + /** + * The slug of the plugin associated with this product. + * Extras, is in short, Jetpack plugin bridge so far. + * + * @var string + */ + public static $plugin_slug = 'jetpack'; + + /** + * 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 __( 'Extras', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product title + * + * @return string + */ + public static function get_title() { + return __( 'Jetpack Extras', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product description + * + * @return string + */ + public static function get_description() { + return __( 'Basic tools for a successful site', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product long description + * + * @return string + */ + public static function get_long_description() { + return __( "Secure and speed up your site for free with Jetpack's powerful WordPress tools.", 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized features list + * + * @return array Boost features list + */ + public static function get_features() { + return array( + __( 'Measure your impact with beautiful stats', 'jetpack-my-jetpack' ), + __( 'Speed up your site with optimized images', 'jetpack-my-jetpack' ), + __( 'Protect your site against bot attacks', 'jetpack-my-jetpack' ), + __( 'Get notifications if your site goes offline', 'jetpack-my-jetpack' ), + __( 'Enhance your site with dozens of other features', 'jetpack-my-jetpack' ), + ); + } + + /** + * Get the product princing details + * + * @return array Pricing details + */ + public static function get_pricing_for_ui() { + return array( + 'available' => true, + 'is_free' => true, + ); + } + + /** + * Checks whether the Product is active. + * If Jetpack plugin is active, then Extras will be inactive. + * + * @return boolean + */ + public static function is_active() { + return static::is_jetpack_plugin_active(); + } + + /** + * Checks whether the plugin is installed + * If Jetpack plugin is installed, then Extras will be inactive. + * + * @return boolean + */ + public static function is_plugin_installed() { + return static::is_jetpack_plugin_installed(); + } + + /** + * Get the URL where the user manages the product + * + * @return ?string + */ + public static function get_manage_url() { + return admin_url( 'admin.php?page=jetpack' ); + } + + /** + * Activates the Jetpack plugin + * + * @return null|WP_Error Null on success, WP_Error on invalid file. + */ + public static function activate_plugin() { + /* + * Silent mode True to avoid redirect + */ + return activate_plugin( static::get_installed_plugin_filename( 'jetpack' ), '', false, true ); + } +} diff --git a/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-hybrid-product.php b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-hybrid-product.php new file mode 100644 index 00000000..1bb995ff --- /dev/null +++ b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-hybrid-product.php @@ -0,0 +1,129 @@ +<?php +/** + * Base product + * + * @package my-jetpack + */ + +namespace Automattic\Jetpack\My_Jetpack; + +use Automattic\Jetpack\Modules; +use Automattic\Jetpack\Plugins_Installer; +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 { + + /** + * Checks whether the Product is active + * + * @return boolean + */ + public static function is_plugin_active() { + return parent::is_plugin_active() || parent::is_jetpack_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 + * + * @return bool + */ + public static function is_module_active() { + if ( ! empty( static::$module_name ) ) { + return ( new Modules() )->is_active( static::$module_name ); + } + return true; + } + + /** + * Checks whether the Product is active + * + * @return boolean + */ + public static function is_active() { + return parent::is_active() && static::is_module_active(); + } + + /** + * Activates the plugin + * + * @return null|WP_Error Null on success, WP_Error on invalid file. + */ + public static function activate_plugin() { + /* + * Activate self-installed plugin if it's installed. + * Silent mode True to avoid redirects in Backup. + * @TODO When new Hybrid products are added, we might not want to go silent with all of them. + */ + if ( parent::is_plugin_installed() ) { + return activate_plugin( static::get_installed_plugin_filename(), '', false, true ); + } + + /* + * 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' ) ); + } + + return new WP_Error( 'plugin_not_found', __( 'Activation failed. Plugin is not installed', 'jetpack-my-jetpack' ) ); + } + + /** + * Activates the product. If the Hybrid product has declared a jetpack module name, let's try to activate it if Jetpack plugin is active + * + * @param bool|WP_Error $product_activation Is the result of the top level activation actions. You probably won't do anything if it is an WP_Error. + * @return bool|WP_Error + */ + public static function do_product_specific_activation( $product_activation ) { + + 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. + if ( 'no_package' === $product_activation->get_error_code() ) { + $product_activation = Plugins_Installer::install_plugin( self::JETPACK_PLUGIN_SLUG ); + if ( ! is_wp_error( $product_activation ) ) { + $product_activation = static::activate_plugin(); + } + } + if ( is_wp_error( $product_activation ) ) { + return $product_activation; + } + } + + 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() ) ); + } + $module_activation = ( new Modules() )->activate( static::$module_name, false, false ); + if ( ! $module_activation ) { + return new WP_Error( 'module_activation_failed', __( 'Error activating Jetpack module', 'jetpack-my-jetpack' ) ); + } + + return $module_activation; + } + + return true; + + } + +} diff --git a/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-module-product.php b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-module-product.php new file mode 100644 index 00000000..e2cf58ef --- /dev/null +++ b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-module-product.php @@ -0,0 +1,135 @@ +<?php +/** + * Base Module product + * + * @package my-jetpack + */ + +namespace Automattic\Jetpack\My_Jetpack; + +use Jetpack; +use WP_Error; + +/** + * Class responsible for handling the Module products + * + * Module products are those that are a Jetpack module behind the scenes. + * + * They require Jetpack plugin and will then activate/deactivate a module. + */ +abstract class Module_Product extends Product { + + /** + * The Jetpack module name associated with this product + * + * @var string|null + */ + public static $module_name = null; + + /** + * Get the plugin slug - ovewrite it ans return Jetpack's + * + * @return ?string + */ + public static function get_plugin_slug() { + return self::JETPACK_PLUGIN_SLUG; + } + + /** + * Get the plugin filename - ovewrite it ans return Jetpack's + * + * @return ?string + */ + public static function get_plugin_filename() { + return self::JETPACK_PLUGIN_FILENAME; + } + + /** + * Ensure that child classes define $module_name attribute + * + * @throws \Exception If required attribute is not declared in the child class. + * @return void + */ + private static function check_for_module_name() { + if ( empty( static::$module_name ) ) { + throw new \Exception( 'Module Product classes must declare the $module_name attribute.' ); + } + } + + /** + * Checks whether the Product is active + * + * @return boolean + */ + public static function is_active() { + return static::is_jetpack_plugin_active() && static::is_module_active(); + } + + /** + * Checks whether the Jetpack module is active + * + * @return bool + */ + public static function is_module_active() { + self::check_for_module_name(); + if ( ! class_exists( 'Jetpack' ) ) { + return false; + } + + return Jetpack::is_module_active( static::$module_name ); + } + + /** + * Gets the current status of the product + * + * @return string + */ + public static function get_status() { + $status = parent::get_status(); + if ( 'active' === $status && ! static::is_module_active() ) { + $status = 'module_disabled'; + } + return $status; + } + + /** + * Activates the product by installing and activating its plugin + * + * @param bool|WP_Error $plugin_activation Is the result of the top level activation actions. You probably won't do anything if it is an WP_Error. + * @return boolean|\WP_Error + */ + public static function do_product_specific_activation( $plugin_activation ) { + self::check_for_module_name(); + + if ( is_wp_error( $plugin_activation ) ) { + return $plugin_activation; + } + + if ( ! class_exists( 'Jetpack' ) ) { + return new WP_Error( 'plugin_activation_failed', __( 'Error activating Jetpack plugin', 'jetpack-my-jetpack' ) ); + } + + $module_activation = Jetpack::activate_module( static::$module_name, false, false ); + + if ( ! $module_activation ) { + return new WP_Error( 'module_activation_failed', __( 'Error activating Jetpack module', 'jetpack-my-jetpack' ) ); + } + + return $module_activation; + + } + + /** + * Deactivate the module + * + * @return boolean + */ + public static function deactivate() { + self::check_for_module_name(); + if ( ! class_exists( 'Jetpack' ) ) { + return true; + } + return Jetpack::deactivate_module( static::$module_name ); + } + +} diff --git a/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-product.php b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-product.php new file mode 100644 index 00000000..ef09aecd --- /dev/null +++ b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-product.php @@ -0,0 +1,437 @@ +<?php +/** + * Base product + * + * @package my-jetpack + */ + +namespace Automattic\Jetpack\My_Jetpack; + +use Automattic\Jetpack\Connection\Manager as Connection_Manager; +use Automattic\Jetpack\Plugins_Installer; +use WP_Error; + +/** + * Class responsible for handling the products + */ +abstract class Product { + + /** + * The product slug + * + * @var string + */ + public static $slug = null; + + /** + * The filename (id) of the plugin associated with this product. Can be a string with a single value or a list of possible values + * + * @var string|string[] + */ + protected static $plugin_filename = null; + + /** + * The slug of the plugin associated with this product. If not defined, it will default to the Jetpack plugin + * + * @var string + */ + public static $plugin_slug = null; + + /** + * The Jetpack plugin slug + * + * @var string + */ + const JETPACK_PLUGIN_SLUG = 'jetpack'; + + /** + * The Jetpack plugin filename + * + * @var string + */ + const JETPACK_PLUGIN_FILENAME = array( + 'jetpack/jetpack.php', + 'jetpack-dev/jetpack.php', + ); + + /** + * Whether this product requires a user connection + * + * @var string + */ + public static $requires_user_connection = true; + + /** + * Get the plugin slug + * + * @return ?string + */ + public static function get_plugin_slug() { + return static::$plugin_slug; + } + + /** + * Get the plugin filename + * + * @return ?string + */ + public static function get_plugin_filename() { + return static::$plugin_filename; + } + + /** + * Get the installed plugin filename, considering all possible filenames a plugin might have + * + * @param string $plugin Which plugin to check. jetpack for the jetpack plugin or product for the product specific plugin. + * + * @return ?string + */ + public static function get_installed_plugin_filename( $plugin = 'product' ) { + $all_plugins = Plugins_Installer::get_plugins(); + $filename = 'jetpack' === $plugin ? self::JETPACK_PLUGIN_FILENAME : static::get_plugin_filename(); + if ( ! is_array( $filename ) ) { + $filename = array( $filename ); + } + foreach ( $filename as $name ) { + $installed = array_key_exists( $name, $all_plugins ); + if ( $installed ) { + return $name; + } + } + } + + /** + * Get the Product info for the API + * + * @throws \Exception If required attribute is not declared in the child class. + * @return array + */ + public static function get_info() { + if ( static::$slug === null ) { + throw new \Exception( 'Product classes must declare the $slug attribute.' ); + } + return array( + 'slug' => static::$slug, + 'name' => static::get_name(), + 'title' => static::get_title(), + 'description' => static::get_description(), + 'long_description' => static::get_long_description(), + 'features' => static::get_features(), + 'status' => static::get_status(), + 'pricing_for_ui' => static::get_pricing_for_ui(), + 'is_bundle' => static::is_bundle_product(), + '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(), + 'manage_url' => static::get_manage_url(), + 'post_activation_url' => static::get_post_activation_url(), + 'class' => get_called_class(), + ); + } + + /** + * Get the internationalized product name + * + * @return string + */ + abstract public static function get_name(); + + /** + * Get the internationalized product title + * + * @return string + */ + abstract public static function get_title(); + + /** + * Get the internationalized product description + * + * @return string + */ + abstract public static function get_description(); + + /** + * Get the internationalized product long description + * + * @return string + */ + abstract public static function get_long_description(); + + /** + * Get the internationalized features list + * + * @return array + */ + abstract public static function get_features(); + + /** + * Get the product pricing + * + * @return array + */ + abstract public static function get_pricing_for_ui(); + + /** + * Get the URL where the user manages the product + * + * @return ?string + */ + abstract public static function get_manage_url(); + + /** + * Get the URL the user is taken after activating the product + * + * @return ?string + */ + public static function get_post_activation_url() { + return static::get_manage_url(); + } + + /** + * Get the WPCOM product slug used to make the purchase + * + * @return ?string + */ + public static function get_wpcom_product_slug() { + return null; + } + + /** + * Checks whether the current plan (or purchases) of the site already supports the product + * + * Returns true if it supports. Return false if a purchase is still required. + * + * Free products will always return true. + * + * @return boolean + */ + public static function has_required_plan() { + return true; + } + + /** + * Checks whether product is a bundle. + * + * @return boolean True if product is a bundle. Otherwise, False. + */ + public static function is_bundle_product() { + return false; + } + + /** + * Check whether the product is upgradable + * by a product bundle. + * + * @return boolean|array Bundles list or False if not upgradable by a bundle. + */ + public static function is_upgradable_by_bundle() { + return false; + } + + /** + * In case it's a bundle product, + * return all the products it contains. + * Empty array by default. + * + * @return Array Product slugs + */ + public static function get_supported_products() { + return array(); + } + + /** + * Undocumented function + * + * @return string + */ + public static function get_status() { + + if ( ! static::is_plugin_installed() ) { + $status = 'plugin_absent'; + } elseif ( static::is_active() ) { + $status = 'active'; + // We only consider missing user connection an error when the Product is active. + if ( static::$requires_user_connection && ! ( new Connection_Manager() )->has_connected_owner() ) { + $status = 'error'; + } elseif ( ! static::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() ) { + $status = 'needs_purchase'; + } else { + $status = 'inactive'; + } + return $status; + } + + /** + * Checks whether the Product is active + * + * @return boolean + */ + public static function is_active() { + return static::is_plugin_active() && static::has_required_plan(); + } + + /** + * Checks whether the plugin is installed + * + * @return boolean + */ + public static function is_plugin_installed() { + return (bool) static::get_installed_plugin_filename(); + } + + /** + * Checks whether the plugin is active + * + * @return boolean + */ + public static function is_plugin_active() { + return Plugins_Installer::is_plugin_active( static::get_installed_plugin_filename() ); + } + + /** + * Checks whether the Jetpack plugin is installed + * + * @return boolean + */ + public static function is_jetpack_plugin_installed() { + return (bool) static::get_installed_plugin_filename( 'jetpack' ); + } + + /** + * Checks whether the Jetpack plugin is active + * + * @return boolean + */ + public static function is_jetpack_plugin_active() { + return Plugins_Installer::is_plugin_active( static::get_installed_plugin_filename( 'jetpack' ) ); + } + + /** + * Activates the plugin + * + * @return null|WP_Error Null on success, WP_Error on invalid file. + */ + public static function activate_plugin() { + return activate_plugin( static::get_installed_plugin_filename() ); + } + + /** + * Perform the top level activation routines, which is installing and activating the required plugin + * + * @return bool|WP_Error + */ + private static function do_activation() { + if ( static::is_active() ) { + return true; + } + + if ( ! static::is_plugin_installed() ) { + $installed = Plugins_Installer::install_plugin( static::get_plugin_slug() ); + if ( is_wp_error( $installed ) ) { + return $installed; + } + } + + if ( ! current_user_can( 'activate_plugins' ) ) { + return new WP_Error( 'not_allowed', __( 'You are not allowed to activate plugins on this site.', 'jetpack-my-jetpack' ) ); + } + + $result = static::activate_plugin(); + if ( is_wp_error( $result ) ) { + return $result; + } + + return true; + } + + /** + * Activates the product by installing and activating its plugin + * + * @return boolean|WP_Error + */ + final public static function activate() { + + $result = self::do_activation(); + + $result = static::do_product_specific_activation( $result ); + + $product_slug = static::$slug; + + /** + * Fires after My Jetpack activates a product and filters the result + * Use this filter to run additional routines for a product activation on stand-alone plugins + * + * @param bool|WP_Error $result The result of the previous steps of activation. + */ + $result = apply_filters( "my_jetpack_{$product_slug}_activation", $result ); + + return $result; + + } + + /** + * Override this method to perform product specific activation routines. + * + * @param bool|WP_Error $current_result Is the result of the top level activation actions. You probably won't do anything if it is an WP_Error. + * @return bool|WP_Error + */ + public static function do_product_specific_activation( $current_result ) { + return $current_result; + } + + /** + * Deactivate the product + * + * @return boolean + */ + public static function deactivate() { + deactivate_plugins( static::get_installed_plugin_filename() ); + return true; + } + + /** + * Returns filtered Jetpack plugin actions links. + * + * @param array $actions - Jetpack plugin action links. + * @return array Filtered Jetpack plugin actions links. + */ + public static function get_plugin_actions_links( $actions ) { + // My Jetpack action link. + $my_jetpack_home_link = array( + 'jetpack-home' => sprintf( + '<a href="%1$s" title="%3$s">%2$s</a>', + admin_url( 'admin.php?page=my-jetpack' ), + __( 'My Jetpack', 'jetpack-my-jetpack' ), + __( 'My Jetpack dashboard', 'jetpack-my-jetpack' ) + ), + ); + + // Otherwise, add it to the beginning of the array. + return array_merge( $my_jetpack_home_link, $actions ); + } + + /** + * Extend the plugin action links. + */ + public static function extend_plugin_action_links() { + + $filenames = static::get_plugin_filename(); + if ( ! is_array( $filenames ) ) { + $filenames = array( $filenames ); + } + + foreach ( $filenames as $filename ) { + $hook = 'plugin_action_links_' . $filename; + $callback = array( static::class, 'get_plugin_actions_links' ); + if ( ! has_filter( $hook, $callback ) ) { + add_filter( $hook, $callback, 20, 2 ); + } + } + } + +} diff --git a/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-protect.php b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-protect.php new file mode 100644 index 00000000..a4cca50a --- /dev/null +++ b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-protect.php @@ -0,0 +1,119 @@ +<?php +/** + * Protect product + * + * @package my-jetpack + */ + +namespace Automattic\Jetpack\My_Jetpack\Products; + +use Automattic\Jetpack\My_Jetpack\Product; + +/** + * Class responsible for handling the Protect product + */ +class Protect extends Product { + + /** + * The product slug + * + * @var string + */ + public static $slug = 'protect'; + + /** + * The filename (id) of the plugin associated with this product. + * + * @var string + */ + public static $plugin_filename = array( + 'jetpack-protect/jetpack-protect.php', + 'protect/jetpack-protect.php', + 'jetpack-protect-dev/jetpack-protect.php', + ); + + /** + * The slug of the plugin associated with this product. + * + * @var string + */ + public static $plugin_slug = 'jetpack-protect'; + + /** + * 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 __( 'Protect', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product title + * + * @return string + */ + public static function get_title() { + return __( 'Jetpack Protect', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product description + * + * @return string + */ + public static function get_description() { + return __( 'Protect your site and scan for security vulnerabilities.', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product long description + * + * @return string + */ + public static function get_long_description() { + return __( 'Protect your site and scan for security vulnerabilities listed in our database.', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized features list + * + * @return array Protect features list + */ + public static function get_features() { + return array( + __( 'Over 20,000 listed vulnerabilities', 'jetpack-my-jetpack' ), + __( 'Daily automatic scans', 'jetpack-my-jetpack' ), + __( 'Check plugin and theme version status', 'jetpack-my-jetpack' ), + __( 'Easy to navigate and use', 'jetpack-my-jetpack' ), + ); + } + + /** + * Get the product princing details + * + * @return array Pricing details + */ + public static function get_pricing_for_ui() { + return array( + 'available' => true, + 'is_free' => true, + ); + } + + /** + * Get the URL where the user manages the product + * + * @return ?string + */ + public static function get_manage_url() { + return admin_url( 'admin.php?page=jetpack-protect' ); + } +} diff --git a/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-scan.php b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-scan.php new file mode 100644 index 00000000..452b1644 --- /dev/null +++ b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-scan.php @@ -0,0 +1,218 @@ +<?php +/** + * Scan product + * + * @package my-jetpack + */ + +namespace Automattic\Jetpack\My_Jetpack\Products; + +use Automattic\Jetpack\Connection\Client; +use Automattic\Jetpack\My_Jetpack\Module_Product; +use Automattic\Jetpack\My_Jetpack\Wpcom_Products; +use Automattic\Jetpack\Redirect; +use Jetpack_Options; +use WP_Error; + +/** + * Class responsible for handling the Scan product + */ +class Scan extends Module_Product { + + /** + * The product slug + * + * @var string + */ + public static $slug = 'scan'; + + /** + * The Jetpack module name + * + * @var string + */ + public static $module_name = 'scan'; + + /** + * Get the internationalized product name + * + * @return string + */ + public static function get_name() { + return __( 'Scan', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product title + * + * @return string + */ + public static function get_title() { + return __( 'Jetpack Scan', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product description + * + * @return string + */ + public static function get_description() { + return __( 'Stay one step ahead of threats', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product long description + * + * @return string + */ + public static function get_long_description() { + return __( 'Automatic scanning and one-click fixes keep your site one step ahead of security threats and malware.', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized features list + * + * @return array Scan features list + */ + public static function get_features() { + return array( + _x( 'Automated daily scanning', 'Scan Product Feature', 'jetpack-my-jetpack' ), + _x( 'One-click fixes for most issues', 'Scan Product Feature', 'jetpack-my-jetpack' ), + _x( 'Instant email notifications', 'Scan Product Feature', 'jetpack-my-jetpack' ), + _x( 'Access to latest Firewall rules', 'Scan Product Feature', 'jetpack-my-jetpack' ), + ); + } + + /** + * Get the product princing details + * + * @return array Pricing details + */ + public static function get_pricing_for_ui() { + return array_merge( + array( + 'available' => true, + 'wpcom_product_slug' => static::get_wpcom_product_slug(), + ), + Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() ) + ); + } + + /** + * Get the WPCOM product slug used to make the purchase + * + * @return ?string + */ + public static function get_wpcom_product_slug() { + return 'jetpack_scan'; + } + + /** + * 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; + } + + /** + * Checks whether the current plan (or purchases) of the site already supports the product + * + * @return boolean + */ + public static function has_required_plan() { + $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; + } + + /** + * Checks whether the Product is active + * + * Scan is not actually a module. Activation takes place on WPCOM. So lets consider it active if jetpack is active and has the plan. + * + * @return boolean + */ + public static function is_active() { + return static::is_jetpack_plugin_active() && static::has_required_plan(); + } + + /** + * Activates the product by installing and activating its plugin + * + * @param bool|WP_Error $current_result Is the result of the top level activation actions. You probably won't do anything if it is an WP_Error. + * @return boolean|\WP_Error + */ + public static function do_product_specific_activation( $current_result ) { + + $product_activation = parent::do_product_specific_activation( $current_result ); + + if ( is_wp_error( $product_activation ) && 'module_activation_failed' === $product_activation->get_error_code() ) { + // Scan is not a module. There's nothing in the plugin to be activated, so it's ok to fail to activate the module. + $product_activation = true; + } + + return $product_activation; + + } + + /** + * Checks whether the Jetpack module is active + * + * Scan is not a module. Nothing needs to be active. Let's always consider it active. + * + * @return bool + */ + public static function is_module_active() { + return true; + } + + /** + * Return product bundles list + * that supports the product. + * + * @return boolean|array Products bundle list. + */ + public static function is_upgradable_by_bundle() { + return array( 'security' ); + } + + /** + * Get the URL the user is taken after activating the product + * + * @return ?string + */ + public static function get_post_activation_url() { + return ''; // stay in My Jetpack page. + } + + /** + * Get the URL where the user manages the product + * + * @return ?string + */ + public static function get_manage_url() { + return Redirect::get_url( 'my-jetpack-manage-scan' ); + } +} diff --git a/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-search-stats.php b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-search-stats.php new file mode 100644 index 00000000..66e81139 --- /dev/null +++ b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-search-stats.php @@ -0,0 +1,89 @@ +<?php +/** + * Get search stats for use in the wp-admin dashboard. + * + * @package my-jetpack + */ + +namespace Automattic\Jetpack\My_Jetpack\Products; + +use Automattic\Jetpack\Connection\Client; +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'; + + /** + * Get stats from the WordPress.com API for the current blog ID. + */ + public function get_stats_from_wpcom() { + $blog_id = Jetpack_Options::get_option( 'id' ); + + if ( ! is_numeric( $blog_id ) ) { + return null; + } + + $response = Client::wpcom_json_api_request_as_blog( + '/sites/' . (int) $blog_id . '/jetpack-search/stats', + '2', + array(), + null, + 'wpcom' + ); + + return $response; + } + + /** + * 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; + } + + global $wpdb; + $indexable_statuses = get_post_stati( array( 'public' => true ) ); + $unindexable_post_types = array_merge( + // Explicitly exclude various post types registered by plugins. + 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' + ) + ); + + $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)" + ); + + wp_cache_set( self::COUNT_ESTIMATE_CACHE_KEY, $count, self::CACHE_GROUP, self::CACHE_EXPIRY ); + return $count; + } +} diff --git a/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-search.php b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-search.php new file mode 100644 index 00000000..fcd2654e --- /dev/null +++ b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-search.php @@ -0,0 +1,251 @@ +<?php +/** + * Search product + * + * @package my-jetpack + */ + +namespace Automattic\Jetpack\My_Jetpack\Products; + +use Automattic\Jetpack\Connection\Client; +use Automattic\Jetpack\My_Jetpack\Hybrid_Product; +use Automattic\Jetpack\My_Jetpack\Wpcom_Products; +use Automattic\Jetpack\Search\Module_Control as Search_Module_Control; +use Jetpack_Options; +use WP_Error; + +/** + * Class responsible for handling the Search product + */ +class Search extends Hybrid_Product { + /** + * The product slug + * + * @var string + */ + public static $slug = 'search'; + + /** + * The Jetpack module name + * + * @var string + */ + public static $module_name = 'search'; + + /** + * The slug of the plugin associated with this product. + * + * @var string + */ + public static $plugin_slug = 'jetpack-search'; + + /** + * The filename (id) of the plugin associated with this product. + * + * @var string + */ + public static $plugin_filename = array( + 'jetpack-search/jetpack-search.php', + 'search/jetpack-search.php', + 'jetpack-search-dev/jetpack-search.php', + ); + + /** + * Get the internationalized product name + * + * @return string + */ + public static function get_name() { + return __( 'Search', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product title + * + * @return string + */ + public static function get_title() { + return __( 'Jetpack Search', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product description + * + * @return string + */ + public static function get_description() { + return __( 'Help them find what they need', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product long description + * + * @return string + */ + public static function get_long_description() { + return __( 'Help your site visitors find answers instantly so they keep reading and buying. Great for sites with a lot of content.', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized features list + * + * @return array Boost features list + */ + public static function get_features() { + return array( + __( 'Instant search and indexing', 'jetpack-my-jetpack' ), + __( 'Powerful filtering', 'jetpack-my-jetpack' ), + __( 'Supports 29 languages', 'jetpack-my-jetpack' ), + __( 'Spelling correction', 'jetpack-my-jetpack' ), + ); + } + + /** + * Get the product princing details + * + * @return array Pricing details + */ + public static function get_pricing_for_ui() { + // Basic pricing info. + $pricing = array_merge( + array( + 'available' => true, + 'wpcom_product_slug' => static::get_wpcom_product_slug(), + ), + Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() ) + ); + + $record_count = intval( Search_Stats::estimate_count() ); + + // Check whether the price is available. + // Bail early return the pricing info if not. + $product = Wpcom_Products::get_product( static::get_wpcom_product_slug() ); + if ( ! isset( $product->price_tier_list ) ) { + return $pricing; + } + + // Sort the tiers. + $price_tier_list = $product->price_tier_list; + array_multisort( array_column( $price_tier_list, 'maximum_units' ), SORT_ASC, $price_tier_list ); + + // Pick the first tier that is less than or equal to the record count. + foreach ( $product->price_tier_list as $price_tier ) { + if ( $record_count <= $price_tier->maximum_units ) { + break; + } + } + + // Compute the minimum price. + $minimum_price = $price_tier->minimum_price / 100; + + // Re define the display price based on the tier. + $pricing = Wpcom_Products::populate_with_discount( $product, $pricing, $minimum_price ); + + // 1. Flat fee in the same tier, so for search, `minimum_price == maximum_price`. + // 2. `maximum_units` is empty on the highest tier, so the logic displays the highest or the highest matching tier. + return array_merge( + $pricing, + array( + 'minimum_units' => $price_tier->minimum_units, + 'maximum_units' => $price_tier->maximum_units, + 'estimated_count' => $record_count, + 'full_price' => $minimum_price, // reset the full price to the minimum price. + ) + ); + } + + /** + * Get the WPCOM product slug used to make the purchase + * + * @return ?string + */ + public static function get_wpcom_product_slug() { + return 'jetpack_search'; + } + + /** + * Hits the wpcom api to check Search status. + * + * @todo Maybe add caching. + * + * @return Object|WP_Error + */ + private static function get_state_from_wpcom() { + static $status = null; + + if ( $status !== null ) { + return $status; + } + + $blog_id = Jetpack_Options::get_option( 'id' ); + + $response = Client::wpcom_json_api_request_as_blog( + '/sites/' . $blog_id . '/jetpack-search/plan', + '2', + array( 'timeout' => 2 ), + null, + 'wpcom' + ); + + if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { + return new WP_Error( 'search_state_fetch_failed' ); + } + + $body = wp_remote_retrieve_body( $response ); + $status = json_decode( $body ); + return $status; + } + + /** + * Checks whether the current plan of the site already supports the product + * + * Returns true if it supports. Return false if a purchase is still required. + * + * Free products will always return true. + * + * @return boolean + */ + public static function has_required_plan() { + $search_state = static::get_state_from_wpcom(); + return ! empty( $search_state->supports_search ) || ! empty( $search_state->supports_instant_search ); + } + + /** + * Activates the product. Try to enable instant search after the Search module was enabled. + * + * @param bool|WP_Error $product_activation Is the result of the top level activation actions. You probably won't do anything if it is an WP_Error. + * @return bool|WP_Error + */ + public static function do_product_specific_activation( $product_activation ) { + $product_activation = parent::do_product_specific_activation( $product_activation ); + if ( is_wp_error( $product_activation ) ) { + return $product_activation; + } + + if ( class_exists( 'Automattic\Jetpack\Search\Module_Control' ) ) { + ( new Search_Module_Control() )->enable_instant_search(); + } + + // we don't want to change the success of the activation if we fail to activate instant search. That's not mandatory. + return $product_activation; + } + + /** + * Get the URL the user is taken after activating the product + * + * @return ?string + */ + public static function get_post_activation_url() { + return ''; // stay in My Jetpack page or continue the purchase flow if needed. + } + + /** + * Get the URL where the user manages the product + * + * @return ?string + */ + public static function get_manage_url() { + return admin_url( 'admin.php?page=jetpack-search' ); + } + +} diff --git a/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-security.php b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-security.php new file mode 100644 index 00000000..f23cfdcf --- /dev/null +++ b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-security.php @@ -0,0 +1,247 @@ +<?php +/** + * Security product + * + * @package my-jetpack + */ + +namespace Automattic\Jetpack\My_Jetpack\Products; + +use Automattic\Jetpack\Connection\Client; +use Automattic\Jetpack\My_Jetpack\Module_Product; +use Automattic\Jetpack\My_Jetpack\Wpcom_Products; +use Jetpack_Options; +use WP_Error; + +/** + * Class responsible for handling the Security product + */ +class Security extends Module_Product { + + /** + * The product slug + * + * @var string + */ + public static $slug = 'security'; + + /** + * The Jetpack module name + * + * @var string + */ + public static $module_name = 'security'; + + /** + * Get the internationalized product name + * + * @return string + */ + public static function get_name() { + return __( 'Security', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product title + * + * @return string + */ + public static function get_title() { + return __( 'Security', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product description + * + * @return string + */ + public static function get_description() { + return __( 'Comprehensive site security, including Backup, Scan, and Anti-spam.', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product long description + * + * @return string + */ + public static function get_long_description() { + return __( 'Comprehensive site security, including Backup, Scan, and Anti-spam.', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized features list + * + * @return array Boost features list + */ + public static function get_features() { + return array( + _x( 'Real-time cloud backups with 10GB storage', 'Security Product Feature', 'jetpack-my-jetpack' ), + _x( 'Automated real-time malware scan', 'Security Product Feature', 'jetpack-my-jetpack' ), + _x( 'One-click fixes for most threats', 'Security Product Feature', 'jetpack-my-jetpack' ), + _x( 'Comment & form spam protection', 'Security Product Feature', 'jetpack-my-jetpack' ), + ); + } + + /** + * Get the product princing details + * + * @return array Pricing details + */ + public static function get_pricing_for_ui() { + return array_merge( + array( + 'available' => true, + 'wpcom_product_slug' => static::get_wpcom_product_slug(), + ), + Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() ) + ); + } + + /** + * Get the WPCOM product slug used to make the purchase + * + * @return ?string + */ + public static function get_wpcom_product_slug() { + return 'jetpack_security_t1_yearly'; + } + + /** + * Checks whether the Jetpack module is active + * + * This is a bundle and not a product. We should not use this information for anything + * + * @return bool + */ + public static function is_module_active() { + return false; + } + + /** + * Activates the product by installing and activating its plugin + * + * @param bool|WP_Error $current_result Is the result of the top level activation actions. You probably won't do anything if it is an WP_Error. + * @return boolean|\WP_Error + */ + public static function do_product_specific_activation( $current_result ) { + + $product_activation = parent::do_product_specific_activation( $current_result ); + + if ( is_wp_error( $product_activation ) && 'module_activation_failed' === $product_activation->get_error_code() ) { + // A bundle is not a module. There's nothing in the plugin to be activated, so it's ok to fail to activate the module. + $product_activation = true; + } + + // At this point, Jetpack plugin is installed. Let's activate each individual product. + $activation = Anti_Spam::activate(); + if ( is_wp_error( $activation ) ) { + return $activation; + } + + $activation = Backup::activate(); + if ( is_wp_error( $activation ) ) { + return $activation; + } + + $activation = Scan::activate(); + if ( is_wp_error( $activation ) ) { + return $activation; + } + + return $activation; + + } + + /** + * Checks whether the Product is active + * + * Security is a bundle and not a module. Activation takes place on WPCOM. So lets consider it active if jetpack is active and has the plan. + * + * @return boolean + */ + public static function is_active() { + return static::is_jetpack_plugin_active() && static::has_required_plan(); + } + + /** + * Hits the wpcom api to check scan status. + * + * @todo Maybe add caching. + * + * @return Object|WP_Error + */ + private static function get_state_from_wpcom() { + static $status = null; + + if ( $status !== null ) { + return $status; + } + + $site_id = Jetpack_Options::get_option( 'id' ); + + $response = Client::wpcom_json_api_request_as_blog( + sprintf( '/sites/%d/purchases', $site_id ), + '1.1', + array( + 'method' => 'GET', + ) + ); + if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { + return new WP_Error( 'purchases_state_fetch_failed' ); + } + + $body = wp_remote_retrieve_body( $response ); + $status = json_decode( $body ); + return $status; + } + + /** + * Checks whether the current plan (or purchases) of the site already supports the product + * + * @return boolean + */ + public static function has_required_plan() { + $purchases_data = static::get_state_from_wpcom(); + if ( is_wp_error( $purchases_data ) ) { + return false; + } + if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) { + foreach ( $purchases_data as $purchase ) { + if ( + 0 === strpos( $purchase->product_slug, 'jetpack_security' ) || + 0 === strpos( $purchase->product_slug, 'jetpack_complete' ) + ) { + return true; + } + } + } + return false; + } + + /** + * Checks whether product is a bundle. + * + * @return boolean True + */ + public static function is_bundle_product() { + return true; + } + + /** + * Return all the products it contains. + * + * @return Array Product slugs + */ + public static function get_supported_products() { + return array( 'backup', 'scan', 'anti-spam' ); + } + + /** + * Get the URL where the user manages the product + * + * @return ?string + */ + public static function get_manage_url() { + return ''; + } +} diff --git a/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-social.php b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-social.php new file mode 100644 index 00000000..432c3501 --- /dev/null +++ b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-social.php @@ -0,0 +1,135 @@ +<?php +/** + * Search product + * + * @package my-jetpack + */ + +namespace Automattic\Jetpack\My_Jetpack\Products; + +use Automattic\Jetpack\My_Jetpack\Hybrid_Product; +use Automattic\Jetpack\My_Jetpack\Wpcom_Products; + +/** + * Class responsible for handling the Social product + */ +class Social extends Hybrid_Product { + + /** + * The product slug + * + * @var string + */ + public static $slug = 'social'; + + /** + * The Jetpack module name + * + * @var string + */ + public static $module_name = 'publicize'; + + /** + * The slug of the plugin associated with this product. + * + * @var string + */ + public static $plugin_slug = 'jetpack-social'; + + /** + * The filename (id) of the plugin associated with this product. + * + * @var string + */ + public static $plugin_filename = array( + 'jetpack-social/jetpack-social.php', + 'social/jetpack-social.php', + 'jetpack-social-dev/jetpack-social.php', + ); + + /** + * Get the internationalized product name + * + * @return string + */ + public static function get_name() { + return __( 'Social', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product title + * + * @return string + */ + public static function get_title() { + return __( 'Jetpack Social', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product description + * + * @return string + */ + public static function get_description() { + return __( 'Reach your audience on social media', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product long description + * + * @return string + */ + public static function get_long_description() { + return __( 'Promote your content on social media by automatically publishing when you publish on your site.', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized features list + * + * @return array Social features list + */ + public static function get_features() { + return array( + __( 'Post to social networks', 'jetpack-my-jetpack' ), + __( 'Schedule publishing', 'jetpack-my-jetpack' ), + __( 'Supports the major social networks', 'jetpack-my-jetpack' ), + ); + } + + /** + * Get the product pricing details + * + * @return array Pricing details + */ + public static function get_pricing_for_ui() { + return array_merge( + array( + 'available' => true, + 'wpcom_product_slug' => static::get_wpcom_product_slug(), + ), + Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() ) + ); + } + + /** + * Get the WPCOM product slug used to make the purchase + * + * @return string + */ + public static function get_wpcom_product_slug() { + return 'jetpack_social'; + } + + /** + * Get the URL where the user manages the product + * + * @return string + */ + public static function get_manage_url() { + if ( static::is_jetpack_plugin_active() ) { + return admin_url( 'admin.php?page=jetpack#/settings?term=publicize' ); + } elseif ( static::is_plugin_active() ) { + return admin_url( 'admin.php?page=jetpack-social' ); + } + } +} diff --git a/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-videopress.php b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-videopress.php new file mode 100644 index 00000000..d16152da --- /dev/null +++ b/plugins/jetpack/jetpack_vendor/automattic/jetpack-my-jetpack/src/products/class-videopress.php @@ -0,0 +1,125 @@ +<?php +/** + * VideoPress product + * + * @package my-jetpack + */ + +namespace Automattic\Jetpack\My_Jetpack\Products; + +use Automattic\Jetpack\My_Jetpack\Module_Product; +use Automattic\Jetpack\My_Jetpack\Wpcom_Products; + +/** + * Class responsible for handling the VideoPress product + */ +class Videopress extends Module_Product { + + /** + * The product slug + * + * @var string + */ + public static $slug = 'videopress'; + + /** + * The Jetpack module name + * + * @var string + */ + public static $module_name = 'videopress'; + + /** + * Get the internationalized product name + * + * @return string + */ + public static function get_name() { + return __( 'VideoPress', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product title + * + * @return string + */ + public static function get_title() { + return __( 'Jetpack VideoPress', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product description + * + * @return string + */ + public static function get_description() { + return __( 'High quality, ad-free video', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized product long description + * + * @return string + */ + public static function get_long_description() { + return __( 'High-quality, ad-free video built specifically for WordPress.', 'jetpack-my-jetpack' ); + } + + /** + * Get the internationalized features list + * + * @return array Boost features list + */ + public static function get_features() { + return array( + _x( '1TB of storage', 'VideoPress Product Feature', 'jetpack-my-jetpack' ), + _x( 'Built into WordPress editor', 'VideoPress Product Feature', 'jetpack-my-jetpack' ), + _x( 'Ad-free and brandable player', 'VideoPress Product Feature', 'jetpack-my-jetpack' ), + _x( 'Unlimited users', 'VideoPress Product Feature', 'jetpack-my-jetpack' ), + ); + } + + /** + * Get the product princing details + * + * @return array Pricing details + */ + public static function get_pricing_for_ui() { + return array_merge( + array( + 'available' => true, + 'wpcom_product_slug' => static::get_wpcom_product_slug(), + ), + Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() ) + ); + } + + /** + * Get the WPCOM product slug used to make the purchase + * + * @return ?string + */ + public static function get_wpcom_product_slug() { + return 'jetpack_videopress'; + } + + /** + * Get the URL the user is taken after activating the product + * + * @return ?string + */ + public static function get_post_activation_url() { + return ''; // stay in My Jetpack page. + } + + /** + * Get the URL where the user manages the product + * + * @return ?string + */ + public static function get_manage_url() { + if ( static::is_active() ) { + return admin_url( 'admin.php?page=jetpack#/settings?term=videopress' ); + } + } +} |