updated plugin Jetpack Protect version 2.0.0

This commit is contained in:
2024-02-08 12:31:43 +00:00
committed by Gitium
parent ce653dd56c
commit 8d5e7cc070
192 changed files with 5244 additions and 2003 deletions

View File

@ -10,6 +10,7 @@ namespace Automattic\Jetpack\Connection;
use Automattic\Jetpack\A8c_Mc_Stats;
use Automattic\Jetpack\Constants;
use Automattic\Jetpack\Heartbeat;
use Automattic\Jetpack\Partner;
use Automattic\Jetpack\Roles;
use Automattic\Jetpack\Status;
use Automattic\Jetpack\Status\Host;
@ -138,6 +139,9 @@ class Manager {
// Initialize token locks.
new Tokens_Locks();
// Initial Partner management.
Partner::init();
}
/**
@ -274,7 +278,7 @@ class Manager {
$jetpack_methods = array();
foreach ( $methods as $method => $callback ) {
if ( 0 === strpos( $method, 'jetpack.' ) ) {
if ( str_starts_with( $method, 'jetpack.' ) ) {
$jetpack_methods[ $method ] = $callback;
}
}
@ -441,7 +445,7 @@ class Manager {
$post_data = $_POST;
$file_hashes = array();
foreach ( $post_data as $post_data_key => $post_data_value ) {
if ( 0 !== strpos( $post_data_key, '_jetpack_file_hmac_' ) ) {
if ( ! str_starts_with( $post_data_key, '_jetpack_file_hmac_' ) ) {
continue;
}
$post_data_key = substr( $post_data_key, strlen( '_jetpack_file_hmac_' ) );
@ -1188,7 +1192,7 @@ class Manager {
$jetpack_public = false;
}
\Jetpack_Options::update_options(
Jetpack_Options::update_options(
array(
'id' => (int) $registration_details->jetpack_id,
'public' => $jetpack_public,
@ -1199,6 +1203,13 @@ class Manager {
$this->get_tokens()->update_blog_token( (string) $registration_details->jetpack_secret );
if ( ! Jetpack_Options::get_option( 'id' ) || ! $this->get_tokens()->get_access_token() ) {
return new WP_Error(
'connection_data_save_failed',
'Failed to save connection data in the database'
);
}
$alternate_authorization_url = isset( $registration_details->alternate_authorization_url ) ? $registration_details->alternate_authorization_url : '';
add_filter(
@ -1916,6 +1927,7 @@ class Manager {
'site_created' => $this->get_assumed_site_creation_date(),
'allow_site_connection' => ! $this->has_connected_owner(),
'calypso_env' => ( new Host() )->get_calypso_env(),
'source' => ( new Host() )->get_source_query(),
)
);
@ -1923,7 +1935,10 @@ class Manager {
$api_url = $this->api_url( 'authorize' );
return add_query_arg( $body, $api_url );
$url = add_query_arg( $body, $api_url );
/** This filter is documented in plugins/jetpack/class-jetpack.php */
return apply_filters( 'jetpack_build_authorize_url', $url );
}
/**
@ -2544,18 +2559,36 @@ class Manager {
/**
* Get the WPCOM or self-hosted site ID.
*
* @return int|WP_Error
* @param bool $quiet Return null instead of an error.
*
* @return int|WP_Error|null
*/
public static function get_site_id() {
public static function get_site_id( $quiet = false ) {
$is_wpcom = ( defined( 'IS_WPCOM' ) && IS_WPCOM );
$site_id = $is_wpcom ? get_current_blog_id() : \Jetpack_Options::get_option( 'id' );
if ( ! $site_id ) {
return new \WP_Error(
'unavailable_site_id',
__( 'Sorry, something is wrong with your Jetpack connection.', 'jetpack-connection' ),
403
);
return $quiet
? null
: new \WP_Error(
'unavailable_site_id',
__( 'Sorry, something is wrong with your Jetpack connection.', 'jetpack-connection' ),
403
);
}
return (int) $site_id;
}
/**
* Check if Jetpack is ready for uninstall cleanup.
*
* @param string $current_plugin_slug The current plugin's slug.
*
* @return bool
*/
public static function is_ready_for_cleanup( $current_plugin_slug ) {
$active_plugins = get_option( Plugin_Storage::ACTIVE_PLUGINS_OPTION_NAME );
return empty( $active_plugins ) || ! is_array( $active_plugins )
|| ( count( $active_plugins ) === 1 && array_key_exists( $current_plugin_slug, $active_plugins ) );
}
}

View File

@ -12,7 +12,7 @@ namespace Automattic\Jetpack\Connection;
*/
class Package_Version {
const PACKAGE_VERSION = '1.58.2';
const PACKAGE_VERSION = '2.2.0';
const PACKAGE_SLUG = 'connection';

View File

@ -0,0 +1,466 @@
<?php
/**
* Class for the Jetpack partner coupon logic.
*
* @package automattic/jetpack-connection
*/
namespace Automattic\Jetpack;
use Automattic\Jetpack\Connection\Client as Connection_Client;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Jetpack_Options;
/**
* Disable direct access.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Jetpack_Partner_Coupon
*
* @since partner-1.6.0
* @since 2.0.0
*/
class Partner_Coupon {
/**
* Name of the Jetpack_Option coupon option.
*
* @var string
*/
public static $coupon_option = 'partner_coupon';
/**
* Name of the Jetpack_Option added option.
*
* @var string
*/
public static $added_option = 'partner_coupon_added';
/**
* Name of "last availability check" transient.
*
* @var string
*/
public static $last_check_transient = 'jetpack_partner_coupon_last_check';
/**
* Callable that executes a blog-authenticated request.
*
* @var callable
*/
protected $request_as_blog;
/**
* Jetpack_Partner_Coupon
*
* @var Partner_Coupon|null
**/
private static $instance = null;
/**
* A list of supported partners.
*
* @var array
*/
private static $supported_partners = array(
'IONOS' => array(
'name' => 'IONOS',
'logo' => array(
'src' => '/images/ionos-logo.jpg',
'width' => 119,
'height' => 32,
),
),
);
/**
* A list of supported presets.
*
* @var array
*/
private static $supported_presets = array(
'IONA' => 'jetpack_backup_daily',
);
/**
* Get singleton instance of class.
*
* @return Partner_Coupon
*/
public static function get_instance() {
if ( self::$instance === null ) {
self::$instance = new Partner_Coupon( array( Connection_Client::class, 'wpcom_json_api_request_as_blog' ) );
}
return self::$instance;
}
/**
* Constructor.
*
* @param callable $request_as_blog Callable that executes a blog-authenticated request.
*/
public function __construct( $request_as_blog ) {
$this->request_as_blog = $request_as_blog;
}
/**
* Register hooks to catch and purge coupon.
*
* @param string $plugin_slug The plugin slug to differentiate between Jetpack connections.
* @param string $redirect_location The location we should redirect to after catching the coupon.
*/
public static function register_coupon_admin_hooks( $plugin_slug, $redirect_location ) {
$instance = self::get_instance();
// We have to use an anonymous function, so we can pass along relevant information
// and not have to hardcode values for a single plugin.
// This open up the opportunity for e.g. the "all-in-one" and backup plugins
// to both implement partner coupon logic.
add_action(
'admin_init',
function () use ( $plugin_slug, $redirect_location, $instance ) {
$instance->catch_coupon( $plugin_slug, $redirect_location );
$instance->maybe_purge_coupon( $plugin_slug );
}
);
}
/**
* Catch partner coupon and redirect to claim component.
*
* @param string $plugin_slug The plugin slug to differentiate between Jetpack connections.
* @param string $redirect_location The location we should redirect to after catching the coupon.
*/
public function catch_coupon( $plugin_slug, $redirect_location ) {
// Accept and store a partner coupon if present, and redirect to Jetpack connection screen.
$partner_coupon = isset( $_GET['jetpack-partner-coupon'] ) ? sanitize_text_field( wp_unslash( $_GET['jetpack-partner-coupon'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( $partner_coupon ) {
Jetpack_Options::update_options(
array(
self::$coupon_option => $partner_coupon,
self::$added_option => time(),
)
);
$connection = new Connection_Manager( $plugin_slug );
if ( $connection->is_connected() ) {
$redirect_location = add_query_arg( array( 'showCouponRedemption' => 1 ), $redirect_location );
wp_safe_redirect( $redirect_location );
} else {
wp_safe_redirect( $redirect_location );
}
}
}
/**
* Purge partner coupon.
*
* We try to remotely check if a coupon looks valid. We also automatically purge
* partner coupons after a certain amount of time to prevent unnecessary look-ups
* and/or promoting a product for months or years in the future due to unknown
* errors.
*
* @param string $plugin_slug The plugin slug to differentiate between Jetpack connections.
*/
public function maybe_purge_coupon( $plugin_slug ) {
// Only run coupon checks on Jetpack admin pages.
// The "admin-ui" package is responsible for registering the Jetpack admin
// page for all Jetpack plugins and has hardcoded the settings page to be
// "jetpack", so we shouldn't need to allow for dynamic/custom values.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( ! isset( $_GET['page'] ) || 'jetpack' !== $_GET['page'] ) {
return;
}
if ( ( new Status() )->is_offline_mode() ) {
return;
}
$connection = new Connection_Manager( $plugin_slug );
if ( ! $connection->is_connected() ) {
return;
}
if ( $this->maybe_purge_coupon_by_added_date() ) {
return;
}
// Limit checks to happen once a minute at most.
if ( get_transient( self::$last_check_transient ) ) {
return;
}
set_transient( self::$last_check_transient, true, MINUTE_IN_SECONDS );
$this->maybe_purge_coupon_by_availability_check();
}
/**
* Purge coupon based on local added date.
*
* We automatically remove the coupon after a month to "self-heal" if
* something in the claim process has broken with the site.
*
* @return bool Return whether we should skip further purge checks.
*/
protected function maybe_purge_coupon_by_added_date() {
$date = Jetpack_Options::get_option( self::$added_option, '' );
if ( empty( $date ) ) {
return true;
}
$expire_date = strtotime( '+30 days', $date );
$today = time();
if ( $today >= $expire_date ) {
$this->delete_coupon_data();
return true;
}
return false;
}
/**
* Purge coupon based on availability check.
*
* @return bool Return whether we deleted coupon data.
*/
protected function maybe_purge_coupon_by_availability_check() {
$blog_id = Jetpack_Options::get_option( 'id', false );
if ( ! $blog_id ) {
return false;
}
$coupon = self::get_coupon();
if ( ! $coupon ) {
return false;
}
$response = call_user_func_array(
$this->request_as_blog,
array(
add_query_arg(
array( 'coupon_code' => $coupon['coupon_code'] ),
sprintf(
'/sites/%d/jetpack-partner/coupon/v1/site/coupon',
$blog_id
)
),
2,
array( 'method' => 'GET' ),
null,
'wpcom',
)
);
$body = json_decode( wp_remote_retrieve_body( $response ), true );
if (
200 === wp_remote_retrieve_response_code( $response ) &&
is_array( $body ) &&
isset( $body['available'] ) &&
false === $body['available']
) {
$this->delete_coupon_data();
return true;
}
return false;
}
/**
* Delete all coupon data.
*/
protected function delete_coupon_data() {
Jetpack_Options::delete_option(
array(
self::$coupon_option,
self::$added_option,
)
);
}
/**
* Get partner coupon data.
*
* @return array|bool
*/
public static function get_coupon() {
$coupon_code = Jetpack_Options::get_option( self::$coupon_option, '' );
if ( ! is_string( $coupon_code ) || empty( $coupon_code ) ) {
return false;
}
$instance = self::get_instance();
$partner = $instance->get_coupon_partner( $coupon_code );
if ( ! $partner ) {
return false;
}
$preset = $instance->get_coupon_preset( $coupon_code );
if ( ! $preset ) {
return false;
}
$product = $instance->get_coupon_product( $preset );
if ( ! $product ) {
return false;
}
return array(
'coupon_code' => $coupon_code,
'partner' => $partner,
'preset' => $preset,
'product' => $product,
);
}
/**
* Get coupon partner.
*
* @param string $coupon_code Coupon code to go through.
* @return array|bool
*/
private function get_coupon_partner( $coupon_code ) {
if ( ! is_string( $coupon_code ) || false === strpos( $coupon_code, '_' ) ) {
return false;
}
$prefix = strtok( $coupon_code, '_' );
$supported_partners = $this->get_supported_partners();
if ( ! isset( $supported_partners[ $prefix ] ) ) {
return false;
}
return array(
'name' => $supported_partners[ $prefix ]['name'],
'prefix' => $prefix,
'logo' => isset( $supported_partners[ $prefix ]['logo'] ) ? $supported_partners[ $prefix ]['logo'] : null,
);
}
/**
* Get coupon product.
*
* @param string $coupon_preset The preset we wish to find a product for.
* @return array|bool
*/
private function get_coupon_product( $coupon_preset ) {
if ( ! is_string( $coupon_preset ) ) {
return false;
}
/**
* Allow for plugins to register supported products.
*
* @since 1.6.0
*
* @param array A list of product details.
* @return array
*/
$product_details = apply_filters( 'jetpack_partner_coupon_products', array() );
$product_slug = $this->get_supported_presets()[ $coupon_preset ];
foreach ( $product_details as $product ) {
if ( ! $this->array_keys_exist( array( 'title', 'slug', 'description', 'features' ), $product ) ) {
continue;
}
if ( $product_slug === $product['slug'] ) {
return $product;
}
}
return false;
}
/**
* Checks if multiple keys are present in an array.
*
* @param array $needles The keys we wish to check for.
* @param array $haystack The array we want to compare keys against.
*
* @return bool
*/
private function array_keys_exist( $needles, $haystack ) {
foreach ( $needles as $needle ) {
if ( ! isset( $haystack[ $needle ] ) ) {
return false;
}
}
return true;
}
/**
* Get coupon preset.
*
* @param string $coupon_code Coupon code to go through.
* @return string|bool
*/
private function get_coupon_preset( $coupon_code ) {
if ( ! is_string( $coupon_code ) ) {
return false;
}
$regex = '/^.*?_(?P<slug>.*?)_.+$/';
$matches = array();
if ( ! preg_match( $regex, $coupon_code, $matches ) ) {
return false;
}
return isset( $this->get_supported_presets()[ $matches['slug'] ] ) ? $matches['slug'] : false;
}
/**
* Get supported partners.
*
* @return array
*/
private function get_supported_partners() {
/**
* Allow external code to add additional supported partners.
*
* @since partner-1.6.0
* @since 2.0.0
*
* @param array $supported_partners A list of supported partners.
* @return array
*/
return apply_filters( 'jetpack_partner_coupon_supported_partners', self::$supported_partners );
}
/**
* Get supported presets.
*
* @return array
*/
private function get_supported_presets() {
/**
* Allow external code to add additional supported presets.
*
* @since partner-1.6.0
* @since 2.0.0
*
* @param array $supported_presets A list of supported presets.
* @return array
*/
return apply_filters( 'jetpack_partner_coupon_supported_presets', self::$supported_presets );
}
}

View File

@ -0,0 +1,215 @@
<?php
/**
* Jetpack Partner utilities.
*
* @package automattic/jetpack-connection
*/
namespace Automattic\Jetpack;
/**
* This class introduces functionality used by Jetpack hosting partners.
*
* @since partner-1.0.0
* @since 2.0.0
*/
class Partner {
/**
* Affiliate code.
*/
const AFFILIATE_CODE = 'affiliate';
/**
* Subsidiary id code.
*/
const SUBSIDIARY_CODE = 'subsidiary';
/**
* Singleton instance.
*
* @since partner-1.0.0
* @since 2.0.0
*
* @var Partner This class instance.
*/
private static $instance = null;
/**
* Partner constructor.
*/
private function __construct() {
}
/**
* Initializes the class or returns the singleton.
*
* @since partner-1.0.0
* @since 2.0.0
*
* @return Partner | false
*/
public static function init() {
if ( self::$instance === null ) {
self::$instance = new Partner();
add_filter( 'jetpack_build_authorize_url', array( self::$instance, 'add_subsidiary_id_as_query_arg' ) );
add_filter( 'jetpack_build_authorize_url', array( self::$instance, 'add_affiliate_code_as_query_arg' ) );
add_filter( 'jetpack_build_connection_url', array( self::$instance, 'add_subsidiary_id_as_query_arg' ) );
add_filter( 'jetpack_build_connection_url', array( self::$instance, 'add_affiliate_code_as_query_arg' ) );
add_filter( 'jetpack_register_request_body', array( self::$instance, 'add_subsidiary_id_to_params_array' ) );
add_filter( 'jetpack_register_request_body', array( self::$instance, 'add_affiliate_code_to_params_array' ) );
}
return self::$instance;
}
/**
* Adds the partner subsidiary code to the passed URL.
*
* @param string $url The URL.
*
* @return string
*/
public function add_subsidiary_id_as_query_arg( $url ) {
return $this->add_code_as_query_arg( self::SUBSIDIARY_CODE, $url );
}
/**
* Adds the affiliate code to the passed URL.
*
* @param string $url The URL.
*
* @return string
*/
public function add_affiliate_code_as_query_arg( $url ) {
return $this->add_code_as_query_arg( self::AFFILIATE_CODE, $url );
}
/**
* Adds the partner subsidiary code to the passed array.
*
* @since partner-1.5.0
* @since 2.0.0
*
* @param array $params The parameters array.
*
* @return array
*/
public function add_subsidiary_id_to_params_array( $params ) {
if ( ! is_array( $params ) ) {
return $params;
}
return array_merge( $params, $this->get_code_as_array( self::SUBSIDIARY_CODE ) );
}
/**
* Adds the affiliate code to the passed array.
*
* @since partner-1.5.0
* @since 2.0.0
*
* @param array $params The parameters array.
*
* @return array
*/
public function add_affiliate_code_to_params_array( $params ) {
if ( ! is_array( $params ) ) {
return $params;
}
return array_merge( $params, $this->get_code_as_array( self::AFFILIATE_CODE ) );
}
/**
* Returns the passed URL with the partner code added as a URL query arg.
*
* @since partner-1.0.0
* @since 2.0.0
*
* @param string $type The partner code.
* @param string $url The URL where the partner subsidiary id will be added.
*
* @return string The passed URL with the partner code added.
*/
public function add_code_as_query_arg( $type, $url ) {
return add_query_arg( $this->get_code_as_array( $type ), $url );
}
/**
* Gets the partner code in an associative array format
*
* @since partner-1.5.0
* @since 2.0.0
*
* @param string $type The partner code.
* @return array
*/
private function get_code_as_array( $type ) {
switch ( $type ) {
case self::AFFILIATE_CODE:
$query_arg_name = 'aff';
break;
case self::SUBSIDIARY_CODE:
$query_arg_name = 'subsidiaryId';
break;
default:
return array();
}
$code = $this->get_partner_code( $type );
if ( '' === $code ) {
return array();
}
return array( $query_arg_name => $code );
}
/**
* Returns a partner code.
*
* @since partner-1.0.0
* @since 2.0.0
*
* @param string $type This can be either 'affiliate' or 'subsidiary'. Returns empty string when code is unknown.
*
* @return string The partner code.
*/
public function get_partner_code( $type ) {
switch ( $type ) {
case self::AFFILIATE_CODE:
/**
* Allow to filter the affiliate code.
*
* @since partner-1.0.0
* @since-jetpack 6.9.0
* @since 2.0.0
*
* @param string $affiliate_code The affiliate code, blank by default.
*/
return apply_filters( 'jetpack_affiliate_code', get_option( 'jetpack_affiliate_code', '' ) );
case self::SUBSIDIARY_CODE:
/**
* Allow to filter the partner subsidiary id.
*
* @since partner-1.0.0
* @since 2.0.0
*
* @param string $subsidiary_id The partner subsidiary id, blank by default.
*/
return apply_filters(
'jetpack_partner_subsidiary_id',
get_option( 'jetpack_partner_subsidiary_id', '' )
);
default:
return '';
}
}
/**
* Resets the singleton for testing purposes.
*/
public static function reset() {
self::$instance = null;
}
}

View File

@ -90,6 +90,7 @@ class Urls {
$option_key = self::HTTPS_CHECK_OPTION_PREFIX . $callable;
$parsed_url = wp_parse_url( $new_value );
if ( ! $parsed_url ) {
return $new_value;
}
@ -98,7 +99,12 @@ class Urls {
} else {
$scheme = '';
}
$scheme_history = get_option( $option_key, array() );
$scheme_history = get_option( $option_key, array() );
if ( ! is_array( $scheme_history ) ) {
$scheme_history = array();
}
$scheme_history[] = $scheme;
// Limit length to self::HTTPS_CHECK_HISTORY.

View File

@ -60,6 +60,7 @@ class XMLRPC_Async_Call {
self::$clients[ $client_blog_id ][ $user_id ] = new Jetpack_IXR_ClientMulticall( array( 'user_id' => $user_id ) );
}
// https://plugins.trac.wordpress.org/ticket/2041
if ( function_exists( 'ignore_user_abort' ) ) {
ignore_user_abort( true );
}

View File

@ -107,9 +107,7 @@ class Authorize_Redirect {
remove_filter( 'jetpack_connect_request_body', array( __CLASS__, 'filter_connect_request_body' ) );
remove_filter( 'jetpack_connect_redirect_url', array( __CLASS__, 'filter_connect_redirect_url' ) );
/**
* This filter is documented in plugins/jetpack/class-jetpack.php
*/
/** This filter is documented in plugins/jetpack/class-jetpack.php */
return apply_filters( 'jetpack_build_authorize_url', $url );
}