PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.3-beta
16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 All 507 releases
← All changes | vendor/automattic/jetpack-plans/src/class-current-plan.php +174 -19 13.7.2 → 16.3-beta View file →
@@ -21,8 +21,25 @@
21 21 */
22 22 private static $active_plan_cache;
23 23
24 24 /**
25 + * Simple Site-specific features available.
26 + * Their calculation can be expensive and slow, so we're caching it for the request.
27 + *
28 + * @var array Site-specific features
29 + */
30 + private static $simple_site_specific_features = array();
31 +
32 + /**
33 + * Atomic site-specific features available, cached for the request alongside the Simple ones.
34 + *
35 + * Not keyed on a blog ID: an Atomic site can only ever answer for itself.
36 + *
37 + * @var array|null Site-specific features.
38 + */
39 + private static $atomic_site_specific_features = null;
40 +
41 + /**
25 42 * The name of the option that will store the site's plan.
26 43 *
27 44 * @var string
28 45 */
@@ -54,8 +71,11 @@
54 71
55 72 'core/video',
56 73 'core/cover',
57 74 'core/audio',
75 + 'multistep-form',
76 + 'form-webhooks',
77 + 'form-conditional-logic',
58 78 ),
59 79 ),
60 80 'personal' => array(
61 81 'plans' => array(
@@ -126,9 +146,11 @@
126 146 'wooexpress-medium-bundle-yearly',
127 147 'wooexpress-medium-bundle-monthly',
128 148 'wp_com_hundred_year_bundle_centennially',
129 149 ),
130 - 'supports' => array(),
150 + 'supports' => array(
151 + 'ai-seo-enhancer',
152 + ),
131 153 ),
132 154
133 155 'complete' => array(
134 156 'plans' => array(
@@ -135,9 +157,12 @@
135 157 'jetpack_complete',
136 158 'jetpack_complete_monthly',
137 159 'vip',
138 160 ),
139 - 'supports' => array(),
161 + 'supports' => array(
162 + 'field-file', // Forms
163 + 'social-image-generator',
164 + ),
140 165 ),
141 166 );
142 167
143 168 /**
@@ -157,33 +182,42 @@
157 182 if ( is_wp_error( $body ) ) {
158 183 return false;
159 184 }
160 185
161 - // Decode the results.
162 - $results = json_decode( $body, true );
186 + return self::update_from_site_record( json_decode( $body, true ) );
187 + }
163 188
164 - if ( ! is_array( $results ) ) {
189 + /**
190 + * Given a decoded `/sites/%d` record, attempt to set the site's plan and products from it.
191 + *
192 + * @since 0.12.0
193 + *
194 + * @param array $record The decoded site record from the WordPress.com `/sites/%d` endpoint.
195 + * @return bool Was the plan successfully updated?
196 + */
197 + public static function update_from_site_record( $record ) {
198 + if ( ! is_array( $record ) ) {
165 199 return false;
166 200 }
167 201
168 - if ( isset( $results['products'] ) ) {
202 + if ( isset( $record['products'] ) ) {
169 203 // Store the site's products in an option and return true if updated.
170 - self::store_data_in_option( self::SITE_PRODUCTS_OPTION, $results['products'] );
204 + self::store_data_in_option( self::SITE_PRODUCTS_OPTION, $record['products'] );
171 205 }
172 206
173 - if ( ! isset( $results['plan'] ) ) {
207 + if ( ! isset( $record['plan'] ) ) {
174 208 return false;
175 209 }
176 210
177 211 $current_plan = get_option( self::PLAN_OPTION, array() );
178 212
179 - if ( ! empty( $current_plan ) && $current_plan === $results['plan'] ) {
213 + if ( ! empty( $current_plan ) && $current_plan === $record['plan'] ) {
180 214 // Bail if the plans array hasn't changed.
181 215 return false;
182 216 }
183 217
184 218 // Store the new plan in an option and return true if updated.
185 - $result = self::store_data_in_option( self::PLAN_OPTION, $results['plan'] );
219 + $result = self::store_data_in_option( self::PLAN_OPTION, $record['plan'] );
186 220
187 221 if ( $result ) {
188 222 // Reset the cache since we've just updated the plan.
189 223 self::$active_plan_cache = null;
@@ -201,15 +235,23 @@
201 235 */
202 236 private static function store_data_in_option( $option, $data ) {
203 237 $result = update_option( $option, $data, true );
204 238
205 - // If something goes wrong with the update, so delete the current option and then update it.
206 - if ( ! $result ) {
207 - delete_option( $option );
208 - $result = update_option( $option, $data, true );
239 + if ( $result ) {
240 + return true;
209 241 }
210 242
211 - return $result;
243 + // update_option() also reports false when the stored value already matches, which is not a
244 + // failure. Both options are autoloaded, so reading it as one rewrites them on every
245 + // unchanged fetch and drops the alloptions cache with it.
246 + if ( get_option( $option ) === $data ) {
247 + return true;
248 + }
249 +
250 + // If the update genuinely failed, delete the option and write it again.
251 + delete_option( $option );
252 +
253 + return update_option( $option, $data, true );
212 254 }
213 255
214 256 /**
215 257 * Make an API call to WordPress.com for plan status
@@ -220,11 +262,21 @@
220 262 *
221 263 * @access public
222 264 * @static
223 265 *
266 + * @since 0.14.0 Accepts request arguments.
267 + *
268 + * @param array $args Request arguments, as accepted by `Client::wpcom_json_api_request_as_blog()`.
269 + * A caller refreshing in front of a page render can cap `timeout` here; the
270 + * `http_request_timeout` filter cannot, because the client always sends one.
224 271 * @return bool True if plan is updated, false if no update
225 272 */
226 - public static function refresh_from_wpcom() {
273 + public static function refresh_from_wpcom( $args = array() ) {
274 + // Also registered as an action callback, and `do_action()` hands those an empty string.
275 + if ( ! is_array( $args ) ) {
276 + $args = array();
277 + }
278 +
227 279 $site_id = Manager::get_site_id();
228 280 if ( is_wp_error( $site_id ) ) {
229 281 return false;
230 282 }
@@ -232,12 +284,22 @@
232 284 // Make the API request.
233 285
234 286 $response = Client::wpcom_json_api_request_as_blog(
235 287 sprintf( '/sites/%d?force=wpcom', $site_id ),
236 - '1.1'
288 + '1.1',
289 + $args
237 290 );
238 291
239 - return self::update_from_sites_response( $response );
292 + $updated = self::update_from_sites_response( $response );
293 +
294 + // The shared site record cache can still hold a record older than this response, and a
295 + // cached read stores the plan again. Dropping it keeps that older record from reverting
296 + // what this fetch just stored.
297 + if ( ! is_wp_error( $response ) ) {
298 + Manager::delete_cached_site_data();
299 + }
300 +
301 + return $updated;
240 302 }
241 303
242 304 /**
243 305 * Get the plan that this Jetpack site is currently using.
@@ -373,9 +435,9 @@
373 435 if ( 'videopress' === $feature ) {
374 436 return true;
375 437 }
376 438
377 - // As of 05 2023 - all plans support Earn features (minus 'simple-payments')
439 + // As of 05 2023 - all plans support Earn features (minus 'simple-payments').
378 440 if ( in_array( $feature, array( 'donations', 'recurring-payments', 'premium-content/container' ), true ) ) {
379 441 return true;
380 442 }
381 443
@@ -388,6 +450,99 @@
388 450 return true;
389 451 }
390 452
391 453 return false;
454 + }
455 +
456 + /**
457 + * Retrieve site-specific features for Simple sites.
458 + *
459 + * See Jetpack_Gutenberg::get_site_specific_features()
460 + *
461 + * @return array
462 + */
463 + public static function get_simple_site_specific_features() {
464 + $is_simple_site = defined( 'IS_WPCOM' ) && constant( 'IS_WPCOM' );
465 +
466 + if ( ! $is_simple_site ) {
467 + return array(
468 + 'active' => array(),
469 + 'available' => array(),
470 + );
471 + }
472 +
473 + $current_blog_id = get_current_blog_id();
474 +
475 + // Return the cached value if it exists.
476 + if ( isset( self::$simple_site_specific_features[ $current_blog_id ] ) ) {
477 + return self::$simple_site_specific_features[ $current_blog_id ];
478 + }
479 +
480 + if ( ! class_exists( '\Store_Product_List' ) ) {
481 + require WP_CONTENT_DIR . '/admin-plugins/wpcom-billing/store-product-list.php';
482 + }
483 +
484 + $simple_site_specific_features = \Store_Product_List::get_site_specific_features_data( $current_blog_id );
485 +
486 + self::$simple_site_specific_features[ $current_blog_id ] = $simple_site_specific_features;
487 +
488 + return $simple_site_specific_features;
489 + }
490 +
491 + /**
492 + * Retrieve site-specific features from the WordPress.com registry the site itself carries.
493 + *
494 + * Simple sites read the store; Atomic sites read the purchases wpcomsh keeps in sync. Both
495 + * answer as of this request, where `self::PLAN_OPTION` is only as current as its last fetch.
496 + *
497 + * @since 0.14.0
498 + *
499 + * @return array|null Active and available features, or null where the site carries no registry.
500 + */
501 + public static function get_wpcom_site_specific_features() {
502 + if ( defined( 'IS_WPCOM' ) && constant( 'IS_WPCOM' ) ) {
503 + return self::get_simple_site_specific_features();
504 + }
505 +
506 + if (
507 + ! Constants::is_true( 'IS_ATOMIC' )
508 + || ! class_exists( '\WPCOM_Features' )
509 + || ! function_exists( 'wpcom_get_site_purchases' )
510 + ) {
511 + return null;
512 + }
513 +
514 + if ( null !== self::$atomic_site_specific_features ) {
515 + return self::$atomic_site_specific_features;
516 + }
517 +
518 + // No blog ID anywhere below. wpcomsh resolves it from `jetpack_options`, where WordPress
519 + // reports 1, and throws when the two disagree.
520 + $purchases = \wpcom_get_site_purchases();
521 +
522 + /*
523 + * A site whose Atomic persistent data has not synced reads exactly like one that bought
524 + * nothing, and answering would gate a paid site. Only WordPress.com can tell the two
525 + * apart, so report no answer and leave the caller its own fallback.
526 + */
527 + if ( ! $purchases ) {
528 + return null;
529 + }
530 +
531 + $active = array();
532 +
533 + foreach ( \WPCOM_Features::get_feature_slugs() as $feature ) {
534 + if ( \WPCOM_Features::has_feature( $feature, $purchases, 'wpcom' ) ) {
535 + $active[] = $feature;
536 + }
537 + }
538 +
539 + // `available` names the plans that would grant each feature the site lacks, which only the
540 + // WordPress.com product catalogue can answer. Callers here read `active`.
541 + self::$atomic_site_specific_features = array(
542 + 'active' => $active,
543 + 'available' => array(),
544 + );
545 +
546 + return self::$atomic_site_specific_features;
392 547 }
393 548 }