laipower/wp-content/plugins/jetpack-protect/jetpack_vendor/automattic/jetpack-my-jetpack/src/class-wpcom-products.php

274 lines
7.7 KiB
PHP

<?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;
use Automattic\Jetpack\Status\Visitor;
use Jetpack_Options;
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';
const MY_JETPACK_PURCHASES_TRANSIENT_KEY = 'my-jetpack-purchases';
/**
* 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' );
$ip = ( new Visitor() )->get_ip( true );
$headers = array(
'X-Forwarded-For' => $ip,
);
// If has a blog id, use connected endpoint.
if ( $blog_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' => $headers,
)
);
} else {
$endpoint = 'https://public-api.wordpress.com/rest/v1.1/products?locale=' . get_user_locale() . '&type=jetpack';
$wpcom_request = wp_remote_get(
esc_url_raw( $endpoint ),
array(
'headers' => $headers,
)
);
}
$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.
* @param bool $renew_cache A flag to force the cache to be renewed.
*
* @return ?Object The product details if found
*/
public static function get_product( $product_slug, $renew_cache = false ) {
$products = self::get_products( $renew_cache );
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;
$is_introductory_offer = false;
$introductory_offer = null;
// Get/compute the discounted price.
if ( isset( $product->introductory_offer->cost_per_interval ) ) {
$discount_price = $product->introductory_offer->cost_per_interval;
$is_introductory_offer = true;
$introductory_offer = $product->introductory_offer;
}
$pricing = array(
'currency_code' => $product->currency_code,
'full_price' => $cost,
'discount_price' => $discount_price,
'is_introductory_offer' => $is_introductory_offer,
'introductory_offer' => $introductory_offer,
'product_term' => $product->product_term,
);
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;
}
/**
* Gets the site purchases from WPCOM.
*
* @return Object|WP_Error
*/
public static function get_site_current_purchases() {
static $purchases = null;
if ( $purchases !== null ) {
return $purchases;
}
// Check for a cached value before doing lookup
$stored_purchases = get_transient( self::MY_JETPACK_PURCHASES_TRANSIENT_KEY );
if ( $stored_purchases !== false ) {
return $stored_purchases;
}
$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 );
$purchases = json_decode( $body );
// Set short transient to help with repeated lookups on the same page load
set_transient( self::MY_JETPACK_PURCHASES_TRANSIENT_KEY, $purchases, 5 );
return $purchases;
}
}