PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.2
Jetpack – WP Security, Backup, Speed, & Growth v16.2
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 13.8.3 All 506 releases
jetpack / jetpack_vendor / automattic / jetpack-blocks / src / class-blocks.php

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

582 lines 18.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /** Blocks package.
3 *
4 * @since 1.1.0
5 *
6 * This package lifts elements from Jetpack's Jetpack_Gutenberg class.
7 * It is now an standalone package reusable outside Jetpack.
8 *
9 * @package automattic/jetpack-blocks
10 */
11
12 namespace Automattic\Jetpack;
13
14 use Automattic\Jetpack\Constants as Jetpack_Constants;
15 use Jetpack_Gutenberg;
16
17 /**
18 * Register and manage blocks within a plugin. Used to manage block registration, enqueues, and more.
19 *
20 * @since 1.1.0
21 */
22 class Blocks {
23 /**
24 * Wrapper function to safely register a Gutenberg block type
25 *
26 * @see register_block_type
27 * @see Automattic\Jetpack\Blocks::is_gutenberg_version_available
28 *
29 * @since 1.1.0
30 *
31 * @param string $slug Slug of the block or absolute path to the block source code directory.
32 * @param array $args {
33 * Arguments that are passed into register_block_type.
34 * See register_block_type for full list of arguments.
35 * Can also include 2 extra arguments not currently supported by register_block_type.
36 *
37 * @type array $version_requirements Array containing required Gutenberg version and, if known, the WordPress version that was released with this minimum version.
38 * @type bool $plan_check Should we check for a specific plan before registering the block.
39 * }
40 *
41 * @return \WP_Block_Type|false The registered block type on success, or false on failure.
42 */
43 public static function jetpack_register_block( $slug, $args = array() ) {
44 // Slug doesn't start with `jetpack/`, isn't an absolute path, or doesn't contain a slash
45 // (synonym of a namespace) at all.
46 if ( ! str_starts_with( $slug, 'jetpack/' ) && ! path_is_absolute( $slug ) && ! strpos( $slug, '/' ) ) {
47 _doing_it_wrong( 'jetpack_register_block', 'Prefix the block with jetpack/ ', 'Jetpack 9.0.0' );
48 $slug = 'jetpack/' . $slug;
49 }
50
51 $block_type = $slug;
52
53 // If a path is passed, make sure to get the block.json file from the build directory and get
54 // the block name from that file.
55 if ( path_is_absolute( $slug ) ) {
56 $block_type = self::get_path_to_block_metadata( $slug );
57 $slug = self::get_block_name_from_path_convention( $slug );
58 }
59
60 if (
61 isset( $args['version_requirements'] )
62 && ! self::is_gutenberg_version_available( $args['version_requirements'], $slug )
63 ) {
64 return false;
65 }
66
67 // Checking whether block is registered to ensure it isn't registered twice.
68 if ( self::is_registered( $slug ) ) {
69 return false;
70 }
71
72 $feature_name = self::remove_extension_prefix( $slug );
73
74 // This is only useful in Jetpack.
75 if ( ! self::is_standalone_block() ) {
76 // If the block is dynamic, and a Jetpack block, wrap the render_callback to check availability.
77 if ( ! empty( $args['plan_check'] ) ) {
78 $existing_attributes = array();
79 $gated_blocks = array(
80 'jetpack/calendly',
81 'jetpack/donations',
82 'jetpack/payment-buttons',
83 'jetpack/paypal-payment-buttons',
84 );
85 if ( in_array( $slug, $gated_blocks, true ) &&
86 is_string( $block_type ) &&
87 file_exists( $block_type )
88 ) {
89 $metadata = self::get_block_metadata( $block_type );
90 $existing_attributes = $metadata['attributes'] ?? array();
91 }
92
93 // Set up attributes.
94 if ( ! isset( $args['attributes'] ) ) {
95 $args['attributes'] = array();
96 }
97 $args['attributes'] = array_merge(
98 $existing_attributes,
99 $args['attributes'],
100 array(
101 // Indicates that this block should display an upgrade nudge on the frontend when applicable.
102 'shouldDisplayFrontendBanner' => array(
103 'type' => 'boolean',
104 'default' => true,
105 ),
106 )
107 );
108 if ( isset( $args['render_callback'] ) ) {
109 $args['render_callback'] = Jetpack_Gutenberg::get_render_callback_with_availability_check( $feature_name, $args['render_callback'] );
110 }
111 $method_name = 'set_availability_for_plan';
112 } else {
113 $method_name = 'set_extension_available';
114 }
115
116 add_action(
117 'jetpack_register_gutenberg_extensions',
118 function () use ( $feature_name, $method_name ) {
119 call_user_func( array( 'Jetpack_Gutenberg', $method_name ), $feature_name );
120 }
121 );
122
123 // Ensure editor styles are registered so that the site editor knows about the
124 // editor style dependency when copying styles to the editor iframe.
125 if ( ! isset( $args['editor_style'] ) ) {
126 $args['editor_style'] = 'jetpack-blocks-editor';
127 }
128
129 // Keep track of the JS loading strategy for any block that specifies it.
130 if ( isset( $args['js_loading_strategy'] ) ) {
131 Jetpack_Gutenberg::set_block_js_loading_strategy( $feature_name, $args['js_loading_strategy'] );
132 }
133 }
134
135 return register_block_type( $block_type, $args );
136 }
137
138 /**
139 * Get the block metadata. Accepts a block.json file's path (or its folder's) or its content, in
140 * which case it becomes an identity function.
141 *
142 * It's used by other helpers in this class so that they can accept various types as argument.
143 *
144 * @param string|array $arg Path to block.json or its parent folder, or its content as an array.
145 *
146 * @return array The block metadata.
147 */
148 private static function get_block_metadata( $arg ) {
149 $metadata = is_array( $arg ) ? $arg : null;
150
151 if ( ! isset( $metadata ) ) {
152 $path = is_string( $arg ) ? $arg : null;
153
154 if ( isset( $path ) && ! empty( $path ) ) {
155 $metadata = self::get_block_metadata_from_file( self::get_path_to_block_metadata( $path ) );
156 }
157 }
158
159 return $metadata ?? array();
160 }
161
162 /**
163 * Read block metadata from a block.json file.
164 *
165 * @param string $filename The path to the block.json file or its directory.
166 *
167 * @return array The block metadata.
168 */
169 public static function get_block_metadata_from_file( $filename ) {
170 $metadata = array();
171 $needle = '/block.json';
172 $filename = $needle === substr( $filename, -strlen( $needle ) ) ? $filename : $filename . $needle;
173
174 if ( file_exists( $filename ) ) {
175 try {
176 $metadata = wp_json_file_decode( $filename, array( 'associative' => true ) );
177 } catch ( \Exception $e ) {
178 $metadata = array();
179 }
180 }
181
182 return $metadata;
183 }
184
185 /**
186 * Get the block name (includes the `jetpack` prefix).
187 *
188 * @param string|array $arg Path to block.json or its parent folder, or its content as an array.
189 *
190 * @return string The block name.
191 */
192 public static function get_block_name( $arg ) {
193 $metadata = self::get_block_metadata( $arg );
194
195 return self::get_block_name_from_metadata( $metadata );
196 }
197
198 /**
199 * Get the block name from the path convention.
200 * For example, path "./extensions/blocks/pinterest" is assumed to define
201 * a block named "jetpack/pinterest" without checking any files.
202 *
203 * Any exceptions should be added to the $breaks_convention array.
204 * For example, blocks/premium-content defines "premium-content\/container", not "jetpack/premium-content".
205 * These paths will use the code that checks the disk for the block name.
206 *
207 * The unit test test_get_block_name_from_path_convention_matches_get_block_name() verifies that
208 * all names are correctly guessed.
209 *
210 * Run with `./vendor/bin/phpunit --filter WP_Test_Jetpack_Gutenberg`.
211 *
212 * @param string $path The path to extract the block name from.
213 *
214 * @return string The block name with 'jetpack/' prefix.
215 */
216 public static function get_block_name_from_path_convention( $path ) {
217 $path_parts = explode( '/', $path );
218 if ( count( $path_parts ) <= 0 ) {
219 $block_type = self::get_path_to_block_metadata( $path );
220 return self::get_block_name( $block_type );
221 }
222
223 $last_part = $path_parts[ count( $path_parts ) - 1 ];
224 $breaks_convention = array( 'premium-content' );
225
226 if ( in_array( $last_part, $breaks_convention, true ) ) {
227 $block_type = self::get_path_to_block_metadata( $path );
228 return self::get_block_name( $block_type );
229 }
230
231 return 'jetpack/' . $last_part;
232 }
233
234 /**
235 * Get the block name from the its metadata.
236 *
237 * @param array $metadata The block metadata.
238 *
239 * @return string The block name.
240 */
241 public static function get_block_name_from_metadata( $metadata ) {
242 return ! isset( $metadata['name'] ) || empty( $metadata['name'] ) ? '' : $metadata['name'];
243 }
244
245 /**
246 * Get the block feature name (i.e. the name without the `jetpack` prefix).
247 *
248 * @param string|array $arg Path to block.json or its parent folder, or its content as an array.
249 *
250 * @return string The block feature name.
251 */
252 public static function get_block_feature( $arg ) {
253 $metadata = self::get_block_metadata( $arg );
254
255 return self::get_block_feature_from_metadata( $metadata );
256 }
257
258 /**
259 * Get the block feature name (i.e. the name without the `jetpack` prefix) from its metadata.
260 *
261 * @param array $metadata The block metadata.
262 *
263 * @return string The block feature name.
264 */
265 public static function get_block_feature_from_metadata( $metadata ) {
266 return str_replace( 'jetpack/', '', self::get_block_name_from_metadata( $metadata ) );
267 }
268
269 /**
270 * Check if an extension/block is already registered
271 *
272 * @since 1.1.0
273 *
274 * @param string $slug Name of extension/block to check.
275 *
276 * @return bool
277 */
278 public static function is_registered( $slug ) {
279 return \WP_Block_Type_Registry::get_instance()->is_registered( $slug );
280 }
281
282 /**
283 * Remove the 'jetpack/' or jetpack-' prefix from an extension name
284 *
285 * @since 1.1.0
286 *
287 * @param string $extension_name The extension name.
288 *
289 * @return string The unprefixed extension name.
290 */
291 public static function remove_extension_prefix( $extension_name ) {
292 if ( str_starts_with( $extension_name, 'jetpack/' ) || str_starts_with( $extension_name, 'jetpack-' ) ) {
293 return substr( $extension_name, strlen( 'jetpack/' ) );
294 }
295 return $extension_name;
296 }
297
298 /**
299 * Check to see if a minimum version of Gutenberg is available. Because a Gutenberg version is not available in
300 * php if the Gutenberg plugin is not installed, if we know which minimum WP release has the required version we can
301 * optionally fall back to that.
302 *
303 * @since 1.1.0
304 *
305 * @param array $version_requirements {
306 * An array containing the required Gutenberg version and, if known, the WordPress version that was released with this minimum version.
307 *
308 * @type string $gutenberg Gutenberg version.
309 * @type string $wp Optional. WordPress version.
310 * }
311 * @param string $slug The slug of the block or plugin that has the Gutenberg version requirement.
312 *
313 * @return boolean True if the version of Gutenberg required by the block or plugin is available.
314 */
315 public static function is_gutenberg_version_available( $version_requirements, $slug ) {
316 global $wp_version;
317
318 // Bail if we don't at least have the Gutenberg version requirement, the WP version is optional.
319 if ( empty( $version_requirements['gutenberg'] ) ) {
320 return false;
321 }
322
323 // If running a local dev build of Gutenberg plugin GUTENBERG_DEVELOPMENT_MODE is set so assume correct version.
324 if ( defined( 'GUTENBERG_DEVELOPMENT_MODE' ) && GUTENBERG_DEVELOPMENT_MODE ) {
325 return true;
326 }
327
328 $version_available = false;
329
330 // If running a production build of the Gutenberg plugin then GUTENBERG_VERSION is set, otherwise if WP version
331 // with required version of Gutenberg is known check that.
332 if ( defined( 'GUTENBERG_VERSION' ) ) {
333 $version_available = version_compare( GUTENBERG_VERSION, $version_requirements['gutenberg'], '>=' );
334 } elseif ( ! empty( $version_requirements['wp'] ) ) {
335 $version_available = version_compare( $wp_version, $version_requirements['wp'], '>=' );
336 }
337
338 if (
339 ! $version_available
340 && ! self::is_standalone_block() // This is only useful in Jetpack.
341 ) {
342 $slug = Jetpack_Gutenberg::remove_extension_prefix( $slug );
343 Jetpack_Gutenberg::set_extension_unavailable(
344 $slug,
345 'incorrect_gutenberg_version',
346 array(
347 'required_feature' => $slug,
348 'required_version' => $version_requirements,
349 'current_version' => array(
350 'wp' => $wp_version,
351 'gutenberg' => defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : null,
352 ),
353 )
354 );
355 }
356
357 return $version_available;
358 }
359
360 /**
361 * Get CSS classes for a block.
362 *
363 * @since 1.1.0
364 *
365 * @param string $slug Block slug.
366 * @param array $attr Block attributes.
367 * @param array $extra Potential extra classes you may want to provide.
368 *
369 * @return string $classes List of CSS classes for a block.
370 */
371 public static function classes( $slug, $attr, $extra = array() ) {
372 if ( empty( $slug ) ) {
373 return '';
374 }
375
376 // Basic block name class.
377 $classes = array(
378 'wp-block-jetpack-' . $slug,
379 );
380
381 // Add alignment if provided.
382 if (
383 ! empty( $attr['align'] )
384 && in_array( $attr['align'], array( 'left', 'center', 'right', 'wide', 'full' ), true )
385 ) {
386 $classes[] = 'align' . $attr['align'];
387 }
388
389 // Add custom classes if provided in the block editor.
390 if ( ! empty( $attr['className'] ) ) {
391 $classes[] = $attr['className'];
392 }
393
394 // Add any extra classes.
395 if ( is_array( $extra ) && ! empty( $extra ) ) {
396 $classes = array_merge( $classes, array_filter( $extra ) );
397 }
398
399 return implode( ' ', $classes );
400 }
401
402 /**
403 * Does the page return AMP content.
404 *
405 * @since 1.1.0
406 *
407 * @return bool $is_amp_request Are we on an AMP view.
408 */
409 public static function is_amp_request() {
410 $is_amp_request = ( function_exists( 'is_amp_endpoint' ) && is_amp_endpoint() );
411
412 /** This filter is documented in 3rd-party/class.jetpack-amp-support.php */
413 return apply_filters( 'jetpack_is_amp_request', $is_amp_request );
414 }
415
416 /**
417 * Is the current theme an FSE/Site Editor theme.
418 *
419 * @since 1.4.0
420 * @since 1.4.22 Remove support for deprecated `gutenberg_is_fse_theme` function.
421 *
422 * @return bool True if the current theme is an FSE/Site Editor theme.
423 */
424 public static function is_fse_theme() {
425 $is_fse_theme = wp_is_block_theme();
426
427 /**
428 * Returns true if the current theme is an FSE/Site Editor theme.
429 *
430 * @since 1.4.0
431 *
432 * @param boolean $is_fse_theme Is the theme an FSE theme.
433 */
434 return apply_filters( 'jetpack_is_fse_theme', $is_fse_theme );
435 }
436
437 /**
438 * Check whether or the block being registered is a standalone block,
439 * running in a context outside of the Jetpack plugin.
440 *
441 * @since 1.3.0
442 *
443 * @return bool
444 */
445 public static function is_standalone_block() {
446 $is_standalone_block = ! class_exists( Jetpack_Gutenberg::class );
447
448 /**
449 * Returns true if the block is not being registered within a Jetpack plugin context.
450 *
451 * @since 1.3.0
452 *
453 * @param boolean $is_standalone_block Is the block running standalone versus as part of the Jetpack plugin.
454 */
455 return apply_filters( 'jetpack_is_standalone_block', $is_standalone_block );
456 }
457
458 /**
459 * Returns the path to the directory containing the block.json metadata file of a block, given its
460 * source code directory and, optionally, the directory that holds the blocks built files of
461 * the package. If the directory doesn't exist, falls back to the source directory.
462 *
463 * @since 1.6.0
464 *
465 * @param string $block_src_dir The path to the folder containing the block source code.
466 * Typically this is done by passing __DIR__ as the argument.
467 * @param string $package_dist_dir Optional. A full path to the directory containing the blocks
468 * built files of the package. Default empty.
469 *
470 * @return string The path to the directory.
471 */
472 public static function get_path_to_block_metadata( $block_src_dir, $package_dist_dir = '' ) {
473 $dir = basename( $block_src_dir );
474 $dist_path = $package_dist_dir;
475
476 if ( empty( $dist_path ) ) {
477 $plugin_file = Jetpack_Constants::get_constant( 'JETPACK__PLUGIN_FILE' );
478 // Guard against strange situations where JETPACK__PLUGIN_FILE is undefined.
479 if ( empty( $plugin_file ) ) {
480 return $block_src_dir;
481 }
482
483 $dist_path = dirname( $plugin_file ) . '/_inc/blocks';
484 }
485
486 $result = realpath( "$dist_path/$dir" );
487
488 return false === $result ? $block_src_dir : $result;
489 }
490
491 /**
492 * Determine whether a site should use the default set of blocks, or a custom set.
493 * Possible variations are currently beta, experimental, and production.
494 *
495 * @since 3.1.0
496 *
497 * @return string $block_variation production|beta|experimental
498 */
499 public static function get_variation() {
500 // Default to production blocks.
501 $block_variation = 'production';
502
503 /*
504 * Prefer to use this JETPACK_BLOCKS_VARIATION constant
505 * or the jetpack_blocks_variation filter
506 * to set the block variation in your code.
507 */
508 $default = Constants::get_constant( 'JETPACK_BLOCKS_VARIATION' );
509 if ( ! empty( $default ) && in_array( $default, array( 'beta', 'experimental', 'production' ), true ) ) {
510 $block_variation = $default;
511 }
512
513 /**
514 * Alternative to `JETPACK_BETA_BLOCKS`, set to `true` to load Beta Blocks.
515 *
516 * @since jetpack-6.9.0
517 * @deprecated jetpack-11.8.0 Use jetpack_blocks_variation filter instead.
518 *
519 * @param boolean
520 */
521 $is_beta = apply_filters_deprecated(
522 'jetpack_load_beta_blocks',
523 array( false ),
524 'jetpack-11.8.0',
525 'jetpack_blocks_variation'
526 );
527
528 /*
529 * Switch to beta blocks if you use the JETPACK_BETA_BLOCKS constant
530 * or the deprecated jetpack_load_beta_blocks filter.
531 * This only applies when not using the newer JETPACK_BLOCKS_VARIATION constant.
532 */
533 if ( empty( $default )
534 && (
535 $is_beta
536 || Constants::is_true( 'JETPACK_BETA_BLOCKS' )
537 )
538 ) {
539 $block_variation = 'beta';
540 }
541
542 /**
543 * Alternative to `JETPACK_EXPERIMENTAL_BLOCKS`, set to `true` to load Experimental Blocks.
544 *
545 * @since jetpack-6.9.0
546 * @deprecated jetpack-11.8.0 Use jetpack_blocks_variation filter instead.
547 *
548 * @param boolean
549 */
550 $is_experimental = apply_filters_deprecated(
551 'jetpack_load_experimental_blocks',
552 array( false ),
553 'jetpack-11.8.0',
554 'jetpack_blocks_variation'
555 );
556
557 /*
558 * Switch to experimental blocks if you use the JETPACK_EXPERIMENTAL_BLOCKS constant
559 * or the deprecated jetpack_load_experimental_blocks filter.
560 * This only applies when not using the newer JETPACK_BLOCKS_VARIATION constant.
561 */
562 if ( empty( $default )
563 && (
564 $is_experimental
565 || Constants::is_true( 'JETPACK_EXPERIMENTAL_BLOCKS' )
566 )
567 ) {
568 $block_variation = 'experimental';
569 }
570
571 /**
572 * Allow customizing the variation of blocks in use on a site.
573 * Overwrites any previously set values, whether by constant or filter.
574 *
575 * @since jetpack-8.1.0
576 *
577 * @param string $block_variation Can be beta, experimental, and production. Defaults to production.
578 */
579 return apply_filters( 'jetpack_blocks_variation', $block_variation );
580 }
581 }
582