Completed
Push — fix/slideshow-dependencies ( a328c8...25d99e )
by
unknown
12:50 queued 06:11
created

class.jetpack-gutenberg.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
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 Jetpack
7
 */
8
9
/**
10
 * Wrapper function to safely register a gutenberg block type
11
 *
12
 * @param string $slug Slug of the block.
13
 * @param array  $args Arguments that are passed into register_block_type.
14
 *
15
 * @see register_block_type
16
 *
17
 * @since 6.7.0
18
 *
19
 * @return WP_Block_Type|false The registered block type on success, or false on failure.
20
 */
21
function jetpack_register_block( $slug, $args = array() ) {
22
	if ( ! function_exists( 'register_block_type' ) ) {
23
		return false;
24
	}
25
	if ( 0 !== strpos( $slug, 'jetpack/' ) && ! strpos( $slug, '/' ) ) {
26
		_doing_it_wrong( 'jetpack_register_block', 'Prefix the block with jetpack/ ', '7.1.0' );
27
		$slug = 'jetpack/' . $slug;
28
	}
29
	return register_block_type( $slug, $args );
30
}
31
32
/**
33
 * Helper function to register a Jetpack Gutenberg plugin
34
 *
35
 * @deprecated 7.1.0 Use Jetpack_Gutenberg::set_extension_available() instead
36
 *
37
 * @param string $slug Slug of the plugin.
38
 *
39
 * @since 6.9.0
40
 *
41
 * @return void
42
 */
43
function jetpack_register_plugin( $slug ) {
44
	_deprecated_function( __FUNCTION__, '7.1', 'Jetpack_Gutenberg::set_extension_available' );
45
46
	Jetpack_Gutenberg::register_plugin( $slug );
47
}
48
49
/**
50
 * Set the reason why an extension (block or plugin) is unavailable
51
 *
52
 * @deprecated 7.1.0 Use Jetpack_Gutenberg::set_extension_unavailable() instead
53
 *
54
 * @param string $slug Slug of the block.
55
 * @param string $reason A string representation of why the extension is unavailable.
56
 *
57
 * @since 7.0.0
58
 *
59
 * @return void
60
 */
61
function jetpack_set_extension_unavailability_reason( $slug, $reason ) {
62
	_deprecated_function( __FUNCTION__, '7.1', 'Jetpack_Gutenberg::set_extension_unavailable' );
63
64
	Jetpack_Gutenberg::set_extension_unavailability_reason( $slug, $reason );
65
}
66
67
/**
68
 * General Gutenberg editor specific functionality
69
 */
70
class Jetpack_Gutenberg {
71
72
	/**
73
	 * Only these extensions can be registered. Used to control availability of beta blocks.
74
	 *
75
	 * @var array Extensions whitelist
76
	 */
77
	private static $extensions = array();
78
79
	/**
80
	 * Keeps track of the reasons why a given extension is unavailable.
81
	 *
82
	 * @var array Extensions availability information
83
	 */
84
	private static $availability = array();
85
86
	/**
87
	 * Prepend the 'jetpack/' prefix to a block name
88
	 *
89
	 * @param string $block_name The block name.
90
	 *
91
	 * @return string The prefixed block name.
92
	 */
93
	private static function prepend_block_prefix( $block_name ) {
94
		return 'jetpack/' . $block_name;
95
	}
96
97
	/**
98
	 * Remove the 'jetpack/' or jetpack-' prefix from an extension name
99
	 *
100
	 * @param string $extension_name The extension name.
101
	 *
102
	 * @return string The unprefixed extension name.
103
	 */
104
	private static function remove_extension_prefix( $extension_name ) {
105
		if ( wp_startswith( $extension_name, 'jetpack/' ) || wp_startswith( $extension_name, 'jetpack-' ) ) {
106
			return substr( $extension_name, strlen( 'jetpack/' ) );
107
		}
108
		return $extension_name;
109
	}
110
111
	/**
112
	 * Whether two arrays share at least one item
113
	 *
114
	 * @param array $a An array.
115
	 * @param array $b Another array.
116
	 *
117
	 * @return boolean True if $a and $b share at least one item
118
	 */
119
	protected static function share_items( $a, $b ) {
120
		return count( array_intersect( $a, $b ) ) > 0;
121
	}
122
123
	/**
124
	 * Register a block
125
	 *
126
	 * @deprecated 7.1.0 Use jetpack_register_block() instead
127
	 *
128
	 * @param string $slug Slug of the block.
129
	 * @param array  $args Arguments that are passed into register_block_type().
130
	 */
131
	public static function register_block( $slug, $args ) {
132
		_deprecated_function( __METHOD__, '7.1', 'jetpack_register_block' );
133
134
		jetpack_register_block( 'jetpack/' . $slug, $args );
135
	}
136
137
	/**
138
	 * Register a plugin
139
	 *
140
	 * @deprecated 7.1.0 Use Jetpack_Gutenberg::set_extension_available() instead
141
	 *
142
	 * @param string $slug Slug of the plugin.
143
	 */
144
	public static function register_plugin( $slug ) {
145
		_deprecated_function( __METHOD__, '7.1', 'Jetpack_Gutenberg::set_extension_available' );
146
147
		self::set_extension_available( $slug );
148
	}
149
150
	/**
151
	 * Register a block
152
	 *
153
	 * @deprecated 7.0.0 Use jetpack_register_block() instead
154
	 *
155
	 * @param string $slug Slug of the block.
156
	 * @param array  $args Arguments that are passed into the register_block_type.
157
	 * @param array  $availability array containing if a block is available and the reason when it is not.
158
	 */
159
	public static function register( $slug, $args, $availability ) {
160
		_deprecated_function( __METHOD__, '7.0', 'jetpack_register_block' );
161
162
		if ( isset( $availability['available'] ) && ! $availability['available'] ) {
163
			self::set_extension_unavailability_reason( $slug, $availability['unavailable_reason'] );
164
		} else {
165
			self::register_block( $slug, $args );
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack_Gutenberg::register_block() has been deprecated with message: 7.1.0 Use jetpack_register_block() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
166
		}
167
	}
168
169
	/**
170
	 * Set a (non-block) extension as available
171
	 *
172
	 * @param string $slug Slug of the extension.
173
	 */
174
	public static function set_extension_available( $slug ) {
175
		self::$availability[ self::remove_extension_prefix( $slug ) ] = true;
176
	}
177
178
	/**
179
	 * Set the reason why an extension (block or plugin) is unavailable
180
	 *
181
	 * @param string $slug Slug of the extension.
182
	 * @param string $reason A string representation of why the extension is unavailable.
183
	 */
184
	public static function set_extension_unavailable( $slug, $reason ) {
185
		self::$availability[ self::remove_extension_prefix( $slug ) ] = $reason;
186
	}
187
188
	/**
189
	 * Set the reason why an extension (block or plugin) is unavailable
190
	 *
191
	 * @deprecated 7.1.0 Use set_extension_unavailable() instead
192
	 *
193
	 * @param string $slug Slug of the extension.
194
	 * @param string $reason A string representation of why the extension is unavailable.
195
	 */
196
	public static function set_extension_unavailability_reason( $slug, $reason ) {
197
		_deprecated_function( __METHOD__, '7.1', 'Jetpack_Gutenberg::set_extension_unavailable' );
198
199
		self::set_extension_unavailable( $slug, $reason );
200
	}
201
202
	/**
203
	 * Set up a whitelist of allowed block editor extensions
204
	 *
205
	 * @return void
206
	 */
207
	public static function init() {
208
		if ( ! self::is_gutenberg_available() ) {
209
			return;
210
		}
211
212
		if ( ! self::should_load() ) {
213
			return;
214
		}
215
216
		/**
217
		 * Alternative to `JETPACK_BETA_BLOCKS`, set to `true` to load Beta Blocks.
218
		 *
219
		 * @since 6.9.0
220
		 *
221
		 * @param boolean
222
		 */
223
		if ( apply_filters( 'jetpack_load_beta_blocks', false ) ) {
224
			Jetpack_Constants::set_constant( 'JETPACK_BETA_BLOCKS', true );
225
		}
226
227
		/**
228
		 * Filter the whitelist of block editor extensions that are available through Jetpack.
229
		 *
230
		 * @since 7.0.0
231
		 *
232
		 * @param array
233
		 */
234
		self::$extensions = apply_filters( 'jetpack_set_available_extensions', self::get_jetpack_gutenberg_extensions_whitelist() );
235
236
		/**
237
		 * Filter the whitelist of block editor plugins that are available through Jetpack.
238
		 *
239
		 * @deprecated 7.0.0 Use jetpack_set_available_extensions instead
240
		 *
241
		 * @since 6.8.0
242
		 *
243
		 * @param array
244
		 */
245
		self::$extensions = apply_filters( 'jetpack_set_available_blocks', self::$extensions );
246
247
		/**
248
		 * Filter the whitelist of block editor plugins that are available through Jetpack.
249
		 *
250
		 * @deprecated 7.0.0 Use jetpack_set_available_extensions instead
251
		 *
252
		 * @since 6.9.0
253
		 *
254
		 * @param array
255
		 */
256
		self::$extensions = apply_filters( 'jetpack_set_available_plugins', self::$extensions );
257
	}
258
259
	/**
260
	 * Resets the class to its original state
261
	 *
262
	 * Used in unit tests
263
	 *
264
	 * @return void
265
	 */
266
	public static function reset() {
267
		self::$extensions         = array();
268
		self::$availability       = array();
269
	}
270
271
	/**
272
	 * Return the Gutenberg extensions (blocks and plugins) directory
273
	 *
274
	 * @return string The Gutenberg extensions directory
275
	 */
276
	public static function get_blocks_directory() {
277
		/**
278
		 * Filter to select Gutenberg blocks directory
279
		 *
280
		 * @since 6.9.0
281
		 *
282
		 * @param string default: '_inc/blocks/'
283
		 */
284
		return apply_filters( 'jetpack_blocks_directory', '_inc/blocks/' );
285
	}
286
287
	/**
288
	 * Checks for a given .json file in the blocks folder.
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
		return file_exists( JETPACK__PLUGIN_DIR . self::get_blocks_directory() . $preset . '.json' );
296
	}
297
298
	/**
299
	 * Decodes JSON loaded from a preset file in the blocks folder
300
	 *
301
	 * @param string $preset The name of the .json file to load.
302
	 *
303
	 * @return mixed Returns an object if the file is present, or false if a valid .json file is not present.
304
	 */
305
	public static function get_preset( $preset ) {
306
		return json_decode( file_get_contents( JETPACK__PLUGIN_DIR . self::get_blocks_directory() . $preset . '.json' ) );
307
	}
308
309
	/**
310
	 * Returns a whitelist of Jetpack Gutenberg extensions (blocks and plugins), based on index.json
311
	 *
312
	 * @return array A list of blocks: eg [ 'publicize', 'markdown' ]
313
	 */
314
	public static function get_jetpack_gutenberg_extensions_whitelist() {
315
		$preset_extensions_manifest = self::preset_exists( 'index' ) ? self::get_preset( 'index' ) : (object) array();
316
317
		$preset_extensions = isset( $preset_extensions_manifest->production ) ? (array) $preset_extensions_manifest->production : array();
318
319
		if ( Jetpack_Constants::is_true( 'JETPACK_BETA_BLOCKS' ) ) {
320
			$beta_extensions = isset( $preset_extensions_manifest->beta ) ? (array) $preset_extensions_manifest->beta : array();
321
			return array_unique( array_merge( $preset_extensions, $beta_extensions ) );
322
		}
323
324
		return $preset_extensions;
325
	}
326
327
	/**
328
	 * Get availability of each block / plugin.
329
	 *
330
	 * @return array A list of block and plugins and their availablity status
331
	 */
332
	public static function get_availability() {
333
		if ( ! self::is_gutenberg_available() ) {
334
			return array();
335
		}
336
337
		/**
338
		 * Fires before Gutenberg extensions availability is computed.
339
		 *
340
		 * In the function call you supply, use `jetpack_register_block()` to set a block as available.
341
		 * Alternatively, use `Jetpack_Gutenberg::set_extension_available()` (for a non-block plugin), and
342
		 * `Jetpack_Gutenberg::set_extension_unavailable()` (if the block or plugin should not be registered
343
		 * but marked as unavailable).
344
		 *
345
		 * @since 7.0.0
346
		 */
347
		do_action( 'jetpack_register_gutenberg_extensions' );
348
349
		$available_extensions = array();
350
351
		foreach ( self::$extensions as $extension ) {
352
			$is_available = WP_Block_Type_Registry::get_instance()->is_registered( 'jetpack/' . $extension ) ||
353
			( isset( self::$availability[ $extension ] ) && self::$availability[ $extension ] === true );
354
355
			$available_extensions[ $extension ] = array(
356
				'available' => $is_available,
357
			);
358
359
			if ( ! $is_available ) {
360
				$reason = isset( self::$availability[ $extension ] ) ? self::$availability[ $extension ] : 'missing_module';
361
				$available_extensions[ $extension ]['unavailable_reason'] = $reason;
362
			}
363
		}
364
365
		$unwhitelisted_blocks = array();
366
		$all_registered_blocks = WP_Block_Type_Registry::get_instance()->get_all_registered();
367
		foreach ( $all_registered_blocks as $block_name => $block_type ) {
368
			if ( ! wp_startswith( $block_name, 'jetpack/' ) || isset( $block_type->parent ) ) {
369
				continue;
370
			}
371
372
			$unprefixed_block_name = self::remove_extension_prefix( $block_name );
373
374
			if ( in_array( $unprefixed_block_name, self::$extensions ) ) {
375
				continue;
376
			}
377
378
			$unwhitelisted_blocks[ $unprefixed_block_name ] = array(
379
				'available'          => false,
380
				'unavailable_reason' => 'not_whitelisted',
381
			);
382
		}
383
384
		// Finally: Unwhitelisted non-block extensions. These are in $availability.
385
		$unwhitelisted_extensions = array_fill_keys(
386
			array_diff( array_keys( self::$availability ), self::$extensions ),
387
			array(
388
				'available'          => false,
389
				'unavailable_reason' => 'not_whitelisted',
390
			)
391
		);
392
		return array_merge( $available_extensions, $unwhitelisted_blocks, $unwhitelisted_extensions );
393
	}
394
395
	/**
396
	 * Check if Gutenberg editor is available
397
	 *
398
	 * @since 6.7.0
399
	 *
400
	 * @return bool
401
	 */
402
	public static function is_gutenberg_available() {
403
		return function_exists( 'register_block_type' );
404
	}
405
406
	/**
407
	 * Check whether conditions indicate Gutenberg Extensions (blocks and plugins) should be loaded
408
	 *
409
	 * Loading blocks and plugins is enabled by default and may be disabled via filter:
410
	 *   add_filter( 'jetpack_gutenberg', '__return_false' );
411
	 *
412
	 * @since 6.9.0
413
	 *
414
	 * @return bool
415
	 */
416
	public static function should_load() {
417
		if ( ! Jetpack::is_active() && ! Jetpack::is_development_mode() ) {
418
			return false;
419
		}
420
421
		/**
422
		 * Filter to disable Gutenberg blocks
423
		 *
424
		 * @since 6.5.0
425
		 *
426
		 * @param bool true Whether to load Gutenberg blocks
427
		 */
428
		return (bool) apply_filters( 'jetpack_gutenberg', true );
429
	}
430
431
	/**
432
	 * Only enqueue block assets when needed.
433
	 *
434
	 * @param string $type slug of the block.
435
	 * @param array  $script_dependencies An array of view-side Javascript dependencies to be enqueued.
436
	 *
437
	 * @return void
438
	 */
439
	public static function load_assets_as_required( $type, $script_dependencies = array() ) {
440
		if ( is_admin() ) {
441
			// A block's view assets will not be required in wp-admin.
442
			return;
443
		}
444
445
		$type = sanitize_title_with_dashes( $type );
446
		// Enqueue styles.
447
		$style_relative_path = self::get_blocks_directory() . $type . '/view' . ( is_rtl() ? '.rtl' : '' ) . '.css';
448 View Code Duplication
		if ( self::block_has_asset( $style_relative_path ) ) {
449
			$style_version = self::get_asset_version( $style_relative_path );
450
			$view_style    = plugins_url( $style_relative_path, JETPACK__PLUGIN_FILE );
451
			wp_enqueue_style( 'jetpack-block-' . $type, $view_style, array(), $style_version );
452
		}
453
454
		// Enqueue script.
455
		$script_relative_path = self::get_blocks_directory() . $type . '/view.js';
456 View Code Duplication
		if ( self::block_has_asset( $script_relative_path ) ) {
457
			$script_version = self::get_asset_version( $script_relative_path );
458
			$view_script    = plugins_url( $script_relative_path, JETPACK__PLUGIN_FILE );
459
			wp_enqueue_script( 'jetpack-block-' . $type, $view_script, $script_dependencies, $script_version, false );
460
		}
461
462
		wp_localize_script(
463
			'jetpack-block-' . $type,
464
			'Jetpack_Block_Assets_Base_Url',
465
			plugins_url( self::get_blocks_directory(), JETPACK__PLUGIN_FILE )
466
		);
467
	}
468
469
	/**
470
	 * Check if an asset exists for a block.
471
	 *
472
	 * @param string $file Path of the file we are looking for.
473
	 *
474
	 * @return bool $block_has_asset Does the file exist.
475
	 */
476
	public static function block_has_asset( $file ) {
477
		return file_exists( JETPACK__PLUGIN_DIR . $file );
478
	}
479
480
	/**
481
	 * Get the version number to use when loading the file. Allows us to bypass cache when developing.
482
	 *
483
	 * @param string $file Path of the file we are looking for.
484
	 *
485
	 * @return string $script_version Version number.
486
	 */
487
	public static function get_asset_version( $file ) {
488
		return Jetpack::is_development_version() && self::block_has_asset( $file )
489
			? filemtime( JETPACK__PLUGIN_DIR . $file )
490
			: JETPACK__VERSION;
491
	}
492
493
	/**
494
	 * Load Gutenberg editor assets
495
	 *
496
	 * @since 6.7.0
497
	 *
498
	 * @return void
499
	 */
500
	public static function enqueue_block_editor_assets() {
501
		if ( ! self::should_load() ) {
502
			return;
503
		}
504
505
		$rtl        = is_rtl() ? '.rtl' : '';
506
		$beta       = Jetpack_Constants::is_true( 'JETPACK_BETA_BLOCKS' ) ? '-beta' : '';
507
		$blocks_dir = self::get_blocks_directory();
508
509
		$editor_script = plugins_url( "{$blocks_dir}editor{$beta}.js", JETPACK__PLUGIN_FILE );
510
		$editor_style  = plugins_url( "{$blocks_dir}editor{$beta}{$rtl}.css", JETPACK__PLUGIN_FILE );
511
512
		$version = Jetpack::is_development_version() && file_exists( JETPACK__PLUGIN_DIR . $blocks_dir . 'editor.js' )
513
			? filemtime( JETPACK__PLUGIN_DIR . $blocks_dir . 'editor.js' )
514
			: JETPACK__VERSION;
515
516 View Code Duplication
		if ( method_exists( 'Jetpack', 'build_raw_urls' ) ) {
517
			$site_fragment = Jetpack::build_raw_urls( home_url() );
518
		} elseif ( class_exists( 'WPCOM_Masterbar' ) && method_exists( 'WPCOM_Masterbar', 'get_calypso_site_slug' ) ) {
519
			$site_fragment = WPCOM_Masterbar::get_calypso_site_slug( get_current_blog_id() );
520
		} else {
521
			$site_fragment = '';
522
		}
523
524
		wp_enqueue_script(
525
			'jetpack-blocks-editor',
526
			$editor_script,
527
			array(
528
				'lodash',
529
				'wp-api-fetch',
530
				'wp-blob',
531
				'wp-blocks',
532
				'wp-components',
533
				'wp-compose',
534
				'wp-data',
535
				'wp-date',
536
				'wp-edit-post',
537
				'wp-editor',
538
				'wp-element',
539
				'wp-hooks',
540
				'wp-i18n',
541
				'wp-keycodes',
542
				'wp-plugins',
543
				'wp-rich-text',
544
				'wp-token-list',
545
				'wp-url',
546
			),
547
			$version,
548
			false
549
		);
550
551
		wp_localize_script(
552
			'jetpack-blocks-editor',
553
			'Jetpack_Block_Assets_Base_Url',
554
			plugins_url( $blocks_dir . '/', JETPACK__PLUGIN_FILE )
555
		);
556
557
		wp_localize_script(
558
			'jetpack-blocks-editor',
559
			'Jetpack_Editor_Initial_State',
560
			array(
561
				'available_blocks' => self::get_availability(),
562
				'jetpack'          => array( 'is_active' => Jetpack::is_active() ),
563
				'siteFragment'     => $site_fragment,
564
			)
565
		);
566
567
		Jetpack::setup_wp_i18n_locale_data();
568
569
		wp_enqueue_style( 'jetpack-blocks-editor', $editor_style, array(), $version );
570
	}
571
572
	/**
573
	 * Some blocks do not depend on a specific module,
574
	 * and can consequently be loaded outside of the usual modules.
575
	 * We will look for such modules in the extensions/ directory.
576
	 *
577
	 * @since 7.1.0
578
	 */
579
	public static function load_independent_blocks() {
580
		if ( self::should_load() && self::is_gutenberg_available() ) {
581
			/**
582
			 * Look for files that match our list of available Jetpack Gutenberg extensions (blocks and plugins).
583
			 * If available, load them.
584
			 */
585
			foreach ( self::$extensions as $extension ) {
586
				$extension_file_glob = glob( JETPACK__PLUGIN_DIR . 'extensions/*/' . $extension . '/' . $extension . '.php' );
587
				if ( ! empty( $extension_file_glob ) ) {
588
					include_once $extension_file_glob[0];
589
				}
590
			}
591
		}
592
	}
593
}
594