Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.64% covered (success)
98.64%
217 / 220
93.75% covered (success)
93.75%
15 / 16
CRAP
n/a
0 / 0
Automattic\Jetpack\PremiumAnalytics\get_stats_plan_tier
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
4
Automattic\Jetpack\PremiumAnalytics\get_wpcom_stats_plan_tier
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
7.02
Automattic\Jetpack\PremiumAnalytics\get_jetpack_stats_plan_tier
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
Automattic\Jetpack\PremiumAnalytics\has_cached_jetpack_plan
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
Automattic\Jetpack\PremiumAnalytics\has_jetpack_stats_commercial_purchase
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
7
Automattic\Jetpack\PremiumAnalytics\has_jetpack_stats_product
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
6
Automattic\Jetpack\PremiumAnalytics\offers_free_site_upgrade
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
Automattic\Jetpack\PremiumAnalytics\get_locked_stats_features
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
7
Automattic\Jetpack\PremiumAnalytics\get_stats_feature_copy
100.00% covered (success)
100.00%
65 / 65
100.00% covered (success)
100.00%
1 / 1
14
Automattic\Jetpack\PremiumAnalytics\localize_stats_support_url
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
Automattic\Jetpack\PremiumAnalytics\get_stats_upgrade_url
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
6
Automattic\Jetpack\PremiumAnalytics\get_stats_plan_gates
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
6
Automattic\Jetpack\PremiumAnalytics\configure_stats_plan_gates
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
Automattic\Jetpack\PremiumAnalytics\inject_stats_plan_gates_script_data
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
5
Automattic\Jetpack\PremiumAnalytics\maybe_invalidate_stats_caches
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
5
Automattic\Jetpack\PremiumAnalytics\maybe_refresh_jetpack_plan
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
6
1<?php
2/**
3 * Which paid Stats features the site's plan lacks, following classic Stats' gates.
4 *
5 * @package automattic/jetpack-premium-analytics
6 */
7
8namespace Automattic\Jetpack\PremiumAnalytics;
9
10use Automattic\Jetpack\Current_Plan;
11use Automattic\Jetpack\PremiumAnalytics\REST\Api_Proxy_Controller;
12use Automattic\Jetpack\Stats\Options as Stats_Options;
13use Automattic\Jetpack\Status;
14use Automattic\Jetpack\Status\Host;
15
16// Guarded on a symbol the file declares; see the include block in Analytics::load_dashboard_components().
17if ( ! function_exists( __NAMESPACE__ . '\\ensure_widget_registry_ready' ) ) {
18    require_once __DIR__ . '/widget-modules.php';
19}
20
21/**
22 * Filter overriding the Stats plan tier.
23 *
24 * @var string
25 */
26const STATS_PLAN_TIER_FILTER = 'jetpack_premium_analytics_stats_plan_tier';
27
28const STATS_TIER_COMMERCIAL   = 'commercial';
29const STATS_TIER_LEGACY_FREE  = 'legacy-free';
30const STATS_TIER_PAID         = 'paid';
31const STATS_TIER_LEGACY_BASIC = 'legacy-basic';
32const STATS_TIER_FREE         = 'free';
33const STATS_TIER_JETPACK_FREE = 'jetpack-free';
34const STATS_TIER_UNKNOWN      = 'unknown';
35
36const STATS_TIERS = array( STATS_TIER_COMMERCIAL, STATS_TIER_LEGACY_FREE, STATS_TIER_PAID, STATS_TIER_LEGACY_BASIC, STATS_TIER_FREE, STATS_TIER_JETPACK_FREE, STATS_TIER_UNKNOWN );
37
38/**
39 * Jetpack products that support commercial use. PWYW and Free Stats do not.
40 */
41const JETPACK_STATS_COMMERCIAL_PRODUCTS = array( 'jetpack_stats_monthly', 'jetpack_stats_yearly', 'jetpack_stats_bi_yearly' );
42
43/**
44 * Jetpack plan families that bundle commercial Stats.
45 */
46const JETPACK_STATS_COMMERCIAL_PLAN_PREFIXES = array( 'jetpack_business', 'jetpack_complete', 'jetpack_growth' );
47
48/**
49 * The Stats plan tier the dashboard is gated by.
50 *
51 * @since $$next-version$$
52 *
53 * @return string One of the STATS_TIER_* values.
54 */
55function get_stats_plan_tier() {
56    $host = new Host();
57
58    if ( $host->is_wpcom_platform() ) {
59        $tier = get_wpcom_stats_plan_tier( $host->is_woa_site() );
60    } elseif ( $host->is_vip_site() ) {
61        $tier = STATS_TIER_COMMERCIAL;
62    } else {
63        $tier = get_jetpack_stats_plan_tier();
64    }
65
66    /**
67     * Filters the Stats plan tier Premium Analytics gates the dashboard by.
68     *
69     * @since $$next-version$$
70     *
71     * @param string $tier One of `commercial`, `legacy-free`, `paid`, `legacy-basic`, `free`, `jetpack-free` or
72     *                     `unknown`, which locks nothing. Any other value is ignored.
73     */
74    $filtered = apply_filters( STATS_PLAN_TIER_FILTER, $tier );
75
76    // A misspelled tier would otherwise fall through to `unknown` and unlock every paid feature.
77    if ( ! in_array( $filtered, STATS_TIERS, true ) ) {
78        // One line: tools/replace-next-version-tag.sh only rewrites the token in a single-line call.
79        _doing_it_wrong( __FUNCTION__, esc_html( sprintf( /* translators: %s: Filter name. */ __( 'The %s filter must return one of the STATS_TIER_* values.', 'jetpack-premium-analytics-pkg' ), STATS_PLAN_TIER_FILTER ) ), 'jetpack-premium-analytics-$$next-version$$' );
80        return $tier;
81    }
82
83    return $filtered;
84}
85
86/**
87 * The tier of a WordPress.com site, from its stats feature.
88 *
89 * @since $$next-version$$
90 *
91 * @param bool $is_atomic Whether the site is Atomic.
92 * @return string One of the STATS_TIER_* values.
93 */
94function get_wpcom_stats_plan_tier( $is_atomic ) {
95    if ( ! function_exists( 'wpcom_site_has_feature' ) ) {
96        return STATS_TIER_UNKNOWN;
97    }
98
99    // Unsynced Atomic data reads like a site that bought nothing; see Current_Plan::get_wpcom_site_specific_features().
100    if ( $is_atomic && function_exists( 'wpcom_get_site_purchases' ) && ! \wpcom_get_site_purchases() ) {
101        return STATS_TIER_UNKNOWN;
102    }
103
104    // Calypso's shouldGateStats() order: legacy free outranks paid.
105    $tiers = array(
106        'stats-commercial' => STATS_TIER_COMMERCIAL,
107        'stats-free'       => STATS_TIER_LEGACY_FREE,
108        'stats-paid'       => STATS_TIER_PAID,
109        'stats-basic'      => STATS_TIER_LEGACY_BASIC,
110    );
111    foreach ( $tiers as $feature => $tier ) {
112        if ( \wpcom_site_has_feature( $feature ) ) {
113            return $tier;
114        }
115    }
116
117    return STATS_TIER_FREE;
118}
119
120/**
121 * The tier of a self-hosted site, from its cached plan.
122 *
123 * @since $$next-version$$
124 *
125 * @return string One of the STATS_TIER_* values.
126 */
127function get_jetpack_stats_plan_tier() {
128    if ( ! has_cached_jetpack_plan() ) {
129        return STATS_TIER_UNKNOWN;
130    }
131
132    return has_jetpack_stats_commercial_purchase() ? STATS_TIER_COMMERCIAL : STATS_TIER_JETPACK_FREE;
133}
134
135/**
136 * Whether the plan cache holds a plan. An empty one cannot tell a paid site from a free one (STATS-475).
137 *
138 * @since $$next-version$$
139 *
140 * @return bool
141 */
142function has_cached_jetpack_plan() {
143    $plan = get_option( Current_Plan::PLAN_OPTION );
144
145    return is_array( $plan ) && is_array( $plan['features']['active'] ?? null ) && $plan['features']['active'];
146}
147
148/**
149 * Whether a self-hosted site's cached plan or products support commercial use.
150 *
151 * @since $$next-version$$
152 *
153 * @return bool
154 */
155function has_jetpack_stats_commercial_purchase() {
156    $plan     = get_option( Current_Plan::PLAN_OPTION );
157    $products = get_option( Current_Plan::SITE_PRODUCTS_OPTION, array() );
158    $products = is_array( $products ) ? $products : array();
159
160    $slugs = array_column( $products, 'product_slug' );
161    if ( isset( $plan['product_slug'] ) ) {
162        $slugs[] = $plan['product_slug'];
163    }
164
165    foreach ( $slugs as $slug ) {
166        if ( in_array( $slug, JETPACK_STATS_COMMERCIAL_PRODUCTS, true ) ) {
167            return true;
168        }
169        foreach ( JETPACK_STATS_COMMERCIAL_PLAN_PREFIXES as $prefix ) {
170            if ( 0 === strpos( $slug, $prefix ) ) {
171                return true;
172            }
173        }
174    }
175
176    return in_array( 'stats-commercial', $plan['features']['active'] ?? array(), true );
177}
178
179/**
180 * Whether a self-hosted site's cached plan or products include any Jetpack Stats product, free or PWYW included.
181 *
182 * @since $$next-version$$
183 *
184 * @return bool
185 */
186function has_jetpack_stats_product() {
187    $plan     = get_option( Current_Plan::PLAN_OPTION );
188    $products = get_option( Current_Plan::SITE_PRODUCTS_OPTION, array() );
189    $products = is_array( $products ) ? $products : array();
190
191    $slugs = array_column( $products, 'product_slug' );
192    if ( isset( $plan['product_slug'] ) ) {
193        $slugs[] = $plan['product_slug'];
194    }
195
196    foreach ( $slugs as $slug ) {
197        if ( is_string( $slug ) && 0 === strpos( $slug, 'jetpack_stats' ) ) {
198            return true;
199        }
200    }
201
202    return false;
203}
204
205/**
206 * Tiers without paid Stats that get the upgrade banner. `free` gets a section upgrade card instead, as in classic Stats,
207 * and `unknown` may be a paid site whose plan has not synced.
208 */
209const STATS_UPGRADE_BANNER_TIERS = array( STATS_TIER_JETPACK_FREE, STATS_TIER_LEGACY_FREE, STATS_TIER_LEGACY_BASIC );
210
211/**
212 * Whether the dashboard offers a paid Stats plan: the tier gets the upgrade banner and the site first opened the dashboard over 30 days ago.
213 *
214 * @since $$next-version$$
215 *
216 * @param string $tier One of the STATS_TIER_* values.
217 * @return bool
218 */
219function offers_free_site_upgrade( $tier ) {
220    // Stats offers Simple sites this banner under rules of their own, which are not ported here.
221    if ( ! in_array( $tier, STATS_UPGRADE_BANNER_TIERS, true ) || ( new Host() )->is_wpcom_simple() ) {
222        return false;
223    }
224
225    $first_visit = get_dashboard_first_visit();
226
227    return null !== $first_visit && time() - $first_visit >= 30 * DAY_IN_SECONDS;
228}
229
230/**
231 * The features classic Stats gates on WordPress.com Personal: the newer, commercial modules.
232 */
233const STATS_COMMERCIAL_FEATURES = array( 'authors', 'search-terms', 'videos', 'utm', 'devices', 'regions-cities' );
234
235/**
236 * The features gated on legacy-free sites and on self-hosted sites without a commercial purchase.
237 */
238const STATS_ADVANCED_FEATURES = array( 'utm', 'devices', 'regions-cities' );
239
240/**
241 * The features a tier locks.
242 *
243 * @since $$next-version$$
244 *
245 * @param string $tier One of the STATS_TIER_* values.
246 * @return string[] Feature keys.
247 */
248function get_locked_stats_features( $tier ) {
249    switch ( $tier ) {
250        case STATS_TIER_LEGACY_FREE:
251        case STATS_TIER_JETPACK_FREE:
252            return STATS_ADVANCED_FEATURES;
253        case STATS_TIER_PAID:
254            return STATS_COMMERCIAL_FEATURES;
255        case STATS_TIER_LEGACY_BASIC:
256            return array_merge( STATS_COMMERCIAL_FEATURES, array( 'referrers', 'clicks', 'csv', 'intervals', 'long-date-presets' ) );
257        case STATS_TIER_FREE:
258            return array_merge(
259                STATS_COMMERCIAL_FEATURES,
260                array( 'top-posts', 'referrers', 'countries', 'clicks', 'file-downloads', 'insights', 'tags', 'csv', 'intervals', 'long-date-presets', 'recent-7-days' )
261            );
262        default:
263            return array();
264    }
265}
266
267/**
268 * What a locked feature says, worded as classic Stats' upsell; `<link>` wraps the words its support page explains.
269 *
270 * @since $$next-version$$
271 *
272 * @param string $feature Feature key.
273 * @return array{description:string, support_url:string} The copy, generic for a feature without its own.
274 */
275function get_stats_feature_copy( $feature ) {
276    $traffic_support = 'https://jetpack.com/support/jetpack-stats/traffic-dashboard/';
277
278    switch ( $feature ) {
279        case 'top-posts':
280            return array(
281                'description' => __( 'Discover your <link>post and pages</link> traffic in detail and learn what content resonates the most.', 'jetpack-premium-analytics-pkg' ),
282                'support_url' => $traffic_support . '#analyzing-popular-posts-pages-and-archives',
283            );
284        case 'referrers':
285            return array(
286                'description' => __( 'Find out where your <link>visitors come from</link> to optimize your content strategy.', 'jetpack-premium-analytics-pkg' ),
287                'support_url' => $traffic_support . '#referrers',
288            );
289        case 'countries':
290            return array(
291                'description' => __( 'Discover where your <link>visitors are located</link> and identify where your traffic is coming from.', 'jetpack-premium-analytics-pkg' ),
292                'support_url' => $traffic_support . '#views-by-locations',
293            );
294        case 'regions-cities':
295            return array(
296                'description' => __( 'Access to <link>region and city stats</link> for more accurate analysis.', 'jetpack-premium-analytics-pkg' ),
297                'support_url' => $traffic_support . '#regions-and-cities',
298            );
299        case 'clicks':
300            return array(
301                'description' => __( 'Learn what <link>external links</link> your visitors click on your site to reveal their areas of interest.', 'jetpack-premium-analytics-pkg' ),
302                'support_url' => $traffic_support . '#clicks',
303            );
304        case 'file-downloads':
305            return array(
306                'description' => __( 'Discover the most <link>downloaded files</link> by your visitors.', 'jetpack-premium-analytics-pkg' ),
307                'support_url' => 'https://wordpress.com/support/stats/analyze-content-performance/#track-file-downloads',
308            );
309        case 'authors':
310            return array(
311                'description' => __( 'Identify your audience’s <link>favorite writers</link> and perspectives.', 'jetpack-premium-analytics-pkg' ),
312                'support_url' => $traffic_support . '#authors',
313            );
314        case 'search-terms':
315            return array(
316                'description' => __( 'Discover the <link>terms and phrases</link> your visitors use to find your site.', 'jetpack-premium-analytics-pkg' ),
317                'support_url' => $traffic_support . '#search-terms',
318            );
319        case 'videos':
320            return array(
321                'description' => __( 'Discover your <link>most popular videos</link> and find out how they performed.', 'jetpack-premium-analytics-pkg' ),
322                'support_url' => 'https://jetpack.com/support/jetpack-videopress/add-video-block-editor/video-stats/',
323            );
324        case 'utm':
325            return array(
326                'description' => __( 'Generate UTM parameters and track your <link>campaign performance data</link>.', 'jetpack-premium-analytics-pkg' ),
327                'support_url' => $traffic_support . '#harnessing-utm-stats-for-precision-tracking',
328            );
329        case 'devices':
330            return array(
331                'description' => __( 'See which <link>devices, browsers and OS</link> your visitors are using.', 'jetpack-premium-analytics-pkg' ),
332                'support_url' => $traffic_support . '#devices-stats',
333            );
334        case 'tags':
335            return array(
336                'description' => __( 'Track the most viewed <link>tags & categories</link>.', 'jetpack-premium-analytics-pkg' ),
337                'support_url' => 'https://wordpress.com/support/stats/learn-insights-about-your-website/#all-time-insights',
338            );
339        default:
340            return array(
341                'description' => __( 'Upgrade your plan to unlock Jetpack Stats.', 'jetpack-premium-analytics-pkg' ),
342                'support_url' => '',
343            );
344    }
345}
346
347/**
348 * Locales with translated WordPress.com support articles: Calypso's `supportSiteLocales`, which classic Stats links with.
349 */
350const WPCOM_SUPPORT_LOCALES = array( 'ar', 'de', 'es', 'fr', 'he', 'id', 'it', 'ja', 'ko', 'nl', 'pt-br', 'ru', 'sv', 'tr', 'zh-cn', 'zh-tw' );
351
352/**
353 * A WordPress.com support URL in the current user's language, where that support site has one.
354 *
355 * Leaves jetpack.com URLs in English: its localized support docs are missing for most locales.
356 *
357 * @since $$next-version$$
358 *
359 * @param string $url Support URL, in English.
360 * @return string
361 */
362function localize_stats_support_url( $url ) {
363    $prefix = 'https://wordpress.com/support/';
364    if ( ! str_starts_with( $url, $prefix ) ) {
365        return $url;
366    }
367
368    $locale = strtolower( str_replace( '_', '-', get_user_locale() ) );
369    if ( ! in_array( $locale, WPCOM_SUPPORT_LOCALES, true ) ) {
370        $locale = strtok( $locale, '-' );
371    }
372    if ( ! in_array( $locale, WPCOM_SUPPORT_LOCALES, true ) ) {
373        return $url;
374    }
375
376    return 'https://wordpress.com/' . $locale . '/support/' . substr( $url, strlen( $prefix ) );
377}
378
379/**
380 * Where to buy a locked feature: classic Stats' purchase destinations.
381 *
382 * @since $$next-version$$
383 *
384 * @param string $feature Feature key.
385 * @param string $tier    One of the STATS_TIER_* values.
386 * @param string $from    Where the purchase started, for the Odyssey purchase flow.
387 * @return string The URL, or an empty string when there is nowhere to buy it.
388 */
389function get_stats_upgrade_url( $feature, $tier, $from = 'jetpack-premium-analytics' ) {
390    $return_path = 'admin.php?page=' . Analytics::MENU_PAGE_SLUG;
391
392    if ( STATS_TIER_JETPACK_FREE === $tier ) {
393        // The purchase flow lives in Odyssey Stats; with it off, `page=stats` shows only an opt-in notice.
394        $stats_page = function_exists( 'menu_page_url' ) ? menu_page_url( 'stats', false ) : '';
395        if ( ! $stats_page || ! Stats_Options::get_option( 'enable_odyssey_stats' ) ) {
396            return '';
397        }
398
399        $query = http_build_query(
400            array(
401                'from'         => $from,
402                'productType'  => 'commercial',
403                'redirect_uri' => $return_path,
404            ),
405            '',
406            '&',
407            PHP_QUERY_RFC3986
408        );
409
410        return $stats_page . '#!/stats/purchase/' . (int) \Jetpack_Options::get_option( 'id' ) . '?' . $query;
411    }
412
413    $plan      = in_array( $feature, STATS_COMMERCIAL_FEATURES, true ) ? 'premium' : 'personal';
414    $site_slug = ( new Status() )->get_site_suffix();
415
416    return 'https://wordpress.com/checkout/' . rawurlencode( $site_slug ) . '/' . $plan . '?redirect_to=' . rawurlencode( admin_url( $return_path ) );
417}
418
419/**
420 * The gates the client applies: each locked feature's copy and upgrade URL, and the feature of each widget type it locks.
421 *
422 * @since $$next-version$$
423 *
424 * @return array{tier:string, locked_features:array<string,array{description:string, support_url:string, upgrade_url:string}>, locked_widget_types:array<string,string>}
425 */
426function get_stats_plan_gates() {
427    $tier     = get_stats_plan_tier();
428    $features = get_locked_stats_features( $tier );
429
430    $locked_features = array();
431    foreach ( $features as $feature ) {
432        $copy                        = get_stats_feature_copy( $feature );
433        $copy['support_url']         = localize_stats_support_url( $copy['support_url'] );
434        $locked_features[ $feature ] = $copy + array( 'upgrade_url' => get_stats_upgrade_url( $feature, $tier ) );
435    }
436
437    $locked_widget_types = array();
438    if ( $locked_features ) {
439        ensure_widget_registry_ready();
440
441        foreach ( get_available_widget_types() as $name => $widget_type ) {
442            if ( isset( $widget_type->plan_feature ) && isset( $locked_features[ $widget_type->plan_feature ] ) ) {
443                $locked_widget_types[ $name ] = $widget_type->plan_feature;
444            }
445        }
446    }
447
448    return array(
449        'tier'                => $tier,
450        'locked_features'     => $locked_features,
451        'locked_widget_types' => $locked_widget_types,
452    );
453}
454
455/**
456 * Configures the plan gates script data.
457 *
458 * @since $$next-version$$
459 *
460 * @return void
461 */
462function configure_stats_plan_gates() {
463    add_filter( 'jetpack_admin_js_script_data', __NAMESPACE__ . '\\inject_stats_plan_gates_script_data', 20 );
464    add_action( 'admin_init', __NAMESPACE__ . '\\maybe_refresh_jetpack_plan' );
465    add_action( 'admin_init', __NAMESPACE__ . '\\maybe_invalidate_stats_caches' );
466}
467
468/**
469 * Injects the plan gates into JetpackScriptData.
470 *
471 * @since $$next-version$$
472 *
473 * @param array $data The script data passed by the assets package.
474 * @return array
475 */
476function inject_stats_plan_gates_script_data( array $data ): array {
477    // Script data prints on every Jetpack admin page; only the dashboard reads the gates.
478    if ( ! Analytics::is_dashboard_request() ) {
479        return $data;
480    }
481
482    if ( ! isset( $data['premium_analytics'] ) || ! is_array( $data['premium_analytics'] ) ) {
483        $data['premium_analytics'] = array();
484    }
485
486    $gates = get_stats_plan_gates();
487
488    // Objects, so an empty map reaches JS as `{}` rather than `[]`.
489    $data['premium_analytics']['plan_gates'] = array(
490        'tier'                => $gates['tier'],
491        'locked_features'     => (object) $gates['locked_features'],
492        'locked_widget_types' => (object) $gates['locked_widget_types'],
493        'has_stats_product'   => has_jetpack_stats_product(),
494        // The banner sells the commercial features, so `utm` picks the plan that unlocks them.
495        'free_site_upgrade'   => offers_free_site_upgrade( $gates['tier'] )
496            ? array( 'upgrade_url' => get_stats_upgrade_url( 'utm', $gates['tier'], 'jetpack-premium-analytics-free-site-upgrade-notice' ) )
497            : null,
498    );
499
500    return $data;
501}
502
503/**
504 * Transient throttling the refresh of an empty plan cache.
505 */
506const EMPTY_PLAN_REFRESH_TRANSIENT = 'jetpack_premium_analytics_empty_plan_refresh';
507
508/**
509 * Transient throttling the refresh after a purchase.
510 */
511const PURCHASE_PLAN_REFRESH_TRANSIENT = 'jetpack_premium_analytics_purchase_plan_refresh';
512
513/**
514 * Transient throttling the cache invalidation after a purchase.
515 */
516const PURCHASE_CACHE_INVALIDATION_TRANSIENT = 'jetpack_premium_analytics_purchase_cache_invalidation';
517
518/**
519 * Drops the cached Stats replies a checkout made stale, before the dashboard it returns to asks for them.
520 *
521 * @since $$next-version$$
522 *
523 * @return void
524 */
525function maybe_invalidate_stats_caches() {
526    // Simple serves the dashboard from WordPress.com, with neither cache.
527    if ( ! Analytics::is_dashboard_request() || '' === get_stats_purchase_success_type() || ( new Host() )->is_wpcom_simple() ) {
528        return;
529    }
530
531    // The marker is a plain GET parameter, so a crafted link could otherwise drop the caches on every visit.
532    if ( get_transient( PURCHASE_CACHE_INVALIDATION_TRANSIENT ) ) {
533        return;
534    }
535    set_transient( PURCHASE_CACHE_INVALIDATION_TRANSIENT, 1, MINUTE_IN_SECONDS );
536
537    Api_Proxy_Controller::invalidate_cache();
538    delete_transient( Notices::NOTICES_CACHE_KEY );
539}
540
541/**
542 * Refreshes a self-hosted site's cached plan on the dashboard when it is empty or a checkout just returned.
543 *
544 * Script data is final once printed, so the gates can only change before it.
545 *
546 * @since $$next-version$$
547 *
548 * @return void
549 */
550function maybe_refresh_jetpack_plan() {
551    if ( ! Analytics::is_dashboard_request() || ( new Host() )->is_wpcom_platform() ) {
552        return;
553    }
554
555    if ( '' !== get_stats_purchase_success_type() ) {
556        $transient = PURCHASE_PLAN_REFRESH_TRANSIENT;
557        $ttl       = MINUTE_IN_SECONDS;
558    } elseif ( ! has_cached_jetpack_plan() ) {
559        $transient = EMPTY_PLAN_REFRESH_TRANSIENT;
560        $ttl       = 15 * MINUTE_IN_SECONDS;
561    } else {
562        return;
563    }
564
565    if ( get_transient( $transient ) ) {
566        return;
567    }
568    set_transient( $transient, 1, $ttl );
569
570    Current_Plan::refresh_from_wpcom( array( 'timeout' => 5 ) );
571}