updated plugin Jetpack Protect
version 1.0.4
This commit is contained in:
@ -0,0 +1,464 @@
|
||||
<?php
|
||||
/**
|
||||
* Class for the Jetpack partner coupon logic.
|
||||
*
|
||||
* @package automattic/jetpack-partner
|
||||
*/
|
||||
|
||||
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 1.6.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 1.6.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 1.6.0
|
||||
*
|
||||
* @param array $supported_presets A list of supported presets.
|
||||
* @return array
|
||||
*/
|
||||
return apply_filters( 'jetpack_partner_coupon_supported_presets', self::$supported_presets );
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,199 @@
|
||||
<?php
|
||||
/**
|
||||
* Jetpack Partner package.
|
||||
*
|
||||
* @package automattic/jetpack-partner
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack;
|
||||
|
||||
/**
|
||||
* This class introduces functionality used by Jetpack hosting partners.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Partner {
|
||||
|
||||
/**
|
||||
* Affiliate code.
|
||||
*/
|
||||
const AFFILIATE_CODE = 'affiliate';
|
||||
|
||||
/**
|
||||
* Subsidiary id code.
|
||||
*/
|
||||
const SUBSIDIARY_CODE = 'subsidiary';
|
||||
|
||||
/**
|
||||
* Singleton instance.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @var Partner This class instance.
|
||||
*/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Partner constructor.
|
||||
*/
|
||||
private function __construct() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the class or returns the singleton.
|
||||
*
|
||||
* @return Partner | false
|
||||
* @since 1.0.0
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @param array $params The parameters array.
|
||||
*
|
||||
* @return array
|
||||
* @since 1.5.0
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @param array $params The parameters array.
|
||||
*
|
||||
* @return array
|
||||
* @since 1.5.0
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @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.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @param string $type The partner code.
|
||||
* @return array
|
||||
* @since 1.5.0
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @param string $type This can be either 'affiliate' or 'subsidiary'. Returns empty string when code is unknown.
|
||||
*
|
||||
* @return string The partner code.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function get_partner_code( $type ) {
|
||||
switch ( $type ) {
|
||||
case self::AFFILIATE_CODE:
|
||||
/**
|
||||
* Allow to filter the affiliate code.
|
||||
*
|
||||
* @param string $affiliate_code The affiliate code, blank by default.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @since-jetpack 6.9.0
|
||||
*/
|
||||
return apply_filters( 'jetpack_affiliate_code', get_option( 'jetpack_affiliate_code', '' ) );
|
||||
case self::SUBSIDIARY_CODE:
|
||||
/**
|
||||
* Allow to filter the partner subsidiary id.
|
||||
*
|
||||
* @param string $subsidiary_id The partner subsidiary id, blank by default.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user