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