Нет описания

class.jetpack-plan.php 8.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. <?php //phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
  2. /**
  3. * Handles fetching of the site's plan and products from WordPress.com and caching values locally.
  4. *
  5. * Not to be confused with the `Jetpack_Plans` class (in `_inc/lib/plans.php`), which
  6. * fetches general information about all available plans from WordPress.com, side-effect free.
  7. *
  8. * @package automattic/jetpack
  9. */
  10. use Automattic\Jetpack\Connection\Client;
  11. /**
  12. * Provides methods methods for fetching the site's plan and products from WordPress.com.
  13. */
  14. class Jetpack_Plan {
  15. /**
  16. * A cache variable to hold the active plan for the current request.
  17. *
  18. * @var array
  19. */
  20. private static $active_plan_cache;
  21. /**
  22. * The name of the option that will store the site's plan.
  23. *
  24. * @var string
  25. */
  26. const PLAN_OPTION = 'jetpack_active_plan';
  27. /**
  28. * The name of the option that will store the site's products.
  29. *
  30. * @var string
  31. */
  32. const SITE_PRODUCTS_OPTION = 'jetpack_site_products';
  33. const PLAN_DATA = array(
  34. 'free' => array(
  35. 'plans' => array(
  36. 'jetpack_free',
  37. ),
  38. 'supports' => array(
  39. 'opentable',
  40. 'calendly',
  41. 'send-a-message',
  42. 'whatsapp-button',
  43. 'social-previews',
  44. 'videopress',
  45. 'core/video',
  46. 'core/cover',
  47. 'core/audio',
  48. ),
  49. ),
  50. 'personal' => array(
  51. 'plans' => array(
  52. 'jetpack_personal',
  53. 'jetpack_personal_monthly',
  54. 'personal-bundle',
  55. 'personal-bundle-monthly',
  56. 'personal-bundle-2y',
  57. ),
  58. 'supports' => array(
  59. 'akismet',
  60. 'recurring-payments',
  61. 'premium-content/container',
  62. 'videopress',
  63. ),
  64. ),
  65. 'premium' => array(
  66. 'plans' => array(
  67. 'jetpack_premium',
  68. 'jetpack_premium_monthly',
  69. 'value_bundle',
  70. 'value_bundle-monthly',
  71. 'value_bundle-2y',
  72. ),
  73. 'supports' => array(
  74. 'donations',
  75. 'simple-payments',
  76. 'vaultpress',
  77. 'videopress',
  78. ),
  79. ),
  80. 'security' => array(
  81. 'plans' => array(
  82. 'jetpack_security_daily',
  83. 'jetpack_security_daily_monthly',
  84. 'jetpack_security_realtime',
  85. 'jetpack_security_realtime_monthly',
  86. ),
  87. 'supports' => array(),
  88. ),
  89. 'business' => array(
  90. 'plans' => array(
  91. 'jetpack_business',
  92. 'jetpack_business_monthly',
  93. 'business-bundle',
  94. 'business-bundle-monthly',
  95. 'business-bundle-2y',
  96. 'ecommerce-bundle',
  97. 'ecommerce-bundle-monthly',
  98. 'ecommerce-bundle-2y',
  99. 'vip',
  100. ),
  101. 'supports' => array(),
  102. ),
  103. 'complete' => array(
  104. 'plans' => array(
  105. 'jetpack_complete',
  106. 'jetpack_complete_monthly',
  107. ),
  108. 'supports' => array(),
  109. ),
  110. );
  111. /**
  112. * Given a response to the `/sites/%d` endpoint, will parse the response and attempt to set the
  113. * site's plan and products from the response.
  114. *
  115. * @param array $response The response from `/sites/%d`.
  116. * @return bool Was the plan successfully updated?
  117. */
  118. public static function update_from_sites_response( $response ) {
  119. // Bail if there was an error or malformed response.
  120. if ( is_wp_error( $response ) || ! is_array( $response ) || ! isset( $response['body'] ) ) {
  121. return false;
  122. }
  123. $body = wp_remote_retrieve_body( $response );
  124. if ( is_wp_error( $body ) ) {
  125. return false;
  126. }
  127. // Decode the results.
  128. $results = json_decode( $body, true );
  129. if ( ! is_array( $results ) ) {
  130. return false;
  131. }
  132. if ( isset( $results['products'] ) ) {
  133. // Store the site's products in an option and return true if updated.
  134. self::store_data_in_option( self::SITE_PRODUCTS_OPTION, $results['products'] );
  135. }
  136. if ( ! isset( $results['plan'] ) ) {
  137. return false;
  138. }
  139. $current_plan = get_option( self::PLAN_OPTION, array() );
  140. if ( ! empty( $current_plan ) && $current_plan === $results['plan'] ) {
  141. // Bail if the plans array hasn't changed.
  142. return false;
  143. }
  144. // Store the new plan in an option and return true if updated.
  145. $result = self::store_data_in_option( self::PLAN_OPTION, $results['plan'] );
  146. if ( $result ) {
  147. // Reset the cache since we've just updated the plan.
  148. self::$active_plan_cache = null;
  149. }
  150. return $result;
  151. }
  152. /**
  153. * Store data in an option.
  154. *
  155. * @param string $option The name of the option that will store the data.
  156. * @param array $data Data to be store in an option.
  157. * @return bool Were the subscriptions successfully updated?
  158. */
  159. private static function store_data_in_option( $option, $data ) {
  160. $result = update_option( $option, $data, true );
  161. // If something goes wrong with the update, so delete the current option and then update it.
  162. if ( ! $result ) {
  163. delete_option( $option );
  164. $result = update_option( $option, $data, true );
  165. }
  166. return $result;
  167. }
  168. /**
  169. * Make an API call to WordPress.com for plan status
  170. *
  171. * @uses Jetpack_Options::get_option()
  172. * @uses Client::wpcom_json_api_request_as_blog()
  173. * @uses update_option()
  174. *
  175. * @access public
  176. * @static
  177. *
  178. * @return bool True if plan is updated, false if no update
  179. */
  180. public static function refresh_from_wpcom() {
  181. // Make the API request.
  182. $request = sprintf( '/sites/%d', Jetpack_Options::get_option( 'id' ) );
  183. $response = Client::wpcom_json_api_request_as_blog( $request, '1.1' );
  184. return self::update_from_sites_response( $response );
  185. }
  186. /**
  187. * Get the plan that this Jetpack site is currently using.
  188. *
  189. * @uses get_option()
  190. *
  191. * @access public
  192. * @static
  193. *
  194. * @return array Active Jetpack plan details
  195. */
  196. public static function get() {
  197. // this can be expensive to compute so we cache for the duration of a request.
  198. if ( is_array( self::$active_plan_cache ) && ! empty( self::$active_plan_cache ) ) {
  199. return self::$active_plan_cache;
  200. }
  201. $plan = get_option( self::PLAN_OPTION, array() );
  202. // Set the default options.
  203. $plan = wp_parse_args(
  204. $plan,
  205. array(
  206. 'product_slug' => 'jetpack_free',
  207. 'class' => 'free',
  208. 'features' => array(
  209. 'active' => array(),
  210. ),
  211. )
  212. );
  213. list( $plan['class'], $supports ) = self::get_class_and_features( $plan['product_slug'] );
  214. // get available features.
  215. foreach ( Jetpack::get_available_modules() as $module_slug ) {
  216. $module = Jetpack::get_module( $module_slug );
  217. if ( ! isset( $module ) || ! is_array( $module ) ) {
  218. continue;
  219. }
  220. if ( in_array( 'free', $module['plan_classes'], true ) || in_array( $plan['class'], $module['plan_classes'], true ) ) {
  221. $supports[] = $module_slug;
  222. }
  223. }
  224. $plan['supports'] = $supports;
  225. self::$active_plan_cache = $plan;
  226. return $plan;
  227. }
  228. /**
  229. * Get the site's products.
  230. *
  231. * @uses get_option()
  232. *
  233. * @access public
  234. * @static
  235. *
  236. * @return array Active Jetpack products
  237. */
  238. public static function get_products() {
  239. return get_option( self::SITE_PRODUCTS_OPTION, array() );
  240. }
  241. /**
  242. * Get the class of plan and a list of features it supports
  243. *
  244. * @param string $plan_slug The plan that we're interested in.
  245. * @return array Two item array, the plan class and the an array of features.
  246. */
  247. private static function get_class_and_features( $plan_slug ) {
  248. $features = array();
  249. foreach ( self::PLAN_DATA as $class => $details ) {
  250. $features = array_merge( $features, $details['supports'] );
  251. if ( in_array( $plan_slug, $details['plans'], true ) ) {
  252. return array( $class, $features );
  253. }
  254. }
  255. return array( 'free', self::PLAN_DATA['free']['supports'] );
  256. }
  257. /**
  258. * Gets the minimum plan slug that supports the given feature
  259. *
  260. * @param string $feature The name of the feature.
  261. * @return string|bool The slug for the minimum plan that supports.
  262. * the feature or false if not found
  263. */
  264. public static function get_minimum_plan_for_feature( $feature ) {
  265. foreach ( self::PLAN_DATA as $details ) {
  266. if ( in_array( $feature, $details['supports'], true ) ) {
  267. return $details['plans'][0];
  268. }
  269. }
  270. return false;
  271. }
  272. /**
  273. * Determine whether the active plan supports a particular feature
  274. *
  275. * @uses Jetpack_Plan::get()
  276. *
  277. * @access public
  278. * @static
  279. *
  280. * @param string $feature The module or feature to check.
  281. *
  282. * @return bool True if plan supports feature, false if not
  283. */
  284. public static function supports( $feature ) {
  285. // Search product bypasses plan feature check.
  286. if ( 'search' === $feature && (bool) get_option( 'has_jetpack_search_product' ) ) {
  287. return true;
  288. }
  289. // As of Q3 2021 - a videopress free tier is available to all plans.
  290. if ( 'videopress' === $feature ) {
  291. return true;
  292. }
  293. $plan = self::get();
  294. // Manually mapping WordPress.com features to Jetpack module slugs.
  295. foreach ( $plan['features']['active'] as $wpcom_feature ) {
  296. switch ( $wpcom_feature ) {
  297. case 'wordads-jetpack':
  298. // WordAds are supported for this site.
  299. if ( 'wordads' === $feature ) {
  300. return true;
  301. }
  302. break;
  303. }
  304. }
  305. if (
  306. in_array( $feature, $plan['supports'], true )
  307. || in_array( $feature, $plan['features']['active'], true )
  308. ) {
  309. return true;
  310. }
  311. return false;
  312. }
  313. }