PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 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 All 508 releases
jetpack / jetpack_vendor / automattic / jetpack-podcast / src / class-podcast-gate.php

class-podcast-gate.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3, at jetpack_vendor/automattic/jetpack-podcast/src/class-podcast-gate.php

149 lines 4.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Podcast product-access gate.
4 *
5 * @package automattic/jetpack-podcast
6 */
7
8 namespace Automattic\Jetpack\Podcast;
9
10 use Automattic\Jetpack\Connection\Client;
11 use Automattic\Jetpack\Current_Plan;
12 use Automattic\Jetpack\Status\Host;
13 use Jetpack_Options;
14
15 /**
16 * Premium podcast feature gate (dashboard, stats, episode block). Two paths:
17 *
18 * - WordPress.com (Simple/WoA): the `podcasting` plan feature via
19 * `Current_Plan::supports`, plus the launch-day grandfather rule. Request-
20 * scoped, so gating another blog needs `switch_to_blog` first.
21 * - Self-hosted Jetpack: a Growth/Complete purchase over the connection
22 * (PODS-123). Only consulted in admin/editor contexts.
23 */
24 class Podcast_Gate {
25
26 const FEATURE_SLUG = 'podcasting';
27
28 /**
29 * Launch-day cutoff for the paying-blog grandfather rule. Paid blogs
30 * registered before this date keep Premium podcast features without
31 * needing the `podcasting` plan feature. WordPress.com only.
32 */
33 const GRANDFATHER_CUTOFF_DATE = '2026-05-18';
34
35 /**
36 * Transient holding the cached `/upgrades` response. Short-lived (30s): dedupes
37 * the lookup across the successive editor/admin loads that consult the gate,
38 * without a synchronous WPCOM request on each one.
39 */
40 const PURCHASES_TRANSIENT = 'jetpack_podcast_site_purchases';
41
42 /**
43 * Whether the current site can use the paid podcast surfaces.
44 *
45 * @return bool
46 */
47 public static function has_product_access(): bool {
48 if ( ! ( new Host() )->is_wpcom_platform() ) {
49 return self::self_hosted_has_paid_plan();
50 }
51
52 $blog_id = get_current_blog_id();
53 if ( $blog_id <= 0 ) {
54 return false;
55 }
56
57 if ( self::is_grandfathered( $blog_id ) ) {
58 return true;
59 }
60
61 return (bool) Current_Plan::supports( self::FEATURE_SLUG );
62 }
63
64 /**
65 * The minimum plan slug to upsell: WordPress.com Premium (`value_bundle`) or
66 * Jetpack Growth (`jetpack_growth_yearly`).
67 *
68 * @return string
69 */
70 public static function get_required_plan_slug(): string {
71 return ( new Host() )->is_wpcom_platform() ? 'value_bundle' : 'jetpack_growth_yearly';
72 }
73
74 /**
75 * Whether a self-hosted site owns a Growth/Complete plan. Matches purchased
76 * product slugs, not the `podcasting` feature (which is true for every Jetpack
77 * site on WordPress.com and can't tell free from paid here).
78 */
79 private static function self_hosted_has_paid_plan(): bool {
80 foreach ( self::get_site_current_purchases() as $purchase ) {
81 $slug = is_array( $purchase ) && isset( $purchase['product_slug'] ) ? $purchase['product_slug'] : '';
82
83 // Prefix match so every Growth/Complete billing term counts.
84 foreach ( array( 'jetpack_growth', 'jetpack_complete' ) as $prefix ) {
85 if ( is_string( $slug ) && 0 === strpos( $slug, $prefix ) ) {
86 return true;
87 }
88 }
89 }
90
91 return false;
92 }
93
94 /**
95 * The site's current purchases from WordPress.com (`/upgrades`). Cached in a
96 * short transient so successive gate checks don't each fire a WPCOM request.
97 * Fails closed to an empty list on any error, without caching it, so the next
98 * request retries rather than serving a stale empty.
99 *
100 * @return array Purchase entries; empty on failure.
101 */
102 private static function get_site_current_purchases(): array {
103 $cached = get_transient( self::PURCHASES_TRANSIENT );
104 if ( is_array( $cached ) ) {
105 return $cached;
106 }
107
108 $response = Client::wpcom_json_api_request_as_blog(
109 sprintf( '/upgrades?site=%d', (int) Jetpack_Options::get_option( 'id' ) ),
110 '1.2',
111 array( 'method' => 'GET' )
112 );
113
114 if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) {
115 return array();
116 }
117
118 $decoded = json_decode( wp_remote_retrieve_body( $response ), true );
119 if ( ! is_array( $decoded ) ) {
120 return array();
121 }
122
123 set_transient( self::PURCHASES_TRANSIENT, $decoded, 30 );
124 return $decoded;
125 }
126
127 /**
128 * Whether the blog is grandfathered: registered before the cutoff AND on a paid plan.
129 *
130 * @param int $blog_id Blog ID.
131 */
132 protected static function is_grandfathered( int $blog_id ): bool {
133 if ( ! function_exists( 'get_blog_details' ) ) {
134 return false;
135 }
136 $details = get_blog_details( $blog_id );
137 if ( ! $details || empty( $details->registered ) ) {
138 return false;
139 }
140 $registered_ts = strtotime( $details->registered );
141 if ( false === $registered_ts || $registered_ts >= strtotime( self::GRANDFATHER_CUTOFF_DATE ) ) {
142 return false;
143 }
144
145 $plan = Current_Plan::get();
146 return ! empty( $plan['class'] ) && 'free' !== $plan['class'];
147 }
148 }
149