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
jetpack / jetpack_vendor / automattic / jetpack-videopress / src / class-initial-state.php

class-initial-state.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-beta, at jetpack_vendor/automattic/jetpack-videopress/src/class-initial-state.php

242 lines 9.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The modernized VideoPress dashboard React initial state.
4 *
5 * @package automattic/jetpack-videopress
6 */
7
8 namespace Automattic\Jetpack\VideoPress;
9
10 use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
11 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
12 use Automattic\Jetpack\My_Jetpack\Product;
13 use Automattic\Jetpack\My_Jetpack\Products as My_Jetpack_Products;
14 use Automattic\Jetpack\Status;
15 use Automattic\Jetpack\Status\Host;
16 use Jetpack_Options;
17 use function admin_url;
18 use function esc_url_raw;
19 use function get_bloginfo;
20 use function get_locale;
21 use function get_option;
22 use function get_site_url;
23 use function plugins_url;
24 use function rest_url;
25 use function wp_add_inline_script;
26 use function wp_create_nonce;
27 use function wp_json_encode;
28 use function wp_parse_url;
29 use function wp_script_is;
30
31 /**
32 * The modernized VideoPress dashboard React initial state.
33 *
34 * Mirrors the Activity Log dashboard pattern: a small structured payload
35 * inlined as `var JPVIDEOPRESS_INITIAL_STATE=...` before the wp-build boot
36 * script runs. Phases 6 and 8 will consume the payload via per-file
37 * `declare const` ambient types.
38 */
39 class Initial_State {
40
41 const SCRIPT_HANDLE = 'jetpack-videopress-dashboard-wp-admin-prerequisites';
42
43 /**
44 * WPCOM product slug purchased by the dashboard's upgrade CTA.
45 *
46 * Mirrors the product `Plan::get_product()` resolves to
47 * (`$products->jetpack_videopress`). Kept as a constant rather than read
48 * from `Plan::get_product()` because that helper issues a synchronous
49 * WPCOM request, which we don't want to incur on every page render.
50 */
51 const VIDEOPRESS_PRODUCT_SLUG = 'jetpack_videopress';
52
53 /**
54 * Register the inline-state enqueue hook.
55 *
56 * Hooks `admin_enqueue_scripts` at priority 11 so the wp-build page
57 * (`build/pages/jetpack-videopress-dashboard/page-wp-admin.php`) has
58 * already registered the prerequisites handle at its default priority 10.
59 * The `wp_script_is( ..., 'registered' )` check inside the callback
60 * doubles as the page guard.
61 *
62 * @return void
63 */
64 public static function init() {
65 add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue' ), 11 );
66 }
67
68 /**
69 * Inline the initial-state payload before the wp-build boot script.
70 *
71 * @return void
72 */
73 public static function enqueue() {
74 if ( ! Admin_UI::is_modernized() ) {
75 return;
76 }
77
78 if ( ! wp_script_is( self::SCRIPT_HANDLE, 'registered' ) ) {
79 return;
80 }
81
82 wp_add_inline_script( self::SCRIPT_HANDLE, ( new self() )->render(), 'before' );
83
84 // Hydrate the JP connection store (`window.JP_CONNECTION_INITIAL_STATE`)
85 // so shared connection-aware hooks — notably
86 // `useProductCheckoutWorkflow`, which powers the upgrade CTA — work on
87 // the modernized dashboard exactly as they do on the legacy one. This
88 // also sets `window.jpTracksContext.blog_id` for `@automattic/jetpack-analytics`.
89 Connection_Initial_State::render_script( self::SCRIPT_HANDLE );
90 }
91
92 /**
93 * Get the initial state data.
94 *
95 * @return array
96 */
97 private function get_data() {
98 $gmt_offset = get_option( 'gmt_offset' );
99 $timezone_string = get_option( 'timezone_string' );
100 $home_host = wp_parse_url( get_site_url(), PHP_URL_HOST );
101
102 return array(
103 'API' => array(
104 'WP_API_root' => esc_url_raw( rest_url() ),
105 'WP_API_nonce' => wp_create_nonce( 'wp_rest' ),
106 'contentNonce' => wp_create_nonce( 'videopress-content-nonce' ),
107 ),
108 'jetpackStatus' => array(
109 'calypsoSlug' => ( new Status() )->get_site_suffix(),
110 ),
111 'product' => array(
112 // Fed to `useProductCheckoutWorkflow` as the product to purchase.
113 'slug' => self::VIDEOPRESS_PRODUCT_SLUG,
114 ),
115 'siteData' => array(
116 'id' => Jetpack_Options::get_option( 'id' ),
117 'title' => get_bloginfo( 'name' ) ? get_bloginfo( 'name' ) : get_site_url(),
118 'adminUrl' => esc_url_raw( admin_url() ),
119 'slug' => is_string( $home_host ) ? $home_host : '',
120 'gmtOffset' => is_numeric( $gmt_offset ) ? (float) $gmt_offset : 0.0,
121 'timezoneString' => is_string( $timezone_string ) ? $timezone_string : '',
122 'locale' => str_replace( '_', '-', (string) get_locale() ),
123 // Paid-tier capability check. Drives the free-tier UX (callout /
124 // DataViews configuration) once designer input lands. Backed by
125 // `Product::get_site_features_from_wpcom()`, which caches the WPCOM
126 // `/sites/%d/features` response in a 15-second site transient. On a
127 // cache miss this issues a synchronous WPCOM request that blocks
128 // page rendering until it returns.
129 'hasVideoPressAccess' => self::has_videopress_access(),
130 'isVideoPress1TB' => self::has_videopress_feature( 'videopress-1tb-storage' ),
131 'isVideoPressUnlimited' => self::has_videopress_feature( 'videopress-unlimited-storage' ),
132 ),
133 'assets' => array(
134 'buildUrl' => plugins_url( '../build/', __FILE__ ),
135 ),
136 // Feature gates mirrored from the PHP-side filters so the client can
137 // hide gated UI without a round trip.
138 'features' => array(
139 'chaptersEditor' => Admin_UI::is_chapters_editor_enabled(),
140 'trimCut' => Admin_UI::is_trim_cut_enabled(),
141 ),
142 // Authoritative map of accepted upload types (extension => mimetype),
143 // so the dashboard's drag-and-drop filter accepts exactly what the
144 // VideoPress backend supports rather than guessing client-side.
145 'allowedVideoExtensions' => Admin_UI::get_allowed_video_extensions(),
146 // Product/pricing payload for the pre-connection upsell. Only
147 // populated when the site isn't connected — the only time the
148 // connection gate renders the upsell — so connected dashboards never
149 // incur the synchronous WPCOM pricing request `get_pricing_data()`
150 // makes. Skipped on WordPress.com Simple, where the connection gate is
151 // bypassed (Simple sites are inherently wpcom-connected).
152 'pricing' => ( ( new Host() )->is_wpcom_simple() || ( new Connection_Manager() )->is_connected() ) ? null : $this->get_pricing_data(),
153 );
154 }
155
156 /**
157 * Product description, feature list, and yearly price for the
158 * pre-connection upsell (a port of the legacy dashboard's pricing table).
159 *
160 * Backed by `Plan::get_product_price()`, which issues a synchronous WPCOM
161 * request, so this only runs for disconnected sites. Returns null when the
162 * product or price data isn't available (e.g. the WPCOM request fails), in
163 * which case the gate falls back to the plain connect screen.
164 *
165 * @return array|null The upsell payload, or null when it can't be built.
166 */
167 private function get_pricing_data() {
168 $site_product = My_Jetpack_Products::get_product( 'videopress' );
169 $product_price = Plan::get_product_price();
170
171 // Plan::get_product_price() returns a WP_Error when the WPCOM products
172 // request doesn't come back 200, and `isset()` on a non-ArrayAccess
173 // object is a fatal in PHP 8 — so the array check has to come first.
174 if ( ! is_array( $site_product ) || ! is_array( $product_price ) || ! isset( $product_price['yearly'] ) ) {
175 return null;
176 }
177
178 return array(
179 'title' => isset( $site_product['description'] ) ? (string) $site_product['description'] : '',
180 'features' => isset( $site_product['features'] ) ? array_values( (array) $site_product['features'] ) : array(),
181 'yearly' => $product_price['yearly'],
182 );
183 }
184
185 /**
186 * Whether the site has any paid VideoPress feature flag active.
187 *
188 * Matches the active-features check used by the existing
189 * `videopress/v1/features` REST endpoint: any of the storage tiers
190 * counts as paid access. On WPCOM the legacy `videopress` slug also
191 * grants access.
192 *
193 * @return bool|null Null when site features could not be retrieved.
194 */
195 public static function has_videopress_access() {
196 $feature_slugs = array( 'videopress-1tb-storage', 'videopress-unlimited-storage' );
197 if ( ( new Host() )->is_wpcom_platform() ) {
198 $feature_slugs[] = 'videopress';
199 }
200
201 foreach ( $feature_slugs as $feature_slug ) {
202 $has_feature = self::has_videopress_feature( $feature_slug );
203 if ( null === $has_feature || $has_feature ) {
204 return $has_feature;
205 }
206 }
207
208 return false;
209 }
210
211 /**
212 * Whether the named feature appears in the WPCOM active-features list.
213 *
214 * @param string $feature_slug Feature slug as returned by WPCOM (e.g. `videopress-1tb-storage`).
215 * @return bool|null Null when site features could not be retrieved.
216 */
217 private static function has_videopress_feature( $feature_slug ) {
218 if ( ( new Host() )->is_wpcom_simple() ) {
219 return function_exists( 'wpcom_site_has_feature' ) && (bool) wpcom_site_has_feature( $feature_slug );
220 }
221
222 $features = Product::get_site_features_from_wpcom();
223
224 if ( is_wp_error( $features ) ) {
225 return null;
226 }
227
228 $active = $features['active'] ?? array();
229
230 return in_array( $feature_slug, $active, true );
231 }
232
233 /**
234 * Render the initial state into a JavaScript variable.
235 *
236 * @return string
237 */
238 public function render() {
239 return 'var JPVIDEOPRESS_INITIAL_STATE=' . wp_json_encode( $this->get_data(), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) . ';';
240 }
241 }
242