Completed
Push — try/block-editor-iframe ( 300600...358964 )
by Kirk
144:48 queued 136:59
created

class.jetpack-gutenberg.php (2 issues)

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 Jetpack Gutenberg block
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 7.1.0
18
 *
19
 * @return WP_Block_Type|false The registered block type on success, or false on failure.
20
 */
21
function jetpack_register_block_type( $slug, $args = array() ) {
22
	if ( ! function_exists( 'register_block_type' ) ) {
23
		return false;
24
	}
25
	return register_block_type( $slug, $args );
26
}
27
28
/**
29
 * Helper function to register a Jetpack Gutenberg block
30
 *
31
 * @deprecated 7.1.0 Use jetpack_register_block_type() instead
32
 *
33
 * @param string $slug Slug of the block.
34
 * @param array  $args Arguments that are passed into register_block_type.
35
 *
36
 * @see register_block_type
37
 *
38
 * @since 6.7.0
39
 *
40
 * @return void
41
 */
42
function jetpack_register_block( $slug, $args = array() ) {
43
	_deprecated_function( __FUNCTION__, '7.1', 'jetpack_register_block_type' );
44
45
	Jetpack_Gutenberg::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_type() 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...
46
}
47
48
/**
49
 * Helper function to register a Jetpack Gutenberg plugin
50
 *
51
 * @deprecated 7.1.0 Use Jetpack_Gutenberg::set_extension_available() instead
52
 *
53
 * @param string $slug Slug of the plugin.
54
 *
55
 * @since 6.9.0
56
 *
57
 * @return void
58
 */
59
function jetpack_register_plugin( $slug ) {
60
	_deprecated_function( __FUNCTION__, '7.1', 'Jetpack_Gutenberg::set_extension_available' );
61
62
	Jetpack_Gutenberg::register_plugin( $slug );
63
}
64
65
/**
66
 * Set the reason why an extension (block or plugin) is unavailable
67
 *
68
 * @deprecated 7.1.0 Use Jetpack_Gutenberg::set_extension_unavailable() instead
69
 *
70
 * @param string $slug Slug of the block.
71
 * @param string $reason A string representation of why the extension is unavailable.
72
 *
73
 * @since 7.0.0
74
 *
75
 * @return void
76
 */
77
function jetpack_set_extension_unavailability_reason( $slug, $reason ) {
78
	_deprecated_function( __FUNCTION__, '7.1', 'Jetpack_Gutenberg::set_extension_unavailable' );
79
80
	Jetpack_Gutenberg::set_extension_unavailability_reason( $slug, $reason );
81
}
82
83
/**
84
 * General Gutenberg editor specific functionality
85
 */
86
class Jetpack_Gutenberg {
87
88
	/**
89
	 * Only these extensions can be registered. Used to control availability of beta blocks.
90
	 *
91
	 * @var array Extensions whitelist
92
	 */
93
	private static $extensions = array();
94
95
	/**
96
	 * Keeps track of the reasons why a given extension is unavailable.
97
	 *
98
	 * @var array Extensions availability information
99
	 */
100
	private static $availability = array();
101
102
	/**
103
	 * Prepend the 'jetpack/' prefix to a block name
104
	 *
105
	 * @param string $block_name The block name.
106
	 *
107
	 * @return string The prefixed block name.
108
	 */
109
	private static function prepend_block_prefix( $block_name ) {
110
		return 'jetpack/' . $block_name;
111
	}
112
113
	/**
114
	 * Remove the 'jetpack/' or jetpack-' prefix from an extension name
115
	 *
116
	 * @param string $extension_name The extension name.
117
	 *
118
	 * @return string The unprefixed extension name.
119
	 */
120
	private static function remove_extension_prefix( $extension_name ) {
121
		if ( wp_startswith( $extension_name, 'jetpack/' ) || wp_startswith( $extension_name, 'jetpack-' ) ) {
122
			return substr( $extension_name, strlen( 'jetpack/' ) );
123
		}
124
		return $extension_name;
125
	}
126
127
	/**
128
	 * Whether two arrays share at least one item
129
	 *
130
	 * @param array $a An array.
131
	 * @param array $b Another array.
132
	 *
133
	 * @return boolean True if $a and $b share at least one item
134
	 */
135
	protected static function share_items( $a, $b ) {
136
		return count( array_intersect( $a, $b ) ) > 0;
137
	}
138
139
	/**
140
	 * Register a block
141
	 *
142
	 * @deprecated 7.1.0 Use jetpack_register_block_type() instead
143
	 *
144
	 * @param string $slug Slug of the block.
145
	 * @param array  $args Arguments that are passed into register_block_type().
146
	 */
147
	public static function register_block( $slug, $args ) {
148
		_deprecated_function( __METHOD__, '7.1', 'jetpack_register_block_type' );
149
150
		jetpack_register_block_type( 'jetpack/' . $slug, $args );
151
	}
152
153
	/**
154
	 * Register a plugin
155
	 *
156
	 * @deprecated 7.1.0 Use Jetpack_Gutenberg::set_extension_available() instead
157
	 *
158
	 * @param string $slug Slug of the plugin.
159
	 */
160
	public static function register_plugin( $slug ) {
161
		_deprecated_function( __METHOD__, '7.1', 'Jetpack_Gutenberg::set_extension_available' );
162
163
		self::set_extension_available( $slug );
164
	}
165
166
	/**
167
	 * Register a block
168
	 *
169
	 * @deprecated 7.0.0 Use jetpack_register_block_type() instead
170
	 *
171
	 * @param string $slug Slug of the block.
172
	 * @param array  $args Arguments that are passed into the register_block_type.
173
	 * @param array  $availability array containing if a block is available and the reason when it is not.
174
	 */
175
	public static function register( $slug, $args, $availability ) {
176
		_deprecated_function( __METHOD__, '7.0', 'jetpack_register_block_type' );
177
178
		if ( isset( $availability['available'] ) && ! $availability['available'] ) {
179
			self::set_extension_unavailability_reason( $slug, $availability['unavailable_reason'] );
180
		} else {
181
			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_type() 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...
182
		}
183
	}
184
185
	/**
186
	 * Set a (non-block) extension as available
187
	 *
188
	 * @param string $slug Slug of the extension.
189
	 */
190
	public static function set_extension_available( $slug ) {
191
		self::$availability[ self::remove_extension_prefix( $slug ) ] = true;
192
	}
193
194
	/**
195
	 * Set the reason why an extension (block or plugin) is unavailable
196
	 *
197
	 * @param string $slug Slug of the extension.
198
	 * @param string $reason A string representation of why the extension is unavailable.
199
	 */
200
	public static function set_extension_unavailable( $slug, $reason ) {
201
		self::$availability[ self::remove_extension_prefix( $slug ) ] = $reason;
202
	}
203
204
	/**
205
	 * Set the reason why an extension (block or plugin) is unavailable
206
	 *
207
	 * @deprecated 7.1.0 Use set_extension_unavailable() instead
208
	 *
209
	 * @param string $slug Slug of the extension.
210
	 * @param string $reason A string representation of why the extension is unavailable.
211
	 */
212
	public static function set_extension_unavailability_reason( $slug, $reason ) {
213
		_deprecated_function( __METHOD__, '7.1', 'Jetpack_Gutenberg::set_extension_unavailable' );
214
215
		self::set_extension_unavailable( $slug, $reason );
216
	}
217
218
	/**
219
	 * Set up a whitelist of allowed block editor extensions
220
	 *
221
	 * @return void
222
	 */
223
	public static function init() {
224
		if ( ! self::is_gutenberg_available() ) {
225
			return;
226
		}
227
228
		if ( ! self::should_load() ) {
229
			return;
230
		}
231
232
		/**
233
		 * Alternative to `JETPACK_BETA_BLOCKS`, set to `true` to load Beta Blocks.
234
		 *
235
		 * @since 6.9.0
236
		 *
237
		 * @param boolean
238
		 */
239
		if ( apply_filters( 'jetpack_load_beta_blocks', false ) ) {
240
			Jetpack_Constants::set_constant( 'JETPACK_BETA_BLOCKS', true );
241
		}
242
243
		/**
244
		 * Filter the whitelist of block editor extensions that are available through Jetpack.
245
		 *
246
		 * @since 7.0.0
247
		 *
248
		 * @param array
249
		 */
250
		self::$extensions = apply_filters( 'jetpack_set_available_extensions', self::get_jetpack_gutenberg_extensions_whitelist() );
251
252
		/**
253
		 * Filter the whitelist of block editor plugins that are available through Jetpack.
254
		 *
255
		 * @deprecated 7.0.0 Use jetpack_set_available_extensions instead
256
		 *
257
		 * @since 6.8.0
258
		 *
259
		 * @param array
260
		 */
261
		self::$extensions = apply_filters( 'jetpack_set_available_blocks', self::$extensions );
262
263
		/**
264
		 * Filter the whitelist of block editor plugins that are available through Jetpack.
265
		 *
266
		 * @deprecated 7.0.0 Use jetpack_set_available_extensions instead
267
		 *
268
		 * @since 6.9.0
269
		 *
270
		 * @param array
271
		 */
272
		self::$extensions = apply_filters( 'jetpack_set_available_plugins', self::$extensions );
273
	}
274
275
	/**
276
	 * Resets the class to its original state
277
	 *
278
	 * Used in unit tests
279
	 *
280
	 * @return void
281
	 */
282
	public static function reset() {
283
		self::$extensions         = array();
284
		self::$availability       = array();
285
	}
286
287
	/**
288
	 * Return the Gutenberg extensions (blocks and plugins) directory
289
	 *
290
	 * @return string The Gutenberg extensions directory
291
	 */
292
	public static function get_blocks_directory() {
293
		/**
294
		 * Filter to select Gutenberg blocks directory
295
		 *
296
		 * @since 6.9.0
297
		 *
298
		 * @param string default: '_inc/blocks/'
299
		 */
300
		return apply_filters( 'jetpack_blocks_directory', '_inc/blocks/' );
301
	}
302
303
	/**
304
	 * Checks for a given .json file in the blocks folder.
305
	 *
306
	 * @param string $preset The name of the .json file to look for.
307
	 *
308
	 * @return bool True if the file is found.
309
	 */
310
	public static function preset_exists( $preset ) {
311
		return file_exists( JETPACK__PLUGIN_DIR . self::get_blocks_directory() . $preset . '.json' );
312
	}
313
314
	/**
315
	 * Decodes JSON loaded from a preset file in the blocks folder
316
	 *
317
	 * @param string $preset The name of the .json file to load.
318
	 *
319
	 * @return mixed Returns an object if the file is present, or false if a valid .json file is not present.
320
	 */
321
	public static function get_preset( $preset ) {
322
		return json_decode( file_get_contents( JETPACK__PLUGIN_DIR . self::get_blocks_directory() . $preset . '.json' ) );
323
	}
324
325
	/**
326
	 * Returns a whitelist of Jetpack Gutenberg extensions (blocks and plugins), based on index.json
327
	 *
328
	 * @return array A list of blocks: eg [ 'publicize', 'markdown' ]
329
	 */
330
	public static function get_jetpack_gutenberg_extensions_whitelist() {
331
		$preset_extensions_manifest = self::preset_exists( 'index' ) ? self::get_preset( 'index' ) : (object) array();
332
333
		$preset_extensions = isset( $preset_extensions_manifest->production ) ? (array) $preset_extensions_manifest->production : array();
334
335
		if ( Jetpack_Constants::is_true( 'JETPACK_BETA_BLOCKS' ) ) {
336
			$beta_extensions = isset( $preset_extensions_manifest->beta ) ? (array) $preset_extensions_manifest->beta : array();
337
			return array_unique( array_merge( $preset_extensions, $beta_extensions ) );
338
		}
339
340
		return $preset_extensions;
341
	}
342
343
	/**
344
	 * Get availability of each block / plugin.
345
	 *
346
	 * @return array A list of block and plugins and their availablity status
347
	 */
348
	public static function get_availability() {
349
		if ( ! self::is_gutenberg_available() ) {
350
			return array();
351
		}
352
353
		/**
354
		 * Fires before Gutenberg extensions availability is computed.
355
		 *
356
		 * In the function call you supply, use `jetpack_register_block_type()` to set a block as available.
357
		 * Alternatively, use `Jetpack_Gutenberg::set_extension_available()` (for a non-block plugin), and
358
		 * `Jetpack_Gutenberg::set_extension_unavailable()` (if the block or plugin should not be registered
359
		 * but marked as unavailable).
360
		 *
361
		 * @since 7.0.0
362
		 */
363
		do_action( 'jetpack_register_gutenberg_extensions' );
364
365
		$available_extensions = array();
366
367
		foreach ( self::$extensions as $extension ) {
368
			$is_available = WP_Block_Type_Registry::get_instance()->is_registered( 'jetpack/' . $extension ) ||
369
			( isset( self::$availability[ $extension ] ) && self::$availability[ $extension ] === true );
370
371
			$available_extensions[ $extension ] = array(
372
				'available' => $is_available,
373
			);
374
375
			if ( ! $is_available ) {
376
				$reason = isset( self::$availability[ $extension ] ) ? self::$availability[ $extension ] : 'missing_module';
377
				$available_extensions[ $extension ]['unavailable_reason'] = $reason;
378
			}
379
		}
380
381
		$unwhitelisted_blocks = array();
382
		$all_registered_blocks = WP_Block_Type_Registry::get_instance()->get_all_registered();
383
		foreach ( $all_registered_blocks as $block_name => $block_type ) {
384
			if ( ! wp_startswith( $block_name, 'jetpack/' ) || isset( $block_type->parent ) ) {
385
				continue;
386
			}
387
388
			$unprefixed_block_name = self::remove_extension_prefix( $block_name );
389
390
			if ( in_array( $unprefixed_block_name, self::$extensions ) ) {
391
				continue;
392
			}
393
394
			$unwhitelisted_blocks[ $unprefixed_block_name ] = array(
395
				'available'          => false,
396
				'unavailable_reason' => 'not_whitelisted',
397
			);
398
		}
399
400
		// Finally: Unwhitelisted non-block extensions. These are in $availability.
401
		$unwhitelisted_extensions = array_fill_keys(
402
			array_diff( array_keys( self::$availability ), self::$extensions ),
403
			array(
404
				'available'          => false,
405
				'unavailable_reason' => 'not_whitelisted',
406
			)
407
		);
408
		return array_merge( $available_extensions, $unwhitelisted_blocks, $unwhitelisted_extensions );
409
	}
410
411
	/**
412
	 * Check if Gutenberg editor is available
413
	 *
414
	 * @since 6.7.0
415
	 *
416
	 * @return bool
417
	 */
418
	public static function is_gutenberg_available() {
419
		return function_exists( 'register_block_type' );
420
	}
421
422
	/**
423
	 * Check whether conditions indicate Gutenberg Extensions (blocks and plugins) should be loaded
424
	 *
425
	 * Loading blocks and plugins is enabled by default and may be disabled via filter:
426
	 *   add_filter( 'jetpack_gutenberg', '__return_false' );
427
	 *
428
	 * @since 6.9.0
429
	 *
430
	 * @return bool
431
	 */
432
	public static function should_load() {
433
		if ( ! Jetpack::is_active() && ! Jetpack::is_development_mode() ) {
434
			return false;
435
		}
436
437
		/**
438
		 * Filter to disable Gutenberg blocks
439
		 *
440
		 * @since 6.5.0
441
		 *
442
		 * @param bool true Whether to load Gutenberg blocks
443
		 */
444
		return (bool) apply_filters( 'jetpack_gutenberg', true );
445
	}
446
447
	/**
448
	 * Only enqueue block assets when needed.
449
	 *
450
	 * @param string $type slug of the block.
451
	 * @param array  $script_dependencies An array of view-side Javascript dependencies to be enqueued.
452
	 *
453
	 * @return void
454
	 */
455
	public static function load_assets_as_required( $type, $script_dependencies = array() ) {
456
		if ( is_admin() ) {
457
			// A block's view assets will not be required in wp-admin.
458
			return;
459
		}
460
461
		$type = sanitize_title_with_dashes( $type );
462
		// Enqueue styles.
463
		$style_relative_path = self::get_blocks_directory() . $type . '/view' . ( is_rtl() ? '.rtl' : '' ) . '.css';
464 View Code Duplication
		if ( self::block_has_asset( $style_relative_path ) ) {
465
			$style_version = self::get_asset_version( $style_relative_path );
466
			$view_style    = plugins_url( $style_relative_path, JETPACK__PLUGIN_FILE );
467
			wp_enqueue_style( 'jetpack-block-' . $type, $view_style, array(), $style_version );
468
		}
469
470
		// Enqueue script.
471
		$script_relative_path = self::get_blocks_directory() . $type . '/view.js';
472 View Code Duplication
		if ( self::block_has_asset( $script_relative_path ) ) {
473
			$script_version = self::get_asset_version( $script_relative_path );
474
			$view_script    = plugins_url( $script_relative_path, JETPACK__PLUGIN_FILE );
475
			wp_enqueue_script( 'jetpack-block-' . $type, $view_script, $script_dependencies, $script_version, false );
476
		}
477
478
		wp_localize_script(
479
			'jetpack-block-' . $type,
480
			'Jetpack_Block_Assets_Base_Url',
481
			plugins_url( self::get_blocks_directory(), JETPACK__PLUGIN_FILE )
482
		);
483
	}
484
485
	/**
486
	 * Check if an asset exists for a block.
487
	 *
488
	 * @param string $file Path of the file we are looking for.
489
	 *
490
	 * @return bool $block_has_asset Does the file exist.
491
	 */
492
	public static function block_has_asset( $file ) {
493
		return file_exists( JETPACK__PLUGIN_DIR . $file );
494
	}
495
496
	/**
497
	 * Get the version number to use when loading the file. Allows us to bypass cache when developing.
498
	 *
499
	 * @param string $file Path of the file we are looking for.
500
	 *
501
	 * @return string $script_version Version number.
502
	 */
503
	public static function get_asset_version( $file ) {
504
		return Jetpack::is_development_version() && self::block_has_asset( $file )
505
			? filemtime( JETPACK__PLUGIN_DIR . $file )
506
			: JETPACK__VERSION;
507
	}
508
509
	/**
510
	 * Load Gutenberg editor assets
511
	 *
512
	 * @since 6.7.0
513
	 *
514
	 * @return void
515
	 */
516
	public static function enqueue_block_editor_assets() {
517
		if ( ! self::should_load() ) {
518
			return;
519
		}
520
521
		$rtl        = is_rtl() ? '.rtl' : '';
522
		$beta       = Jetpack_Constants::is_true( 'JETPACK_BETA_BLOCKS' ) ? '-beta' : '';
523
		$blocks_dir = self::get_blocks_directory();
524
525
		$editor_script = plugins_url( "{$blocks_dir}editor{$beta}.js", JETPACK__PLUGIN_FILE );
526
		$editor_style  = plugins_url( "{$blocks_dir}editor{$beta}{$rtl}.css", JETPACK__PLUGIN_FILE );
527
528
		$version = Jetpack::is_development_version() && file_exists( JETPACK__PLUGIN_DIR . $blocks_dir . 'editor.js' )
529
			? filemtime( JETPACK__PLUGIN_DIR . $blocks_dir . 'editor.js' )
530
			: JETPACK__VERSION;
531
532
		wp_enqueue_script(
533
			'jetpack-blocks-editor',
534
			$editor_script,
535
			array(
536
				'lodash',
537
				'wp-api-fetch',
538
				'wp-blob',
539
				'wp-blocks',
540
				'wp-components',
541
				'wp-compose',
542
				'wp-data',
543
				'wp-date',
544
				'wp-edit-post',
545
				'wp-editor',
546
				'wp-element',
547
				'wp-hooks',
548
				'wp-i18n',
549
				'wp-keycodes',
550
				'wp-plugins',
551
				'wp-rich-text',
552
				'wp-token-list',
553
				'wp-url',
554
			),
555
			$version,
556
			false
557
		);
558
559
		wp_localize_script(
560
			'jetpack-blocks-editor',
561
			'Jetpack_Block_Assets_Base_Url',
562
			plugins_url( $blocks_dir . '/', JETPACK__PLUGIN_FILE )
563
		);
564
565
		wp_localize_script(
566
			'jetpack-blocks-editor',
567
			'Jetpack_Editor_Initial_State',
568
			array(
569
				'available_blocks' => self::get_availability(),
570
				'jetpack'          => array( 'is_active' => Jetpack::is_active() ),
571
			)
572
		);
573
574
		Jetpack::setup_wp_i18n_locale_data();
575
576
		wp_enqueue_style( 'jetpack-blocks-editor', $editor_style, array(), $version );
577
	}
578
579
	/**
580
	 * Some blocks do not depend on a specific module,
581
	 * and can consequently be loaded outside of the usual modules.
582
	 * We will look for such modules in the extensions/ directory.
583
	 *
584
	 * @since 7.1.0
585
	 */
586
	public static function load_independent_blocks() {
587
		if ( self::should_load() && self::is_gutenberg_available() ) {
588
			/**
589
			 * Look for files that match our list of available Jetpack Gutenberg extensions (blocks and plugins).
590
			 * If available, load them.
591
			 */
592
			foreach ( self::$extensions as $extension ) {
593
				$extension_file_glob = glob( JETPACK__PLUGIN_DIR . 'extensions/*/' . $extension . '/' . $extension . '.php' );
594
				if ( ! empty( $extension_file_glob ) ) {
595
					include_once $extension_file_glob[0];
596
				}
597
			}
598
		}
599
	}
600
}
601