PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 15.9-a.3
Jetpack – WP Security, Backup, Speed, & Growth v15.9-a.3
15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / class.jetpack-gutenberg.php
jetpack Last commit date
3rd-party 2 months ago _inc 2 weeks ago css 2 weeks ago extensions 2 weeks ago images 1 month ago jetpack_vendor 2 weeks ago json-endpoints 2 weeks ago modules 2 weeks ago sal 2 weeks ago src 4 weeks ago vendor 2 weeks ago views 1 month ago CHANGELOG.md 2 weeks ago LICENSE.txt 5 months ago SECURITY.md 2 years ago class-jetpack-connection-status.php 2 years ago class-jetpack-gallery-settings.php 6 months ago class-jetpack-newsletter-dashboard-widget.php 6 months ago class-jetpack-pre-connection-jitms.php 2 years ago class-jetpack-stats-dashboard-widget.php 3 months ago class-jetpack-xmlrpc-methods.php 6 months ago class.frame-nonce-preview.php 6 months ago class.jetpack-admin.php 1 month ago class.jetpack-autoupdate.php 6 months ago class.jetpack-cli.php 5 months ago class.jetpack-client-server.php 2 years ago class.jetpack-gutenberg.php 2 months ago class.jetpack-heartbeat.php 3 months ago class.jetpack-modules-list-table.php 6 months ago class.jetpack-network-sites-list-table.php 6 months ago class.jetpack-network.php 1 month ago class.jetpack-plan.php 2 years ago class.jetpack-post-images.php 2 months ago class.jetpack-twitter-cards.php 3 months ago class.jetpack-user-agent.php 2 years ago class.jetpack.php 3 weeks ago class.json-api-endpoints.php 2 weeks ago class.json-api.php 2 weeks ago class.photon.php 3 years ago composer.json 2 weeks ago enhanced-open-graph.php 3 months ago functions.compat.php 3 months ago functions.cookies.php 2 years ago functions.global.php 1 month ago functions.is-mobile.php 2 years ago functions.opengraph.php 2 months ago functions.photon.php 2 years ago jetpack.php 2 weeks ago json-api-config.php 3 years ago json-endpoints.php 2 years ago load-jetpack.php 2 months ago locales.php 6 months ago readme.txt 2 weeks ago unauth-file-upload.php 6 months ago uninstall.php 6 months ago wpml-config.xml 3 years ago
class.jetpack-gutenberg.php
1419 lines
1 <?php //phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * Handles server-side registration and use of all blocks and plugins available in Jetpack for the block editor, aka Gutenberg.
4 * Works in tandem with client-side block registration via `index.json`
5 *
6 * @package automattic/jetpack
7 */
8
9 use Automattic\Jetpack\Assets;
10 use Automattic\Jetpack\Blocks;
11 use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
12 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
13 use Automattic\Jetpack\Constants;
14 use Automattic\Jetpack\Current_Plan as Jetpack_Plan;
15 use Automattic\Jetpack\Modules;
16 use Automattic\Jetpack\My_Jetpack\Initializer as My_Jetpack_Initializer;
17 use Automattic\Jetpack\Status;
18 use Automattic\Jetpack\Status\Host;
19
20 if ( ! defined( 'ABSPATH' ) ) {
21 exit( 0 );
22 }
23
24 // phpcs:disable Universal.Files.SeparateFunctionsFromOO.Mixed -- TODO: Move the functions and such to some other file.
25
26 /**
27 * General Gutenberg editor specific functionality
28 */
29 class Jetpack_Gutenberg {
30
31 /**
32 * Only these extensions can be registered. Used to control availability of beta blocks.
33 *
34 * @var array|null Extensions allowed list or `null` if not initialized yet.
35 * @see static::get_extensions()
36 */
37 private static $extensions = null;
38
39 /**
40 * Keeps track of the reasons why a given extension is unavailable.
41 *
42 * @var array Extensions availability information
43 */
44 private static $availability = array();
45
46 /**
47 * A cached array of the fully processed availability data. Keeps track of
48 * reasons why an extension is unavailable or missing.
49 *
50 * @var array Extensions availability information.
51 */
52 private static $cached_availability = null;
53
54 /**
55 * Site-specific features available.
56 * Their calculation can be expensive and slow, so we're caching it for the request.
57 *
58 * @var array Site-specific features
59 */
60 private static $site_specific_features = array();
61
62 /**
63 * List of deprecated blocks.
64 *
65 * @var array List of deprecated blocks.
66 */
67 private static $deprecated_blocks = array(
68 'jetpack/revue',
69 );
70
71 /**
72 * Fallback minimum plan requirements for WordPress.com/Atomic sites.
73 *
74 * Used when features have conditional availability (e.g., sticker-based gating)
75 * and don't appear in features_data['available']. This only affects the upsell
76 * message shown to users.
77 *
78 * @since 15.5
79 * @var array Feature slug => minimum WordPress.com plan slug.
80 */
81 private static $wpcom_minimum_plan_fallbacks = array(
82 'donations' => 'value_bundle',
83 'payment-buttons' => 'value_bundle',
84 'paypal-payment-buttons' => 'value_bundle',
85 );
86
87 /**
88 * Storing the contents of the preset file.
89 *
90 * Already been json_decode.
91 *
92 * @var null|object JSON decoded object after first usage.
93 */
94 private static $preset_cache = null;
95
96 /**
97 * Keep track of JS loading strategies for each block that needs it.
98 *
99 * @var array<string, array|bool>
100 *
101 * @since 15.0
102 */
103 private static $block_js_loading_strategies = array();
104
105 /**
106 * Check to see if a minimum version of Gutenberg is available. Because a Gutenberg version is not available in
107 * php if the Gutenberg plugin is not installed, if we know which minimum WP release has the required version we can
108 * optionally fall back to that.
109 *
110 * @param array $version_requirements An array containing the required Gutenberg version and, if known, the WordPress version that was released with this minimum version.
111 * @param string $slug The slug of the block or plugin that has the gutenberg version requirement.
112 *
113 * @since 8.3.0
114 *
115 * @return boolean True if the version of gutenberg required by the block or plugin is available.
116 */
117 public static function is_gutenberg_version_available( $version_requirements, $slug ) {
118 global $wp_version;
119
120 // Bail if we don't at least have the gutenberg version requirement, the WP version is optional.
121 if ( empty( $version_requirements['gutenberg'] ) ) {
122 return false;
123 }
124
125 // If running a local dev build of gutenberg plugin GUTENBERG_DEVELOPMENT_MODE is set so assume correct version.
126 if ( defined( 'GUTENBERG_DEVELOPMENT_MODE' ) && GUTENBERG_DEVELOPMENT_MODE ) {
127 return true;
128 }
129
130 $version_available = false;
131
132 // If running a production build of the gutenberg plugin then GUTENBERG_VERSION is set, otherwise if WP version
133 // with required version of Gutenberg is known check that.
134 if ( defined( 'GUTENBERG_VERSION' ) ) {
135 $version_available = version_compare( GUTENBERG_VERSION, $version_requirements['gutenberg'], '>=' );
136 } elseif ( ! empty( $version_requirements['wp'] ) ) {
137 $version_available = version_compare( $wp_version, $version_requirements['wp'], '>=' );
138 }
139
140 if ( ! $version_available ) {
141 $slug = self::remove_extension_prefix( $slug );
142 self::set_extension_unavailable(
143 $slug,
144 'incorrect_gutenberg_version',
145 array(
146 'required_feature' => $slug,
147 'required_version' => $version_requirements,
148 'current_version' => array(
149 'wp' => $wp_version,
150 'gutenberg' => defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : null,
151 ),
152 )
153 );
154 }
155
156 return $version_available;
157 }
158
159 /**
160 * Prepend the 'jetpack/' prefix to a block name
161 *
162 * @param string $block_name The block name.
163 *
164 * @return string The prefixed block name.
165 */
166 private static function prepend_block_prefix( $block_name ) {
167 return 'jetpack/' . $block_name;
168 }
169
170 /**
171 * Remove the 'jetpack/' or jetpack-' prefix from an extension name
172 *
173 * @param string $extension_name The extension name.
174 *
175 * @return string The unprefixed extension name.
176 */
177 public static function remove_extension_prefix( $extension_name ) {
178 if ( str_starts_with( $extension_name, 'jetpack/' ) || str_starts_with( $extension_name, 'jetpack-' ) ) {
179 return substr( $extension_name, strlen( 'jetpack/' ) );
180 }
181 return $extension_name;
182 }
183
184 /**
185 * Whether two arrays share at least one item
186 *
187 * @param array $a An array.
188 * @param array $b Another array.
189 *
190 * @return boolean True if $a and $b share at least one item
191 */
192 protected static function share_items( $a, $b ) {
193 return array_intersect( $a, $b ) !== array();
194 }
195
196 /**
197 * Set a (non-block) extension as available
198 *
199 * @param string $slug Slug of the extension.
200 */
201 public static function set_extension_available( $slug ) {
202 $slug = self::remove_extension_prefix( $slug );
203 self::$availability[ $slug ] = true;
204 }
205
206 /**
207 * Set the reason why an extension (block or plugin) is unavailable
208 *
209 * @param string $slug Slug of the extension.
210 * @param string $reason A string representation of why the extension is unavailable.
211 * @param array $details A free-form array containing more information on why the extension is unavailable.
212 */
213 public static function set_extension_unavailable( $slug, $reason, $details = array() ) {
214 if (
215 // Extensions that require a plan may be eligible for upgrades.
216 'missing_plan' === $reason
217 && (
218 /**
219 * Filter 'jetpack_block_editor_enable_upgrade_nudge' with `true` to enable or `false`
220 * to disable paid feature upgrade nudges in the block editor.
221 *
222 * When this is changed to default to `true`, you should also update `modules/memberships/class-jetpack-memberships.php`
223 * See https://github.com/Automattic/jetpack/pull/13394#pullrequestreview-293063378
224 *
225 * @since 7.7.0
226 *
227 * @param boolean
228 */
229 ! apply_filters( 'jetpack_block_editor_enable_upgrade_nudge', false )
230 /** This filter is documented in _inc/lib/admin-pages/class.jetpack-react-page.php */
231 || ! apply_filters( 'jetpack_show_promotions', true )
232 )
233 ) {
234 // The block editor may apply an upgrade nudge if `missing_plan` is the reason.
235 // Add a descriptive suffix to disable behavior but provide informative reason.
236 $reason .= '__nudge_disabled';
237 }
238 $slug = self::remove_extension_prefix( $slug );
239 self::$availability[ $slug ] = array(
240 'reason' => $reason,
241 'details' => $details,
242 );
243 }
244
245 /**
246 * Used to initialize the class, no longer in use.
247 *
248 * @return void
249 * @deprecated 12.2 No longer needed.
250 */
251 public static function init() {
252 _deprecated_function( __METHOD__, '12.2' );
253 }
254
255 /**
256 * Resets the class to its original state
257 *
258 * Used in unit tests
259 *
260 * @return void
261 */
262 public static function reset() {
263 self::$extensions = null;
264 self::$availability = array();
265 self::$cached_availability = null;
266 self::$block_js_loading_strategies = array();
267 }
268
269 /**
270 * Return the Gutenberg extensions (blocks and plugins) directory
271 *
272 * @return string The Gutenberg extensions directory
273 */
274 public static function get_blocks_directory() {
275 /**
276 * Filter to select Gutenberg blocks directory
277 *
278 * @since 6.9.0
279 *
280 * @param string default: '_inc/blocks/'
281 */
282 return apply_filters( 'jetpack_blocks_directory', '_inc/blocks/' );
283 }
284
285 /**
286 * Checks for a given .json file in the blocks folder.
287 *
288 * @deprecated 14.3
289 *
290 * @param string $preset The name of the .json file to look for.
291 *
292 * @return bool True if the file is found.
293 */
294 public static function preset_exists( $preset ) {
295 _deprecated_function( __METHOD__, '14.3' );
296 return file_exists( JETPACK__PLUGIN_DIR . self::get_blocks_directory() . $preset . '.json' );
297 }
298
299 /**
300 * Decodes JSON loaded from the preset file in the blocks folder
301 *
302 * @since 14.3 Deprecated argument. Only one value is ever used.
303 *
304 * @param null $deprecated No longer used.
305 *
306 * @return mixed Returns an object if the file is present, or false if a valid .json file is not present.
307 */
308 public static function get_preset( $deprecated = null ) {
309 if ( $deprecated ) {
310 _deprecated_argument( __METHOD__, '14.3', 'The $preset argument is no longer needed or used.' );
311 }
312
313 if ( self::$preset_cache ) {
314 return self::$preset_cache;
315 }
316
317 self::$preset_cache = json_decode(
318 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
319 file_get_contents( JETPACK__PLUGIN_DIR . self::get_blocks_directory() . 'index.json' )
320 );
321 return self::$preset_cache;
322 }
323
324 /**
325 * Returns a list of Jetpack Gutenberg extensions (blocks and plugins), based on index.json
326 *
327 * @return array A list of blocks: eg [ 'publicize', 'markdown' ]
328 */
329 public static function get_jetpack_gutenberg_extensions_allowed_list() {
330 $preset_extensions_manifest = ( defined( 'TESTING_IN_JETPACK' ) && TESTING_IN_JETPACK ) ? array() : self::get_preset();
331 $blocks_variation = self::blocks_variation();
332
333 return self::get_extensions_preset_for_variation( $preset_extensions_manifest, $blocks_variation );
334 }
335
336 /**
337 * Returns a diff from a combined list of allowed extensions and extensions determined to be excluded
338 *
339 * @param array $allowed_extensions An array of allowed extensions.
340 *
341 * @return array A list of blocks: eg array( 'publicize', 'markdown' )
342 */
343 public static function get_available_extensions( $allowed_extensions = null ) {
344 $exclusions = get_option( 'jetpack_excluded_extensions', array() );
345 $allowed_extensions = $allowed_extensions === null ? self::get_jetpack_gutenberg_extensions_allowed_list() : $allowed_extensions;
346
347 // Avoid errors if option data is not as expected.
348 if ( ! is_array( $exclusions ) ) {
349 $exclusions = array();
350 }
351
352 return array_diff( $allowed_extensions, $exclusions );
353 }
354
355 /**
356 * Return true if the extension has been registered and there's nothing in the availablilty array.
357 *
358 * @param string $extension The name of the extension.
359 *
360 * @return bool whether the extension has been registered and there's nothing in the availablilty array.
361 */
362 public static function is_registered_and_no_entry_in_availability( $extension ) {
363 return self::is_registered( 'jetpack/' . $extension ) && ! isset( self::$availability[ $extension ] );
364 }
365
366 /**
367 * Return true if the extension has a true entry in the availablilty array.
368 *
369 * @param string $extension The name of the extension.
370 *
371 * @return bool whether the extension has a true entry in the availablilty array.
372 */
373 public static function is_available( $extension ) {
374 return isset( self::$availability[ $extension ] ) && true === self::$availability[ $extension ];
375 }
376
377 /**
378 * Get the availability of each block / plugin, or return the cached availability
379 * if it has already been calculated. Avoids re-registering extensions when not
380 * necessary.
381 *
382 * @return array A list of block and plugins and their availability status.
383 */
384 public static function get_cached_availability() {
385 if ( null === self::$cached_availability ) {
386 self::$cached_availability = self::get_availability();
387 }
388 return self::$cached_availability;
389 }
390
391 /**
392 * Get availability of each block / plugin.
393 *
394 * @return array A list of block and plugins and their availablity status
395 */
396 public static function get_availability() {
397 /**
398 * Fires before Gutenberg extensions availability is computed.
399 *
400 * In the function call you supply, use `Blocks::jetpack_register_block()` to set a block as available.
401 * Alternatively, use `Jetpack_Gutenberg::set_extension_available()` (for a non-block plugin), and
402 * `Jetpack_Gutenberg::set_extension_unavailable()` (if the block or plugin should not be registered
403 * but marked as unavailable).
404 *
405 * @since 7.0.0
406 */
407 do_action( 'jetpack_register_gutenberg_extensions' );
408
409 $available_extensions = array();
410
411 foreach ( static::get_extensions() as $extension ) {
412 $is_available = self::is_registered_and_no_entry_in_availability( $extension ) || self::is_available( $extension );
413 $available_extensions[ $extension ] = array(
414 'available' => $is_available,
415 );
416
417 if ( ! $is_available ) {
418 $reason = isset( self::$availability[ $extension ] ) ? self::$availability[ $extension ]['reason'] : 'missing_module';
419 $details = isset( self::$availability[ $extension ] ) ? self::$availability[ $extension ]['details'] : array();
420 $available_extensions[ $extension ]['unavailable_reason'] = $reason;
421 $available_extensions[ $extension ]['details'] = $details;
422 }
423 }
424
425 return $available_extensions;
426 }
427
428 /**
429 * Return the list of extensions that are available.
430 *
431 * @since 11.9
432 *
433 * @return array A list of block and plugins and their availability status.
434 */
435 public static function get_extensions() {
436 if ( ! static::should_load() ) {
437 return array();
438 }
439
440 if ( null === self::$extensions ) {
441 /**
442 * Filter the list of block editor extensions that are available through Jetpack.
443 *
444 * @since 7.0.0
445 *
446 * @param array
447 */
448 self::$extensions = apply_filters( 'jetpack_set_available_extensions', self::get_available_extensions() );
449
450 if ( ! is_array( self::$extensions ) ) {
451 _doing_it_wrong( __METHOD__, esc_html__( 'The jetpack_set_available_extensions filter must return an array.', 'jetpack' ), '14.9' );
452 self::$extensions = array();
453 }
454 }
455
456 return self::$extensions;
457 }
458
459 /**
460 * Check if an extension/block is already registered
461 *
462 * @since 7.2
463 *
464 * @param string $slug Name of extension/block to check.
465 *
466 * @return bool
467 */
468 public static function is_registered( $slug ) {
469 return WP_Block_Type_Registry::get_instance()->is_registered( $slug );
470 }
471
472 /**
473 * Check if Gutenberg editor is available
474 *
475 * @since 6.7.0
476 *
477 * @return bool
478 */
479 public static function is_gutenberg_available() {
480 return true;
481 }
482
483 /**
484 * Check whether conditions indicate Gutenberg Extensions (blocks and plugins) should be loaded
485 *
486 * Loading blocks and plugins is enabled by default and may be disabled via filter:
487 * add_filter( 'jetpack_gutenberg', '__return_false' );
488 *
489 * @since 6.9.0
490 *
491 * @return bool
492 */
493 public static function should_load() {
494 if ( ! Jetpack::is_connection_ready() && ! ( new Status() )->is_offline_mode() ) {
495 return false;
496 }
497
498 $return = true;
499
500 if ( ! ( new Modules() )->is_active( 'blocks' ) ) {
501 $return = false;
502 }
503
504 /**
505 * Filter to enable Gutenberg blocks.
506 *
507 * Defaults to true if (connected or in offline mode) and the Blocks module is active.
508 *
509 * @since 6.5.0
510 * @since 13.9 Filter is able to activate or deactivate Gutenberg blocks.
511 *
512 * @param bool true Whether to load Gutenberg blocks
513 */
514 return (bool) apply_filters( 'jetpack_gutenberg', $return );
515 }
516
517 /**
518 * Queue a script to set `Jetpack_Block_Assets_Base_Url`.
519 *
520 * In certain cases Webpack needs to know a base to load additional assets from.
521 * Normally it can determine that itself, but when JS concatenation is involved that tends to confuse it.
522 * We work around that by explicitly outputting a variable with the correct URL.
523 * We set that as its own "script" so we can reliably only output it once.
524 */
525 private static function register_blocks_assets_base_url() {
526 if ( ! wp_script_is( 'jetpack-blocks-assets-base-url', 'registered' ) ) {
527 // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- No actual script, so no version needed.
528 wp_register_script( 'jetpack-blocks-assets-base-url', false, array(), null, array( 'in_footer' => false ) );
529 $json_encode_flags = JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP;
530 if ( get_option( 'blog_charset' ) === 'UTF-8' ) {
531 $json_encode_flags |= JSON_UNESCAPED_UNICODE;
532 }
533 wp_add_inline_script(
534 'jetpack-blocks-assets-base-url',
535 'var Jetpack_Block_Assets_Base_Url=' . wp_json_encode( plugins_url( self::get_blocks_directory(), JETPACK__PLUGIN_FILE ), $json_encode_flags ) . ';',
536 'before'
537 );
538 }
539 }
540
541 /**
542 * Only enqueue block assets when needed.
543 *
544 * @param string $type Slug of the block or absolute path to the block source code directory.
545 * @param array $script_dependencies Script dependencies. Will be merged with automatically
546 * detected script dependencies from the webpack build.
547 *
548 * @return void
549 */
550 public static function load_assets_as_required( $type, $script_dependencies = array() ) {
551 if ( is_admin() ) {
552 // A block's view assets will not be required in wp-admin.
553 return;
554 }
555
556 // Retrieve the feature from block.json if a path is passed.
557 if ( path_is_absolute( $type ) ) {
558 $metadata = Blocks::get_block_metadata_from_file( Blocks::get_path_to_block_metadata( $type ) );
559 $feature = Blocks::get_block_feature_from_metadata( $metadata );
560
561 if ( ! empty( $feature ) ) {
562 $type = $feature;
563 }
564 }
565
566 $type = sanitize_title_with_dashes( $type );
567 self::load_styles_as_required( $type );
568 self::load_scripts_as_required( $type, $script_dependencies );
569 }
570
571 /**
572 * Only enqueue block sytles when needed.
573 *
574 * @param string $type Slug of the block.
575 *
576 * @since 7.2.0
577 *
578 * @return void
579 */
580 public static function load_styles_as_required( $type ) {
581 if ( is_admin() ) {
582 // A block's view assets will not be required in wp-admin.
583 return;
584 }
585
586 // Enqueue styles.
587 $style_relative_path = self::get_blocks_directory() . $type . '/view' . ( is_rtl() ? '.rtl' : '' ) . '.css';
588 if ( self::block_has_asset( $style_relative_path ) ) {
589 $style_version = self::get_asset_version( $style_relative_path );
590 $view_style = plugins_url( $style_relative_path, JETPACK__PLUGIN_FILE );
591 $view_style = add_query_arg( 'minify', 'false', $view_style );
592
593 // If this is a customizer preview, render the style directly to the preview after autosave.
594 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
595 if ( is_customize_preview() && ! empty( $_GET['customize_autosaved'] ) ) {
596 // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet
597 echo '<link rel="stylesheet" id="jetpack-block-' . esc_attr( $type ) . '" href="' . esc_attr( $view_style ) . '&amp;ver=' . esc_attr( $style_version ) . '" media="all">';
598 } else {
599 wp_enqueue_style( 'jetpack-block-' . $type, $view_style, array(), $style_version );
600 wp_style_add_data( 'jetpack-block-' . $type, 'path', JETPACK__PLUGIN_DIR . $style_relative_path );
601 }
602 }
603 }
604
605 /**
606 * Only enqueue block scripts when needed.
607 *
608 * @param string $type Slug of the block.
609 * @param array $script_dependencies Script dependencies. Will be merged with automatically
610 * detected script dependencies from the webpack build.
611 *
612 * @since 7.2.0
613 *
614 * @return void
615 */
616 public static function load_scripts_as_required( $type, $script_dependencies = array() ) {
617 if ( is_admin() ) {
618 // A block's view assets will not be required in wp-admin.
619 return;
620 }
621
622 self::register_blocks_assets_base_url();
623
624 // Enqueue script.
625 $script_relative_path = self::get_blocks_directory() . $type . '/view.js';
626 $script_deps_path = JETPACK__PLUGIN_DIR . self::get_blocks_directory() . $type . '/view.asset.php';
627 $script_dependencies[] = 'jetpack-blocks-assets-base-url';
628 if ( file_exists( $script_deps_path ) ) {
629 $asset_manifest = include $script_deps_path;
630 $script_dependencies = array_unique( array_merge( $script_dependencies, $asset_manifest['dependencies'] ) );
631 }
632
633 if ( ! Blocks::is_amp_request() && self::block_has_asset( $script_relative_path ) ) {
634 $script_version = self::get_asset_version( $script_relative_path );
635 $view_script = plugins_url( $script_relative_path, JETPACK__PLUGIN_FILE );
636 $view_script = add_query_arg( 'minify', 'false', $view_script );
637 $strategy = self::get_block_js_loading_strategy( $type );
638
639 // Enqueue dependencies.
640 wp_enqueue_script( 'jetpack-block-' . $type, $view_script, $script_dependencies, $script_version, $strategy );
641
642 // If this is a customizer preview, enqueue the dependencies and render the script directly to the preview after autosave.
643 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
644 if ( is_customize_preview() && ! empty( $_GET['customize_autosaved'] ) ) {
645 // The Map block is dependent on wp-element, and it doesn't appear to to be possible to load
646 // this dynamically into the customizer iframe currently.
647 if ( 'map' === $type ) {
648 echo '<div>' . esc_html__( 'No map preview available. Publish and refresh to see this widget.', 'jetpack' ) . '</div>';
649 echo '<script>';
650 echo 'Array.from(document.getElementsByClassName(\'wp-block-jetpack-map\')).forEach(function(element){element.style.display = \'none\';})';
651 echo '</script>';
652 } else {
653 echo '<script id="jetpack-block-' . esc_attr( $type ) . '" src="' . esc_attr( $view_script ) . '&amp;ver=' . esc_attr( $script_version ) . '"></script>';
654 }
655 }
656 }
657 }
658
659 /**
660 * Check if an asset exists for a block.
661 *
662 * @param string $file Path of the file we are looking for.
663 *
664 * @return bool $block_has_asset Does the file exist.
665 */
666 public static function block_has_asset( $file ) {
667 return file_exists( JETPACK__PLUGIN_DIR . $file );
668 }
669
670 /**
671 * Get the version number to use when loading the file. Allows us to bypass cache when developing.
672 *
673 * @param string $file Path of the file we are looking for.
674 *
675 * @return string $script_version Version number.
676 */
677 public static function get_asset_version( $file ) {
678 return Jetpack::is_development_version() && self::block_has_asset( $file )
679 ? filemtime( JETPACK__PLUGIN_DIR . $file )
680 : JETPACK__VERSION;
681 }
682
683 /**
684 * Load Gutenberg editor assets
685 *
686 * @since 6.7.0
687 *
688 * @return void
689 */
690 public static function enqueue_block_editor_assets() {
691 if ( ! self::should_load() ) {
692 return;
693 }
694
695 $status = new Status();
696
697 // Required for Analytics. See _inc/lib/admin-pages/class.jetpack-admin-page.php.
698 if ( ! $status->is_offline_mode() && Jetpack::is_connection_ready() ) {
699 wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true );
700 }
701
702 $blocks_dir = self::get_blocks_directory();
703 $blocks_variation = self::blocks_variation();
704
705 if ( 'production' !== $blocks_variation ) {
706 $blocks_env = '-' . esc_attr( $blocks_variation );
707 } else {
708 $blocks_env = '';
709 }
710
711 self::register_blocks_assets_base_url();
712
713 Assets::register_script(
714 'jetpack-blocks-editor',
715 "{$blocks_dir}editor{$blocks_env}.js",
716 JETPACK__PLUGIN_FILE,
717 array(
718 'textdomain' => 'jetpack',
719 'dependencies' => array( 'jetpack-blocks-assets-base-url' ),
720 )
721 );
722
723 /**
724 * This can be called multiple times per page load in the admin, during the `enqueue_block_assets` action.
725 * These assets are necessary for the admin for editing but are not necessary for each pattern preview.
726 * Therefore we dequeue them, so they don't load for each pattern preview iframe.
727 */
728 if ( ! wp_should_load_block_editor_scripts_and_styles() ) {
729 wp_dequeue_script( 'jp-tracks' );
730 wp_dequeue_script( 'jetpack-blocks-editor' );
731
732 return;
733 }
734
735 // Hack around #20357 (specifically, that the editor bundle depends on
736 // wp-edit-post but wp-edit-post's styles break the Widget Editor and
737 // Site Editor) until a real fix gets unblocked.
738 // @todo Remove this once #20357 is properly fixed.
739 $wp_styles_fix = wp_styles()->query( 'jetpack-blocks-editor', 'registered' );
740 if ( empty( $wp_styles_fix ) ) {
741 wp_die( 'Your installation of Jetpack is incomplete. Please run "jetpack build plugins/jetpack" in your dev env.' );
742 }
743 wp_styles()->query( 'jetpack-blocks-editor', 'registered' )->deps = array();
744
745 Assets::enqueue_script( 'jetpack-blocks-editor' );
746
747 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
748 $user = wp_get_current_user();
749 $user_data = array(
750 'email' => $user->user_email,
751 'userid' => $user->ID,
752 'username' => $user->user_login,
753 );
754 $blog_id = get_current_blog_id();
755 $is_current_user_connected = true;
756 } else {
757 $user_data = Jetpack_Tracks_Client::get_connected_user_tracks_identity();
758 $blog_id = Jetpack_Options::get_option( 'id', 0 );
759 $is_current_user_connected = ( new Connection_Manager( 'jetpack' ) )->is_user_connected();
760 }
761
762 if ( $blocks_variation === 'beta' && $is_current_user_connected ) {
763 wp_enqueue_style( 'recoleta-font', '//s1.wp.com/i/fonts/recoleta/css/400.min.css', array(), Constants::get_constant( 'JETPACK__VERSION' ) );
764 }
765 // AI Assistant
766 $ai_assistant_state = array(
767 'is-enabled' => apply_filters( 'jetpack_ai_enabled', true ),
768 );
769
770 $screen_base = null;
771 if ( function_exists( 'get_current_screen' ) ) {
772 $current_screen = get_current_screen();
773 $screen_base = $current_screen ? $current_screen->base : null;
774 }
775
776 $modules = array();
777 if ( class_exists( 'Jetpack_Core_API_Module_List_Endpoint' ) ) {
778 $module_list_endpoint = new Jetpack_Core_API_Module_List_Endpoint();
779 $modules = $module_list_endpoint->get_modules();
780 }
781
782 $jetpack_plan = Jetpack_Plan::get();
783 $initial_state = array(
784 'available_blocks' => self::get_availability(),
785 'blocks_variation' => $blocks_variation,
786 'modules' => $modules,
787 'jetpack' => array(
788 'is_active' => Jetpack::is_connection_ready(),
789 'is_current_user_connected' => $is_current_user_connected,
790 /** This filter is documented in class.jetpack-gutenberg.php */
791 'enable_upgrade_nudge' => apply_filters( 'jetpack_block_editor_enable_upgrade_nudge', false ),
792 'is_private_site' => $status->is_private_site(),
793 'is_coming_soon' => $status->is_coming_soon(),
794 'is_offline_mode' => $status->is_offline_mode(),
795 'is_newsletter_feature_enabled' => class_exists( '\Jetpack_Memberships' ),
796 // this is the equivalent of JP initial state siteData.showMyJetpack (class-jetpack-redux-state-helper)
797 // used to determine if we can link to My Jetpack from the block editor
798 'is_my_jetpack_available' => My_Jetpack_Initializer::should_initialize(),
799 'jetpack_plan' => array(
800 'data' => $jetpack_plan['product_slug'],
801 ),
802 /**
803 * Enable the RePublicize UI in the block editor context.
804 *
805 * @module publicize
806 *
807 * @since 10.3.0
808 * @deprecated 11.5 This is a feature flag that is no longer used.
809 *
810 * @param bool true Enable the RePublicize UI in the block editor context. Defaults to true.
811 */
812 'republicize_enabled' => apply_filters( 'jetpack_block_editor_republicize_feature', true ),
813 ),
814 'siteFragment' => $status->get_site_suffix(),
815 'adminUrl' => esc_url( admin_url() ),
816 'tracksUserData' => $user_data,
817 'wpcomBlogId' => $blog_id,
818 'allowedMimeTypes' => wp_get_mime_types(),
819 'siteLocale' => str_replace( '_', '-', get_locale() ),
820 'ai-assistant' => $ai_assistant_state,
821 'screenBase' => $screen_base,
822 /**
823 * Add your own feature flags to the block editor.
824 *
825 * You can access the feature flags in the block editor via hasFeatureFlag( 'your-feature-flag' ) function.
826 *
827 * @since 14.8
828 *
829 * @param array true Enable the RePublicize UI in the block editor context. Defaults to true.
830 */
831 'feature_flags' => apply_filters( 'jetpack_block_editor_feature_flags', array() ),
832 'pluginBasePath' => plugins_url( '', Constants::get_constant( 'JETPACK__PLUGIN_FILE' ) ),
833 );
834
835 wp_localize_script(
836 'jetpack-blocks-editor',
837 'Jetpack_Editor_Initial_State',
838 $initial_state
839 );
840
841 // Adds Connection package initial state.
842 Connection_Initial_State::render_script( 'jetpack-blocks-editor' );
843 }
844
845 /**
846 * Some blocks do not depend on a specific module,
847 * and can consequently be loaded outside of the usual modules.
848 * We will look for such modules in the extensions/ directory.
849 *
850 * @since 7.1.0
851 * @see wp_common_block_scripts_and_styles()
852 */
853 public static function load_independent_blocks() {
854 if ( self::should_load() ) {
855 /**
856 * Look for files that match our list of available Jetpack Gutenberg extensions (blocks and plugins).
857 * If available, load them.
858 */
859 $directories = array( 'blocks', 'plugins', 'extended-blocks' );
860
861 foreach ( static::get_extensions() as $extension ) {
862 foreach ( $directories as $dirname ) {
863 $path = JETPACK__PLUGIN_DIR . "extensions/{$dirname}/{$extension}/{$extension}.php";
864
865 if ( file_exists( $path ) ) {
866 include_once $path;
867 continue 2;
868 }
869 }
870 }
871 }
872 }
873
874 /**
875 * Loads PHP components of block editor extensions.
876 *
877 * @since 8.9.0
878 */
879 public static function load_block_editor_extensions() {
880 if ( self::should_load() ) {
881 // Block editor extensions to load.
882 $extensions_to_load = array(
883 'extended-blocks',
884 'plugins',
885 );
886
887 // Collect the extension paths.
888 foreach ( $extensions_to_load as $extension_to_load ) {
889 $extensions_folder = glob( JETPACK__PLUGIN_DIR . 'extensions/' . $extension_to_load . '/*' );
890
891 // Require each of the extension files, in case it exists.
892 foreach ( $extensions_folder as $extension_folder ) {
893 $name = basename( $extension_folder );
894 $extension_file_path = JETPACK__PLUGIN_DIR . 'extensions/' . $extension_to_load . '/' . $name . '/' . $name . '.php';
895
896 if ( file_exists( $extension_file_path ) ) {
897 include_once $extension_file_path;
898 }
899 }
900 }
901 }
902 }
903
904 /**
905 * Determine whether a site should use the default set of blocks, or a custom set.
906 * Possible variations are currently beta, experimental, and production.
907 *
908 * @since 8.1.0
909 *
910 * @return string $block_varation production|beta|experimental
911 */
912 public static function blocks_variation() {
913 // Default to production blocks.
914 $block_varation = 'production';
915
916 /*
917 * Prefer to use this JETPACK_BLOCKS_VARIATION constant
918 * or the jetpack_blocks_variation filter
919 * to set the block variation in your code.
920 */
921 $default = Constants::get_constant( 'JETPACK_BLOCKS_VARIATION' );
922 if ( ! empty( $default ) && in_array( $default, array( 'beta', 'experimental', 'production' ), true ) ) {
923 $block_varation = $default;
924 }
925
926 /**
927 * Alternative to `JETPACK_BETA_BLOCKS`, set to `true` to load Beta Blocks.
928 *
929 * @since 6.9.0
930 * @deprecated 11.8.0 Use jetpack_blocks_variation filter instead.
931 *
932 * @param boolean
933 */
934 $is_beta = apply_filters_deprecated(
935 'jetpack_load_beta_blocks',
936 array( false ),
937 'jetpack-11.8.0',
938 'jetpack_blocks_variation'
939 );
940
941 /*
942 * Switch to beta blocks if you use the JETPACK_BETA_BLOCKS constant
943 * or the deprecated jetpack_load_beta_blocks filter.
944 * This only applies when not using the newer JETPACK_BLOCKS_VARIATION constant.
945 */
946 if (
947 empty( $default )
948 && (
949 $is_beta
950 || Constants::is_true( 'JETPACK_BETA_BLOCKS' )
951 )
952 ) {
953 $block_varation = 'beta';
954 }
955
956 /**
957 * Alternative to `JETPACK_EXPERIMENTAL_BLOCKS`, set to `true` to load Experimental Blocks.
958 *
959 * @since 6.9.0
960 * @deprecated 11.8.0 Use jetpack_blocks_variation filter instead.
961 *
962 * @param boolean
963 */
964 $is_experimental = apply_filters_deprecated(
965 'jetpack_load_experimental_blocks',
966 array( false ),
967 'jetpack-11.8.0',
968 'jetpack_blocks_variation'
969 );
970
971 /*
972 * Switch to experimental blocks if you use the JETPACK_EXPERIMENTAL_BLOCKS constant
973 * or the deprecated jetpack_load_experimental_blocks filter.
974 * This only applies when not using the newer JETPACK_BLOCKS_VARIATION constant.
975 */
976 if (
977 empty( $default )
978 && (
979 $is_experimental
980 || Constants::is_true( 'JETPACK_EXPERIMENTAL_BLOCKS' )
981 )
982 ) {
983 $block_varation = 'experimental';
984 }
985
986 /**
987 * Allow customizing the variation of blocks in use on a site.
988 * Overwrites any previously set values, whether by constant or filter.
989 *
990 * @since 8.1.0
991 *
992 * @param string $block_variation Can be beta, experimental, and production. Defaults to production.
993 */
994 return apply_filters( 'jetpack_blocks_variation', $block_varation );
995 }
996
997 /**
998 * Get a list of extensions available for the variation you chose.
999 *
1000 * @since 8.1.0
1001 *
1002 * @param object $preset_extensions_manifest List of extensions available in Jetpack.
1003 * @param string $blocks_variation Subset of blocks. production|beta|experimental.
1004 *
1005 * @return array $preset_extensions Array of extensions for that variation
1006 */
1007 public static function get_extensions_preset_for_variation( $preset_extensions_manifest, $blocks_variation ) {
1008 $preset_extensions = isset( $preset_extensions_manifest->{ $blocks_variation } )
1009 ? (array) $preset_extensions_manifest->{ $blocks_variation }
1010 : array();
1011
1012 /*
1013 * Experimental and Beta blocks need the production blocks as well.
1014 */
1015 if (
1016 'experimental' === $blocks_variation
1017 || 'beta' === $blocks_variation
1018 ) {
1019 $production_extensions = isset( $preset_extensions_manifest->production )
1020 ? (array) $preset_extensions_manifest->production
1021 : array();
1022
1023 $preset_extensions = array_unique( array_merge( $preset_extensions, $production_extensions ) );
1024 }
1025
1026 /*
1027 * Beta blocks need the experimental blocks as well.
1028 *
1029 * If you've chosen to see Beta blocks,
1030 * we want to make all blocks available to you:
1031 * - Production
1032 * - Experimental
1033 * - Beta
1034 */
1035 if ( 'beta' === $blocks_variation ) {
1036 $production_extensions = isset( $preset_extensions_manifest->experimental )
1037 ? (array) $preset_extensions_manifest->experimental
1038 : array();
1039
1040 $preset_extensions = array_unique( array_merge( $preset_extensions, $production_extensions ) );
1041 }
1042
1043 return $preset_extensions;
1044 }
1045
1046 /**
1047 * Validate a URL used in a SSR block.
1048 *
1049 * @since 8.3.0
1050 *
1051 * @param string $url URL saved as an attribute in block.
1052 * @param array $allowed Array of allowed hosts for that block, or regexes to check against.
1053 * @param bool $is_regex Array of regexes matching the URL that could be used in block.
1054 *
1055 * @return bool|string
1056 */
1057 public static function validate_block_embed_url( $url, $allowed = array(), $is_regex = false ) {
1058 if (
1059 empty( $url )
1060 || ! is_array( $allowed )
1061 || empty( $allowed )
1062 ) {
1063 return false;
1064 }
1065
1066 $url_components = wp_parse_url( $url );
1067
1068 // Bail early if we cannot find a host.
1069 if ( empty( $url_components['host'] ) ) {
1070 return false;
1071 }
1072
1073 // Normalize URL.
1074 $url = sprintf(
1075 '%s://%s%s%s',
1076 isset( $url_components['scheme'] ) ? $url_components['scheme'] : 'https',
1077 $url_components['host'],
1078 isset( $url_components['path'] ) ? $url_components['path'] : '/',
1079 isset( $url_components['query'] ) ? '?' . $url_components['query'] : ''
1080 );
1081
1082 if ( ! empty( $url_components['fragment'] ) ) {
1083 $url = $url . '#' . rawurlencode( $url_components['fragment'] );
1084 }
1085
1086 /*
1087 * If we're using an allowed list of hosts,
1088 * check if the URL belongs to one of the domains allowed for that block.
1089 */
1090 if (
1091 false === $is_regex
1092 && in_array( $url_components['host'], $allowed, true )
1093 ) {
1094 return $url;
1095 }
1096
1097 /*
1098 * If we are using an array of regexes to check against,
1099 * loop through that.
1100 */
1101 if ( true === $is_regex ) {
1102 foreach ( $allowed as $regex ) {
1103 if ( 1 === preg_match( $regex, $url ) ) {
1104 return $url;
1105 }
1106 }
1107 }
1108
1109 return false;
1110 }
1111
1112 /**
1113 * Determines whether a preview of the block with an upgrade nudge should
1114 * be displayed for admins on the site frontend.
1115 *
1116 * @since 8.4.0
1117 *
1118 * @param array $availability_for_block The availability for the block.
1119 *
1120 * @return bool
1121 */
1122 public static function should_show_frontend_preview( $availability_for_block ) {
1123 return (
1124 isset( $availability_for_block['details']['required_plan'] )
1125 && current_user_can( 'manage_options' )
1126 && ! is_feed()
1127 );
1128 }
1129
1130 /**
1131 * Output an UpgradeNudge Component on the frontend of a site.
1132 *
1133 * @since 8.4.0
1134 *
1135 * @param string $plan The plan that users need to purchase to make the block work.
1136 *
1137 * @return string
1138 */
1139 public static function upgrade_nudge( $plan ) {
1140 require_once JETPACK__PLUGIN_DIR . '_inc/lib/components.php';
1141 return Jetpack_Components::render_upgrade_nudge(
1142 array(
1143 'plan' => $plan,
1144 )
1145 );
1146 }
1147
1148 /**
1149 * Output a notice within a block.
1150 *
1151 * @since 8.6.0
1152 *
1153 * @param string $message Notice we want to output.
1154 * @param string $status Status of the notice. Can be one of success, info, warning, error. info by default.
1155 * @param string $classes List of CSS classes.
1156 *
1157 * @return string
1158 */
1159 public static function notice( $message, $status = 'info', $classes = '' ) {
1160 if (
1161 empty( $message )
1162 || ! in_array( $status, array( 'success', 'info', 'warning', 'error' ), true )
1163 ) {
1164 return '';
1165 }
1166
1167 $color = '';
1168 switch ( $status ) {
1169 case 'success':
1170 $color = '#00a32a';
1171 break;
1172 case 'warning':
1173 $color = '#dba617';
1174 break;
1175 case 'error':
1176 $color = '#d63638';
1177 break;
1178 case 'info':
1179 default:
1180 $color = '#72aee6';
1181 break;
1182 }
1183
1184 return sprintf(
1185 '<div class="jetpack-block__notice %1$s %3$s" style="border-left:5px solid %4$s;padding:1em;background-color:#f8f9f9;">%2$s</div>',
1186 esc_attr( $status ),
1187 wp_kses(
1188 $message,
1189 array(
1190 'br' => array(),
1191 'p' => array(),
1192 'a' => array(
1193 'href' => array(),
1194 'target' => array(),
1195 'rel' => array(),
1196 ),
1197 )
1198 ),
1199 esc_attr( $classes ),
1200 sanitize_hex_color( $color )
1201 );
1202 }
1203
1204 /**
1205 * Retrieve site-specific features for Simple sites.
1206 *
1207 * We're caching the data for the lifetime of the request, because it can be slow to calculate,
1208 * and it can be called multiple times per single request.
1209 *
1210 * We intentionally don't use object caching or any other type of persistent caching,
1211 * in order to avoid complex cache invalidation on subscription addition or removal.
1212 *
1213 * @since 10.7
1214 *
1215 * @return array
1216 */
1217 private static function get_site_specific_features() {
1218 $current_blog_id = get_current_blog_id();
1219
1220 if ( isset( self::$site_specific_features[ $current_blog_id ] ) ) {
1221 return self::$site_specific_features[ $current_blog_id ];
1222 }
1223
1224 if ( ! class_exists( 'Store_Product_List' ) ) {
1225 require WP_CONTENT_DIR . '/admin-plugins/wpcom-billing/store-product-list.php';
1226 }
1227
1228 $site_specific_features = Store_Product_List::get_site_specific_features_data( $current_blog_id );
1229 self::$site_specific_features[ $current_blog_id ] = $site_specific_features;
1230
1231 return $site_specific_features;
1232 }
1233
1234 /**
1235 * Set the availability of the block as the editor
1236 * is loaded.
1237 *
1238 * @param string $slug Slug of the block.
1239 */
1240 public static function set_availability_for_plan( $slug ) {
1241 $slug = self::remove_extension_prefix( $slug );
1242
1243 if ( Jetpack_Plan::supports( $slug ) ) {
1244 self::set_extension_available( $slug );
1245 return;
1246 }
1247
1248 // Check what's the minimum plan where the feature is available.
1249 $plan = '';
1250 $features_data = array();
1251 $is_simple_site = defined( 'IS_WPCOM' ) && IS_WPCOM;
1252 $is_atomic_site = ( new Host() )->is_woa_site();
1253
1254 if ( $is_simple_site || $is_atomic_site ) {
1255 // Simple sites.
1256 if ( $is_simple_site ) {
1257 $features_data = self::get_site_specific_features();
1258 } else {
1259 // Atomic sites.
1260 $option = get_option( 'jetpack_active_plan' );
1261 if ( isset( $option['features'] ) ) {
1262 $features_data = $option['features'];
1263 }
1264 }
1265
1266 if ( ! empty( $features_data['available'][ $slug ] ) ) {
1267 $plan = $features_data['available'][ $slug ][0];
1268 } elseif ( isset( self::$wpcom_minimum_plan_fallbacks[ $slug ] ) ) {
1269 // Fallback for features with conditional availability (e.g., sticker-based gating)
1270 // that don't appear in features_data['available'].
1271 $plan = self::$wpcom_minimum_plan_fallbacks[ $slug ];
1272 }
1273 } else {
1274 // Jetpack sites.
1275 $plan = Jetpack_Plan::get_minimum_plan_for_feature( $slug );
1276 }
1277
1278 self::set_extension_unavailable(
1279 $slug,
1280 'missing_plan',
1281 array(
1282 'required_feature' => $slug,
1283 'required_plan' => $plan,
1284 )
1285 );
1286 }
1287
1288 /**
1289 * Wraps the suplied render_callback in a function to check
1290 * the availability of the block before rendering it.
1291 *
1292 * @param string $slug The block slug, used to check for availability.
1293 * @param callable $render_callback The render_callback that will be called if the block is available.
1294 */
1295 public static function get_render_callback_with_availability_check( $slug, $render_callback ) {
1296 return function ( $prepared_attributes, $block_content, $block ) use ( $render_callback, $slug ) {
1297 $availability = self::get_cached_availability();
1298 $bare_slug = self::remove_extension_prefix( $slug );
1299 if ( isset( $availability[ $bare_slug ] ) && $availability[ $bare_slug ]['available'] ) {
1300 return call_user_func( $render_callback, $prepared_attributes, $block_content, $block );
1301 }
1302
1303 // A preview of the block is rendered for admins on the frontend with an upgrade nudge.
1304 if ( isset( $availability[ $bare_slug ] ) ) {
1305 if ( self::should_show_frontend_preview( $availability[ $bare_slug ] ) ) {
1306 $block_preview = call_user_func( $render_callback, $prepared_attributes, $block_content, $block );
1307
1308 // If the upgrade nudge isn't already being displayed by a parent block, display the nudge.
1309 if ( isset( $block->attributes['shouldDisplayFrontendBanner'] ) && $block->attributes['shouldDisplayFrontendBanner'] ) {
1310 $upgrade_nudge = self::upgrade_nudge( $availability[ $bare_slug ]['details']['required_plan'] );
1311 return $upgrade_nudge . $block_preview;
1312 }
1313
1314 return $block_preview;
1315 }
1316 }
1317
1318 return null;
1319 };
1320 }
1321
1322 /**
1323 * Display a message to site editors and roles above when a block is no longer supported.
1324 * This is only displayed on the frontend.
1325 *
1326 * @since 12.3
1327 *
1328 * @param string $block_content The block content.
1329 * @param array $block The full block, including name and attributes.
1330 *
1331 * @return string
1332 */
1333 public static function display_deprecated_block_message( $block_content, $block ) {
1334 if ( isset( $block['blockName'] ) && in_array( $block['blockName'], self::$deprecated_blocks, true ) ) {
1335 if ( current_user_can( 'edit_posts' ) ) {
1336 $block_content = self::notice(
1337 __( 'This block is no longer supported. Its contents will no longer be displayed to your visitors and as such this block should be removed.', 'jetpack' ),
1338 'warning',
1339 'jetpack-block-deprecated'
1340 );
1341 } else {
1342 $block_content = '';
1343 }
1344 }
1345
1346 return $block_content;
1347 }
1348
1349 /**
1350 * Register block metadata collection for Jetpack blocks.
1351 * This allows for more efficient block metadata loading by avoiding
1352 * individual block.json file reads at runtime.
1353 *
1354 * Uses wp_register_block_metadata_collection() if the manifest file
1355 * exists. The manifest file is auto-generated during the build process.
1356 *
1357 * Runs on plugins_loaded to ensure registration happens before individual
1358 * blocks register themselves on init.
1359 *
1360 * @static
1361 * @since 14.1
1362 * @return void
1363 */
1364 public static function register_block_metadata_collection() {
1365 $meta_file_path = JETPACK__PLUGIN_DIR . '_inc/blocks/blocks-manifest.php';
1366 if ( file_exists( $meta_file_path ) ) {
1367 wp_register_block_metadata_collection(
1368 JETPACK__PLUGIN_DIR . '_inc/blocks/',
1369 $meta_file_path
1370 );
1371 }
1372 }
1373
1374 /**
1375 * Set the JS loading strategy for a block.
1376 *
1377 * @param string $block_name The block name.
1378 * @param array|bool $strategy The JS loading strategy.
1379 *
1380 * @since 15.0
1381 */
1382 public static function set_block_js_loading_strategy( $block_name, $strategy ) {
1383 self::$block_js_loading_strategies[ $block_name ] = $strategy;
1384 }
1385
1386 /**
1387 * Get the JS loading strategy for a block.
1388 *
1389 * @param string $block_name The block name.
1390 *
1391 * @return array|bool The JS loading strategy for the block.
1392 *
1393 * @since 15.0
1394 */
1395 public static function get_block_js_loading_strategy( $block_name ) {
1396 $strategy = array(
1397 'strategy' => 'defer',
1398 'in_footer' => true,
1399 );
1400
1401 if ( isset( self::$block_js_loading_strategies[ $block_name ] ) ) {
1402 $strategy = self::$block_js_loading_strategies[ $block_name ];
1403 }
1404
1405 return $strategy;
1406 }
1407 }
1408
1409 if ( ( new Host() )->is_woa_site() ) {
1410 /**
1411 * Enable upgrade nudge for Atomic sites.
1412 * This feature is false as default,
1413 * so let's enable it through this filter.
1414 *
1415 * More doc: https://github.com/Automattic/jetpack/blob/trunk/projects/plugins/jetpack/extensions/README.md#upgrades-for-blocks
1416 */
1417 add_filter( 'jetpack_block_editor_enable_upgrade_nudge', '__return_true' );
1418 }
1419