Completed
Push — try/avoid-register-block-for-s... ( 87901a )
by
unknown
57:02 queued 49:01
created

class.jetpack-gutenberg.php ➔ jetpack_registration_checks()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 3
nop 2
dl 0
loc 13
rs 9.8333
c 0
b 0
f 0
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 Jetpack
7
 */
8
9
use Automattic\Jetpack\Constants;
10
use Automattic\Jetpack\Status;
11
12
13
function jetpack_registration_checks( $slug, $args = array() ) {
14
	if ( isset( $args['version_requirements'] )
15
		&& ! Jetpack_Gutenberg::is_gutenberg_version_available( $args['version_requirements'], $slug ) ) {
16
		return false;
17
	}
18
19
	// Checking whether block is registered to ensure it isn't registered twice.
20
	if ( Jetpack_Gutenberg::is_registered( $slug ) ) {
21
		return false;
22
	}
23
24
	return true;
25
}
26
27
/**
28
 * Wrapper function to safely register a gutenberg block type.
29
 *
30
 * @param string $slug Slug of the block.
31
 * @param array  $args Arguments that are passed into register_block_type.
32
 *
33
 * @see register_block_type
34
 *
35
 * @since 6.7.0
36
 *
37
 * @return WP_Block_Type|false The registered block type on success, or false on failure.
38
 */
39
function jetpack_register_block( $slug, $args = array() ) {
40
41
	// Attempt to register as Jetpack extension.
42
	if ( ! jetpack_register_extension( $slug, $args ) ) {
43
		return;
44
	}
45
46
	// Register Block with Gutenberg.
47
	return register_block_type( $slug, $args );
48
}
49
50
/**
51
 * Wrapper function to safely register a gutenberg extension (block or otherwise)
52
 *
53
 * @param string $slug Slug of the extension.
54
 * @param array  $args Arguments that are passed into register_block_type.
55
 *
56
 * @since X.X.X
57
 *
58
 * @return Boolean Whether or not the registration was successful.
59
 */
60
function jetpack_register_extension( $slug, $args = array() ) {
61
62
	if ( 0 !== strpos( $slug, 'jetpack/' ) && ! strpos( $slug, '/' ) ) {
63
		_doing_it_wrong( 'jetpack_register_block', 'Prefix the block with jetpack/ ', '7.1.0' );
64
		$slug = 'jetpack/' . $slug;
65
	}
66
67
	if ( ! jetpack_registration_checks( $slug, $args ) ) {
68
		return;
69
	}
70
71
	$feature_name = Jetpack_Gutenberg::remove_extension_prefix( $slug );
72
73
	// If the block is dynamic, and a Jetpack block, wrap the render_callback to check availability.
74
	if (
75
		isset( $args['plan_check'] )
76
		&& true === $args['plan_check']
77
	) {
78
		// Optionally check for render_callback - only extensions which are blocks should provide this.
79
		if ( ! empty( $args['render_callback'] ) ) {
80
			$args['render_callback'] = Jetpack_Gutenberg::get_render_callback_with_availability_check( $feature_name, $args['render_callback'] );
81
		}
82
83
		$method_name = 'set_availability_for_plan';
84
	} else {
85
		$method_name = 'set_extension_available';
86
	}
87
88
	add_action(
89
		'jetpack_register_gutenberg_extensions',
90
		function() use ( $feature_name, $method_name ) {
91
			call_user_func( array( 'Jetpack_Gutenberg', $method_name ), $feature_name );
92
		}
93
	);
94
}
95
96
/**
97
 * Helper function to register a Jetpack Gutenberg plugin
98
 *
99
 * @deprecated 7.1.0 Use Jetpack_Gutenberg::set_extension_available() instead
100
 *
101
 * @param string $slug Slug of the plugin.
102
 *
103
 * @since 6.9.0
104
 *
105
 * @return void
106
 */
107
function jetpack_register_plugin( $slug ) {
108
	_deprecated_function( __FUNCTION__, '7.1', 'Jetpack_Gutenberg::set_extension_available' );
109
110
	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 Jetpack_Gutenberg::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...
111
}
112
113
/**
114
 * Set the reason why an extension (block or plugin) is unavailable
115
 *
116
 * @deprecated 7.1.0 Use Jetpack_Gutenberg::set_extension_unavailable() instead
117
 *
118
 * @param string $slug Slug of the block.
119
 * @param string $reason A string representation of why the extension is unavailable.
120
 *
121
 * @since 7.0.0
122
 *
123
 * @return void
124
 */
125
function jetpack_set_extension_unavailability_reason( $slug, $reason ) {
126
	_deprecated_function( __FUNCTION__, '7.1', 'Jetpack_Gutenberg::set_extension_unavailable' );
127
128
	Jetpack_Gutenberg::set_extension_unavailability_reason( $slug, $reason );
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack_Gutenberg::set_e...unavailability_reason() has been deprecated with message: 7.1.0 Use set_extension_unavailable() 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...
129
}
130
131
/**
132
 * General Gutenberg editor specific functionality
133
 */
134
class Jetpack_Gutenberg {
135
136
	/**
137
	 * Only these extensions can be registered. Used to control availability of beta blocks.
138
	 *
139
	 * @var array Extensions allowed list.
140
	 */
141
	private static $extensions = array();
142
143
	/**
144
	 * Keeps track of the reasons why a given extension is unavailable.
145
	 *
146
	 * @var array Extensions availability information
147
	 */
148
	private static $availability = array();
149
150
	/**
151
	 * Check to see if a minimum version of Gutenberg is available. Because a Gutenberg version is not available in
152
	 * php if the Gutenberg plugin is not installed, if we know which minimum WP release has the required version we can
153
	 * optionally fall back to that.
154
	 *
155
	 * @param array  $version_requirements An array containing the required Gutenberg version and, if known, the WordPress version that was released with this minimum version.
156
	 * @param string $slug The slug of the block or plugin that has the gutenberg version requirement.
157
	 *
158
	 * @since 8.3.0
159
	 *
160
	 * @return boolean True if the version of gutenberg required by the block or plugin is available.
161
	 */
162
	public static function is_gutenberg_version_available( $version_requirements, $slug ) {
163
		global $wp_version;
164
165
		// Bail if we don't at least have the gutenberg version requirement, the WP version is optional.
166
		if ( empty( $version_requirements['gutenberg'] ) ) {
167
			return false;
168
		}
169
170
		// If running a local dev build of gutenberg plugin GUTENBERG_DEVELOPMENT_MODE is set so assume correct version.
171
		if ( defined( 'GUTENBERG_DEVELOPMENT_MODE' ) && GUTENBERG_DEVELOPMENT_MODE ) {
172
			return true;
173
		}
174
175
		$version_available = false;
176
177
		// If running a production build of the gutenberg plugin then GUTENBERG_VERSION is set, otherwise if WP version
178
		// with required version of Gutenberg is known check that.
179
		if ( defined( 'GUTENBERG_VERSION' ) ) {
180
			$version_available = version_compare( GUTENBERG_VERSION, $version_requirements['gutenberg'], '>=' );
181
		} elseif ( ! empty( $version_requirements['wp'] ) ) {
182
			$version_available = version_compare( $wp_version, $version_requirements['wp'], '>=' );
183
		}
184
185
		if ( ! $version_available ) {
186
			self::set_extension_unavailable(
187
				$slug,
188
				'incorrect_gutenberg_version',
189
				array(
190
					'required_feature' => $slug,
191
					'required_version' => $version_requirements,
192
					'current_version'  => array(
193
						'wp'        => $wp_version,
194
						'gutenberg' => defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : null,
195
					),
196
				)
197
			);
198
		}
199
200
		return $version_available;
201
	}
202
203
	/**
204
	 * Prepend the 'jetpack/' prefix to a block name
205
	 *
206
	 * @param string $block_name The block name.
207
	 *
208
	 * @return string The prefixed block name.
209
	 */
210
	private static function prepend_block_prefix( $block_name ) {
211
		return 'jetpack/' . $block_name;
212
	}
213
214
	/**
215
	 * Remove the 'jetpack/' or jetpack-' prefix from an extension name
216
	 *
217
	 * @param string $extension_name The extension name.
218
	 *
219
	 * @return string The unprefixed extension name.
220
	 */
221
	public static function remove_extension_prefix( $extension_name ) {
222
		if ( 0 === strpos( $extension_name, 'jetpack/' ) || 0 === strpos( $extension_name, 'jetpack-' ) ) {
223
			return substr( $extension_name, strlen( 'jetpack/' ) );
224
		}
225
		return $extension_name;
226
	}
227
228
	/**
229
	 * Whether two arrays share at least one item
230
	 *
231
	 * @param array $a An array.
232
	 * @param array $b Another array.
233
	 *
234
	 * @return boolean True if $a and $b share at least one item
235
	 */
236
	protected static function share_items( $a, $b ) {
237
		return count( array_intersect( $a, $b ) ) > 0;
238
	}
239
240
	/**
241
	 * Register a block
242
	 *
243
	 * @deprecated 7.1.0 Use jetpack_register_block() instead
244
	 *
245
	 * @param string $slug Slug of the block.
246
	 * @param array  $args Arguments that are passed into register_block_type().
247
	 */
248
	public static function register_block( $slug, $args ) {
249
		_deprecated_function( __METHOD__, '7.1', 'jetpack_register_block' );
250
251
		jetpack_register_block( 'jetpack/' . $slug, $args );
252
	}
253
254
	/**
255
	 * Register a plugin
256
	 *
257
	 * @deprecated 7.1.0 Use Jetpack_Gutenberg::set_extension_available() instead
258
	 *
259
	 * @param string $slug Slug of the plugin.
260
	 */
261
	public static function register_plugin( $slug ) {
262
		_deprecated_function( __METHOD__, '7.1', 'Jetpack_Gutenberg::set_extension_available' );
263
264
		self::set_extension_available( $slug );
265
	}
266
267
	/**
268
	 * Register a block
269
	 *
270
	 * @deprecated 7.0.0 Use jetpack_register_block() instead
271
	 *
272
	 * @param string $slug Slug of the block.
273
	 * @param array  $args Arguments that are passed into the register_block_type.
274
	 * @param array  $availability array containing if a block is available and the reason when it is not.
275
	 */
276
	public static function register( $slug, $args, $availability ) {
277
		_deprecated_function( __METHOD__, '7.0', 'jetpack_register_block' );
278
279
		if ( isset( $availability['available'] ) && ! $availability['available'] ) {
280
			self::set_extension_unavailability_reason( $slug, $availability['unavailable_reason'] );
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack_Gutenberg::set_e...unavailability_reason() has been deprecated with message: 7.1.0 Use set_extension_unavailable() 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...
281
		} else {
282
			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...
283
		}
284
	}
285
286
	/**
287
	 * Set a (non-block) extension as available
288
	 *
289
	 * @param string $slug Slug of the extension.
290
	 */
291
	public static function set_extension_available( $slug ) {
292
		self::$availability[ self::remove_extension_prefix( $slug ) ] = true;
293
	}
294
295
	/**
296
	 * Set the reason why an extension (block or plugin) is unavailable
297
	 *
298
	 * @param string $slug Slug of the extension.
299
	 * @param string $reason A string representation of why the extension is unavailable.
300
	 * @param array  $details A free-form array containing more information on why the extension is unavailable.
301
	 */
302
	public static function set_extension_unavailable( $slug, $reason, $details = array() ) {
303
		if (
304
			// Extensions that require a plan may be eligible for upgrades.
305
			'missing_plan' === $reason
306
			&& (
307
				/**
308
				 * Filter 'jetpack_block_editor_enable_upgrade_nudge' with `true` to enable or `false`
309
				 * to disable paid feature upgrade nudges in the block editor.
310
				 *
311
				 * When this is changed to default to `true`, you should also update `modules/memberships/class-jetpack-memberships.php`
312
				 * See https://github.com/Automattic/jetpack/pull/13394#pullrequestreview-293063378
313
				 *
314
				 * @since 7.7.0
315
				 *
316
				 * @param boolean
317
				 */
318
				! apply_filters( 'jetpack_block_editor_enable_upgrade_nudge', false )
319
				/** This filter is documented in _inc/lib/admin-pages/class.jetpack-react-page.php */
320
				|| ! apply_filters( 'jetpack_show_promotions', true )
321
			)
322
		) {
323
			// The block editor may apply an upgrade nudge if `missing_plan` is the reason.
324
			// Add a descriptive suffix to disable behavior but provide informative reason.
325
			$reason .= '__nudge_disabled';
326
		}
327
328
		self::$availability[ self::remove_extension_prefix( $slug ) ] = array(
329
			'reason'  => $reason,
330
			'details' => $details,
331
		);
332
	}
333
334
	/**
335
	 * Set the reason why an extension (block or plugin) is unavailable
336
	 *
337
	 * @deprecated 7.1.0 Use set_extension_unavailable() instead
338
	 *
339
	 * @param string $slug Slug of the extension.
340
	 * @param string $reason A string representation of why the extension is unavailable.
341
	 */
342
	public static function set_extension_unavailability_reason( $slug, $reason ) {
343
		_deprecated_function( __METHOD__, '7.1', 'Jetpack_Gutenberg::set_extension_unavailable' );
344
345
		self::set_extension_unavailable( $slug, $reason );
346
	}
347
348
	/**
349
	 * Set up a list of allowed block editor extensions
350
	 *
351
	 * @return void
352
	 */
353
	public static function init() {
354
		if ( ! self::should_load() ) {
355
			return;
356
		}
357
358
		/**
359
		 * Alternative to `JETPACK_BETA_BLOCKS`, set to `true` to load Beta Blocks.
360
		 *
361
		 * @since 6.9.0
362
		 *
363
		 * @param boolean
364
		 */
365
		if ( apply_filters( 'jetpack_load_beta_blocks', false ) ) {
366
			Constants::set_constant( 'JETPACK_BETA_BLOCKS', true );
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
367
		}
368
369
		/**
370
		 * Alternative to `JETPACK_EXPERIMENTAL_BLOCKS`, set to `true` to load Experimental Blocks.
371
		 *
372
		 * @since 8.4.0
373
		 *
374
		 * @param boolean
375
		 */
376
		if ( apply_filters( 'jetpack_load_experimental_blocks', false ) ) {
377
			Constants::set_constant( 'JETPACK_EXPERIMENTAL_BLOCKS', true );
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
378
		}
379
380
		/**
381
		 * Filter the list of block editor extensions that are available through Jetpack.
382
		 *
383
		 * @since 7.0.0
384
		 *
385
		 * @param array
386
		 */
387
		self::$extensions = apply_filters( 'jetpack_set_available_extensions', self::get_available_extensions() );
388
389
		/**
390
		 * Filter the list of block editor plugins that are available through Jetpack.
391
		 *
392
		 * @deprecated 7.0.0 Use jetpack_set_available_extensions instead
393
		 *
394
		 * @since 6.8.0
395
		 *
396
		 * @param array
397
		 */
398
		self::$extensions = apply_filters( 'jetpack_set_available_blocks', self::$extensions );
399
400
		/**
401
		 * Filter the list of block editor plugins that are available through Jetpack.
402
		 *
403
		 * @deprecated 7.0.0 Use jetpack_set_available_extensions instead
404
		 *
405
		 * @since 6.9.0
406
		 *
407
		 * @param array
408
		 */
409
		self::$extensions = apply_filters( 'jetpack_set_available_plugins', self::$extensions );
410
	}
411
412
	/**
413
	 * Resets the class to its original state
414
	 *
415
	 * Used in unit tests
416
	 *
417
	 * @return void
418
	 */
419
	public static function reset() {
420
		self::$extensions   = array();
421
		self::$availability = array();
422
	}
423
424
	/**
425
	 * Return the Gutenberg extensions (blocks and plugins) directory
426
	 *
427
	 * @return string The Gutenberg extensions directory
428
	 */
429
	public static function get_blocks_directory() {
430
		/**
431
		 * Filter to select Gutenberg blocks directory
432
		 *
433
		 * @since 6.9.0
434
		 *
435
		 * @param string default: '_inc/blocks/'
436
		 */
437
		return apply_filters( 'jetpack_blocks_directory', '_inc/blocks/' );
438
	}
439
440
	/**
441
	 * Checks for a given .json file in the blocks folder.
442
	 *
443
	 * @param string $preset The name of the .json file to look for.
444
	 *
445
	 * @return bool True if the file is found.
446
	 */
447
	public static function preset_exists( $preset ) {
448
		return file_exists( JETPACK__PLUGIN_DIR . self::get_blocks_directory() . $preset . '.json' );
449
	}
450
451
	/**
452
	 * Decodes JSON loaded from a preset file in the blocks folder
453
	 *
454
	 * @param string $preset The name of the .json file to load.
455
	 *
456
	 * @return mixed Returns an object if the file is present, or false if a valid .json file is not present.
457
	 */
458
	public static function get_preset( $preset ) {
459
		return json_decode(
460
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
461
			file_get_contents( JETPACK__PLUGIN_DIR . self::get_blocks_directory() . $preset . '.json' )
462
		);
463
	}
464
465
	/**
466
	 * Returns a list of Jetpack Gutenberg extensions (blocks and plugins), based on index.json
467
	 *
468
	 * @return array A list of blocks: eg [ 'publicize', 'markdown' ]
469
	 */
470
	public static function get_jetpack_gutenberg_extensions_allowed_list() {
471
		$preset_extensions_manifest = self::preset_exists( 'index' )
472
			? self::get_preset( 'index' )
473
			: (object) array();
474
		$blocks_variation           = self::blocks_variation();
475
476
		return self::get_extensions_preset_for_variation( $preset_extensions_manifest, $blocks_variation );
477
	}
478
479
	/**
480
	 * Returns a list of Jetpack Gutenberg extensions (blocks and plugins), based on index.json
481
	 *
482
	 * @deprecated 8.7.0 Use get_jetpack_gutenberg_extensions_allowed_list()
483
	 *
484
	 * @return array A list of blocks: eg [ 'publicize', 'markdown' ]
485
	 */
486
	public static function get_jetpack_gutenberg_extensions_whitelist() {
487
		_deprecated_function( __FUNCTION__, 'jetpack-8.7.0', 'Jetpack_Gutenberg::get_jetpack_gutenberg_extensions_allowed_list' );
488
		return self::get_jetpack_gutenberg_extensions_allowed_list();
489
	}
490
491
	/**
492
	 * Returns a diff from a combined list of allowed extensions and extensions determined to be excluded
493
	 *
494
	 * @param  array $allowed_extensions An array of allowed extensions.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $allowed_extensions not be array|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
495
	 *
496
	 * @return array A list of blocks: eg array( 'publicize', 'markdown' )
497
	 */
498
	public static function get_available_extensions( $allowed_extensions = null ) {
499
		$exclusions         = get_option( 'jetpack_excluded_extensions', array() );
500
		$allowed_extensions = is_null( $allowed_extensions ) ? self::get_jetpack_gutenberg_extensions_allowed_list() : $allowed_extensions;
501
502
		return array_diff( $allowed_extensions, $exclusions );
503
	}
504
505
	/**
506
	 * Return true if the extension has been registered and there's nothing in the availablilty array.
507
	 *
508
	 * @param string $extension The name of the extension.
509
	 *
510
	 * @return bool whether the extension has been registered and there's nothing in the availablilty array.
511
	 */
512
	public static function is_registered_and_no_entry_in_availability( $extension ) {
513
		return self::is_registered( 'jetpack/' . $extension ) && ! isset( self::$availability[ $extension ] );
514
	}
515
516
	/**
517
	 * Return true if the extension has a true entry in the availablilty array.
518
	 *
519
	 * @param string $extension The name of the extension.
520
	 *
521
	 * @return bool whether the extension has a true entry in the availablilty array.
522
	 */
523
	public static function is_available( $extension ) {
524
		return isset( self::$availability[ $extension ] ) && true === self::$availability[ $extension ];
525
	}
526
527
	/**
528
	 * Get availability of each block / plugin.
529
	 *
530
	 * @return array A list of block and plugins and their availablity status
531
	 */
532
	public static function get_availability() {
533
		/**
534
		 * Fires before Gutenberg extensions availability is computed.
535
		 *
536
		 * In the function call you supply, use `jetpack_register_block()` to set a block as available.
537
		 * Alternatively, use `Jetpack_Gutenberg::set_extension_available()` (for a non-block plugin), and
538
		 * `Jetpack_Gutenberg::set_extension_unavailable()` (if the block or plugin should not be registered
539
		 * but marked as unavailable).
540
		 *
541
		 * @since 7.0.0
542
		 */
543
		do_action( 'jetpack_register_gutenberg_extensions' );
544
545
		$available_extensions = array();
546
547
		foreach ( self::$extensions as $extension ) {
548
			$is_available                       = self::is_registered_and_no_entry_in_availability( $extension ) || self::is_available( $extension );
549
			$available_extensions[ $extension ] = array(
550
				'available' => $is_available,
551
			);
552
553
			if ( ! $is_available ) {
554
				$reason  = isset( self::$availability[ $extension ] ) ? self::$availability[ $extension ]['reason'] : 'missing_module';
555
				$details = isset( self::$availability[ $extension ] ) ? self::$availability[ $extension ]['details'] : array();
556
				$available_extensions[ $extension ]['unavailable_reason'] = $reason;
557
				$available_extensions[ $extension ]['details']            = $details;
558
			}
559
		}
560
561
		return $available_extensions;
562
	}
563
564
	/**
565
	 * Check if an extension/block is already registered
566
	 *
567
	 * @since 7.2
568
	 *
569
	 * @param string $slug Name of extension/block to check.
570
	 *
571
	 * @return bool
572
	 */
573
	public static function is_registered( $slug ) {
574
		return WP_Block_Type_Registry::get_instance()->is_registered( $slug );
575
	}
576
577
	/**
578
	 * Check if Gutenberg editor is available
579
	 *
580
	 * @since 6.7.0
581
	 *
582
	 * @return bool
583
	 */
584
	public static function is_gutenberg_available() {
585
		return true;
586
	}
587
588
	/**
589
	 * Check whether conditions indicate Gutenberg Extensions (blocks and plugins) should be loaded
590
	 *
591
	 * Loading blocks and plugins is enabled by default and may be disabled via filter:
592
	 *   add_filter( 'jetpack_gutenberg', '__return_false' );
593
	 *
594
	 * @since 6.9.0
595
	 *
596
	 * @return bool
597
	 */
598
	public static function should_load() {
599
		if ( ! Jetpack::is_active() && ! ( new Status() )->is_offline_mode() ) {
600
			return false;
601
		}
602
603
		/**
604
		 * Filter to disable Gutenberg blocks
605
		 *
606
		 * @since 6.5.0
607
		 *
608
		 * @param bool true Whether to load Gutenberg blocks
609
		 */
610
		return (bool) apply_filters( 'jetpack_gutenberg', true );
611
	}
612
613
	/**
614
	 * Only enqueue block assets when needed.
615
	 *
616
	 * @param string $type Slug of the block.
617
	 * @param array  $script_dependencies Script dependencies. Will be merged with automatically
618
	 *                                    detected script dependencies from the webpack build.
619
	 *
620
	 * @return void
621
	 */
622
	public static function load_assets_as_required( $type, $script_dependencies = array() ) {
623
		if ( is_admin() ) {
624
			// A block's view assets will not be required in wp-admin.
625
			return;
626
		}
627
628
		$type = sanitize_title_with_dashes( $type );
629
		self::load_styles_as_required( $type );
630
		self::load_scripts_as_required( $type, $script_dependencies );
631
	}
632
633
	/**
634
	 * Only enqueue block sytles when needed.
635
	 *
636
	 * @param string $type Slug of the block.
637
	 *
638
	 * @since 7.2.0
639
	 *
640
	 * @return void
641
	 */
642
	public static function load_styles_as_required( $type ) {
643
		if ( is_admin() ) {
644
			// A block's view assets will not be required in wp-admin.
645
			return;
646
		}
647
648
		// Enqueue styles.
649
		$style_relative_path = self::get_blocks_directory() . $type . '/view' . ( is_rtl() ? '.rtl' : '' ) . '.css';
650
		if ( self::block_has_asset( $style_relative_path ) ) {
651
			$style_version = self::get_asset_version( $style_relative_path );
652
			$view_style    = plugins_url( $style_relative_path, JETPACK__PLUGIN_FILE );
653
			wp_enqueue_style( 'jetpack-block-' . $type, $view_style, array(), $style_version );
654
		}
655
656
	}
657
658
	/**
659
	 * Only enqueue block scripts when needed.
660
	 *
661
	 * @param string $type Slug of the block.
662
	 * @param array  $script_dependencies Script dependencies. Will be merged with automatically
663
	 *                             detected script dependencies from the webpack build.
664
	 *
665
	 * @since 7.2.0
666
	 *
667
	 * @return void
668
	 */
669
	public static function load_scripts_as_required( $type, $script_dependencies = array() ) {
670
		if ( is_admin() ) {
671
			// A block's view assets will not be required in wp-admin.
672
			return;
673
		}
674
675
		// Enqueue script.
676
		$script_relative_path  = self::get_blocks_directory() . $type . '/view.js';
677
		$script_deps_path      = JETPACK__PLUGIN_DIR . self::get_blocks_directory() . $type . '/view.asset.php';
678
		$script_dependencies[] = 'wp-polyfill';
679
		if ( file_exists( $script_deps_path ) ) {
680
			$asset_manifest      = include $script_deps_path;
681
			$script_dependencies = array_unique( array_merge( $script_dependencies, $asset_manifest['dependencies'] ) );
682
		}
683
684
		if ( ( ! class_exists( 'Jetpack_AMP_Support' ) || ! Jetpack_AMP_Support::is_amp_request() ) && self::block_has_asset( $script_relative_path ) ) {
685
			$script_version = self::get_asset_version( $script_relative_path );
686
			$view_script    = plugins_url( $script_relative_path, JETPACK__PLUGIN_FILE );
687
			wp_enqueue_script( 'jetpack-block-' . $type, $view_script, $script_dependencies, $script_version, false );
688
		}
689
690
		wp_localize_script(
691
			'jetpack-block-' . $type,
692
			'Jetpack_Block_Assets_Base_Url',
693
			plugins_url( self::get_blocks_directory(), JETPACK__PLUGIN_FILE )
694
		);
695
	}
696
697
	/**
698
	 * Check if an asset exists for a block.
699
	 *
700
	 * @param string $file Path of the file we are looking for.
701
	 *
702
	 * @return bool $block_has_asset Does the file exist.
703
	 */
704
	public static function block_has_asset( $file ) {
705
		return file_exists( JETPACK__PLUGIN_DIR . $file );
706
	}
707
708
	/**
709
	 * Get the version number to use when loading the file. Allows us to bypass cache when developing.
710
	 *
711
	 * @param string $file Path of the file we are looking for.
712
	 *
713
	 * @return string $script_version Version number.
714
	 */
715
	public static function get_asset_version( $file ) {
716
		return Jetpack::is_development_version() && self::block_has_asset( $file )
717
			? filemtime( JETPACK__PLUGIN_DIR . $file )
718
			: JETPACK__VERSION;
719
	}
720
721
	/**
722
	 * Load Gutenberg editor assets
723
	 *
724
	 * @since 6.7.0
725
	 *
726
	 * @return void
727
	 */
728
	public static function enqueue_block_editor_assets() {
729
		if ( ! self::should_load() ) {
730
			return;
731
		}
732
733
		// Required for Analytics. See _inc/lib/admin-pages/class.jetpack-admin-page.php.
734
		if ( ! ( new Status() )->is_offline_mode() && Jetpack::is_active() ) {
735
			wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true );
736
		}
737
738
		$rtl              = is_rtl() ? '.rtl' : '';
739
		$blocks_dir       = self::get_blocks_directory();
740
		$blocks_variation = self::blocks_variation();
741
742
		if ( 'production' !== $blocks_variation ) {
743
			$blocks_env = '-' . esc_attr( $blocks_variation );
744
		} else {
745
			$blocks_env = '';
746
		}
747
748
		$editor_script = plugins_url( "{$blocks_dir}editor{$blocks_env}.js", JETPACK__PLUGIN_FILE );
749
		$editor_style  = plugins_url( "{$blocks_dir}editor{$blocks_env}{$rtl}.css", JETPACK__PLUGIN_FILE );
750
751
		$editor_deps_path = JETPACK__PLUGIN_DIR . $blocks_dir . "editor{$blocks_env}.asset.php";
752
		$editor_deps      = array( 'wp-polyfill' );
753
		if ( file_exists( $editor_deps_path ) ) {
754
			$asset_manifest = include $editor_deps_path;
755
			$editor_deps    = $asset_manifest['dependencies'];
756
		}
757
758
		$version = Jetpack::is_development_version() && file_exists( JETPACK__PLUGIN_DIR . $blocks_dir . 'editor.js' )
759
			? filemtime( JETPACK__PLUGIN_DIR . $blocks_dir . 'editor.js' )
760
			: JETPACK__VERSION;
761
762
		if ( method_exists( 'Jetpack', 'build_raw_urls' ) ) {
763
			$site_fragment = Jetpack::build_raw_urls( home_url() );
764
		} elseif ( class_exists( 'WPCOM_Masterbar' ) && method_exists( 'WPCOM_Masterbar', 'get_calypso_site_slug' ) ) {
765
			$site_fragment = WPCOM_Masterbar::get_calypso_site_slug( get_current_blog_id() );
766
		} else {
767
			$site_fragment = '';
768
		}
769
770
		wp_enqueue_script(
771
			'jetpack-blocks-editor',
772
			$editor_script,
773
			$editor_deps,
774
			$version,
775
			false
776
		);
777
778
		wp_localize_script(
779
			'jetpack-blocks-editor',
780
			'Jetpack_Block_Assets_Base_Url',
781
			plugins_url( $blocks_dir . '/', JETPACK__PLUGIN_FILE )
782
		);
783
784
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
785
			$user                      = wp_get_current_user();
786
			$user_data                 = array(
787
				'userid'   => $user->ID,
788
				'username' => $user->user_login,
789
			);
790
			$blog_id                   = get_current_blog_id();
791
			$is_current_user_connected = true;
792
		} else {
793
			$user_data                 = Jetpack_Tracks_Client::get_connected_user_tracks_identity();
794
			$blog_id                   = Jetpack_Options::get_option( 'id', 0 );
795
			$is_current_user_connected = Jetpack::is_user_connected();
796
		}
797
798
		wp_localize_script(
799
			'jetpack-blocks-editor',
800
			'Jetpack_Editor_Initial_State',
801
			array(
802
				'available_blocks' => self::get_availability(),
803
				'jetpack'          => array(
804
					'is_active'                 => Jetpack::is_active(),
805
					'is_current_user_connected' => $is_current_user_connected,
806
				),
807
				'siteFragment'     => $site_fragment,
808
				'tracksUserData'   => $user_data,
809
				'wpcomBlogId'      => $blog_id,
810
				'allowedMimeTypes' => wp_get_mime_types(),
811
			)
812
		);
813
814
		wp_set_script_translations( 'jetpack-blocks-editor', 'jetpack' );
815
816
		wp_enqueue_style( 'jetpack-blocks-editor', $editor_style, array(), $version );
817
	}
818
819
	/**
820
	 * Some blocks do not depend on a specific module,
821
	 * and can consequently be loaded outside of the usual modules.
822
	 * We will look for such modules in the extensions/ directory.
823
	 *
824
	 * @since 7.1.0
825
	 */
826
	public static function load_independent_blocks() {
827
		if ( self::should_load() ) {
828
			/**
829
			 * Look for files that match our list of available Jetpack Gutenberg extensions (blocks and plugins).
830
			 * If available, load them.
831
			 */
832
			foreach ( self::$extensions as $extension ) {
833
				$extension_file_glob = glob( JETPACK__PLUGIN_DIR . 'extensions/*/' . $extension . '/' . $extension . '.php' );
834
				if ( ! empty( $extension_file_glob ) ) {
835
					include_once $extension_file_glob[0];
836
				}
837
			}
838
		}
839
	}
840
841
	/**
842
	 * Get CSS classes for a block.
843
	 *
844
	 * @since 7.7.0
845
	 *
846
	 * @param string $slug  Block slug.
847
	 * @param array  $attr  Block attributes.
848
	 * @param array  $extra Potential extra classes you may want to provide.
849
	 *
850
	 * @return string $classes List of CSS classes for a block.
851
	 */
852
	public static function block_classes( $slug = '', $attr, $extra = array() ) {
853
		if ( empty( $slug ) ) {
854
			return '';
855
		}
856
857
		// Basic block name class.
858
		$classes = array(
859
			'wp-block-jetpack-' . $slug,
860
		);
861
862
		// Add alignment if provided.
863
		if (
864
			! empty( $attr['align'] )
865
			&& in_array( $attr['align'], array( 'left', 'center', 'right', 'wide', 'full' ), true )
866
		) {
867
			array_push( $classes, 'align' . $attr['align'] );
868
		}
869
870
		// Add custom classes if provided in the block editor.
871
		if ( ! empty( $attr['className'] ) ) {
872
			array_push( $classes, $attr['className'] );
873
		}
874
875
		// Add any extra classes.
876
		if ( is_array( $extra ) && ! empty( $extra ) ) {
877
			$classes = array_merge( $classes, array_filter( $extra ) );
878
		}
879
880
		return implode( ' ', $classes );
881
	}
882
883
	/**
884
	 * Determine whether a site should use the default set of blocks, or a custom set.
885
	 * Possible variations are currently beta, experimental, and production.
886
	 *
887
	 * @since 8.1.0
888
	 *
889
	 * @return string $block_varation production|beta|experimental
890
	 */
891
	public static function blocks_variation() {
892
		// Default to production blocks.
893
		$block_varation = 'production';
894
895
		if ( Constants::is_true( 'JETPACK_BETA_BLOCKS' ) ) {
896
			$block_varation = 'beta';
897
		}
898
899
		/*
900
		 * Switch to experimental blocks if you use the JETPACK_EXPERIMENTAL_BLOCKS constant.
901
		 */
902
		if ( Constants::is_true( 'JETPACK_EXPERIMENTAL_BLOCKS' ) ) {
903
			$block_varation = 'experimental';
904
		}
905
906
		/**
907
		 * Allow customizing the variation of blocks in use on a site.
908
		 *
909
		 * @since 8.1.0
910
		 *
911
		 * @param string $block_variation Can be beta, experimental, and production. Defaults to production.
912
		 */
913
		return apply_filters( 'jetpack_blocks_variation', $block_varation );
914
	}
915
916
	/**
917
	 * Get a list of extensions available for the variation you chose.
918
	 *
919
	 * @since 8.1.0
920
	 *
921
	 * @param obj    $preset_extensions_manifest List of extensions available in Jetpack.
922
	 * @param string $blocks_variation           Subset of blocks. production|beta|experimental.
923
	 *
924
	 * @return array $preset_extensions Array of extensions for that variation
925
	 */
926
	public static function get_extensions_preset_for_variation( $preset_extensions_manifest, $blocks_variation ) {
927
		$preset_extensions = isset( $preset_extensions_manifest->{ $blocks_variation } )
928
				? (array) $preset_extensions_manifest->{ $blocks_variation }
929
				: array();
930
931
		/*
932
		 * Experimental and Beta blocks need the production blocks as well.
933
		 */
934 View Code Duplication
		if (
935
			'experimental' === $blocks_variation
936
			|| 'beta' === $blocks_variation
937
		) {
938
			$production_extensions = isset( $preset_extensions_manifest->production )
939
				? (array) $preset_extensions_manifest->production
940
				: array();
941
942
			$preset_extensions = array_unique( array_merge( $preset_extensions, $production_extensions ) );
943
		}
944
945
		/*
946
		 * Beta blocks need the experimental blocks as well.
947
		 *
948
		 * If you've chosen to see Beta blocks,
949
		 * we want to make all blocks available to you:
950
		 * - Production
951
		 * - Experimental
952
		 * - Beta
953
		 */
954 View Code Duplication
		if ( 'beta' === $blocks_variation ) {
955
			$production_extensions = isset( $preset_extensions_manifest->experimental )
956
				? (array) $preset_extensions_manifest->experimental
957
				: array();
958
959
			$preset_extensions = array_unique( array_merge( $preset_extensions, $production_extensions ) );
960
		}
961
962
		return $preset_extensions;
963
	}
964
965
	/**
966
	 * Validate a URL used in a SSR block.
967
	 *
968
	 * @since 8.3.0
969
	 *
970
	 * @param string $url      URL saved as an attribute in block.
971
	 * @param array  $allowed  Array of allowed hosts for that block, or regexes to check against.
972
	 * @param bool   $is_regex Array of regexes matching the URL that could be used in block.
973
	 *
974
	 * @return bool|string
975
	 */
976
	public static function validate_block_embed_url( $url, $allowed = array(), $is_regex = false ) {
977
		if (
978
			empty( $url )
979
			|| ! is_array( $allowed )
980
			|| empty( $allowed )
981
		) {
982
			return false;
983
		}
984
985
		$url_components = wp_parse_url( $url );
986
987
		// Bail early if we cannot find a host.
988
		if ( empty( $url_components['host'] ) ) {
989
			return false;
990
		}
991
992
		// Normalize URL.
993
		$url = sprintf(
994
			'%s://%s%s%s',
995
			isset( $url_components['scheme'] ) ? $url_components['scheme'] : 'https',
996
			$url_components['host'],
997
			isset( $url_components['path'] ) ? $url_components['path'] : '/',
998
			isset( $url_components['query'] ) ? '?' . $url_components['query'] : ''
999
		);
1000
1001
		if ( ! empty( $url_components['fragment'] ) ) {
1002
			$url = $url . '#' . rawurlencode( $url_components['fragment'] );
1003
		}
1004
1005
		/*
1006
		 * If we're using an allowed list of hosts,
1007
		 * check if the URL belongs to one of the domains allowed for that block.
1008
		 */
1009
		if (
1010
			false === $is_regex
1011
			&& in_array( $url_components['host'], $allowed, true )
1012
		) {
1013
			return $url;
1014
		}
1015
1016
		/*
1017
		 * If we are using an array of regexes to check against,
1018
		 * loop through that.
1019
		 */
1020
		if ( true === $is_regex ) {
1021
			foreach ( $allowed as $regex ) {
1022
				if ( 1 === preg_match( $regex, $url ) ) {
1023
					return $url;
1024
				}
1025
			}
1026
		}
1027
1028
		return false;
1029
	}
1030
1031
	/**
1032
	 * Output an UpgradeNudge Component on the frontend of a site.
1033
	 *
1034
	 * @since 8.4.0
1035
	 *
1036
	 * @param string $plan The plan that users need to purchase to make the block work.
1037
	 *
1038
	 * @return string
1039
	 */
1040
	public static function upgrade_nudge( $plan ) {
1041
		if (
1042
			! current_user_can( 'manage_options' )
1043
			/** This filter is documented in class.jetpack-gutenberg.php */
1044
			|| ! apply_filters( 'jetpack_block_editor_enable_upgrade_nudge', false )
1045
			/** This filter is documented in _inc/lib/admin-pages/class.jetpack-react-page.php */
1046
			|| ! apply_filters( 'jetpack_show_promotions', true )
1047
			|| is_feed()
1048
		) {
1049
			return;
1050
		}
1051
1052
		jetpack_require_lib( 'components' );
1053
		return Jetpack_Components::render_upgrade_nudge(
1054
			array(
1055
				'plan' => $plan,
1056
			)
1057
		);
1058
	}
1059
1060
	/**
1061
	 * Output a notice within a block.
1062
	 *
1063
	 * @since 8.6.0
1064
	 *
1065
	 * @param string $message Notice we want to output.
1066
	 * @param string $status  Status of the notice. Can be one of success, info, warning, error. info by default.
1067
	 * @param string $classes List of CSS classes.
1068
	 *
1069
	 * @return string
1070
	 */
1071
	public static function notice( $message, $status = 'info', $classes = '' ) {
1072
		if (
1073
			empty( $message )
1074
			|| ! in_array( $status, array( 'success', 'info', 'warning', 'error' ), true )
1075
		) {
1076
			return '';
1077
		}
1078
1079
		$color = '';
0 ignored issues
show
Unused Code introduced by
$color is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1080
		switch ( $status ) {
1081
			case 'success':
1082
				$color = '#46b450';
1083
				break;
1084
			case 'warning':
1085
				$color = '#ffb900';
1086
				break;
1087
			case 'error':
1088
				$color = '#dc3232';
1089
				break;
1090
			case 'info':
1091
			default:
1092
				$color = '#00a0d2';
1093
				break;
1094
		}
1095
1096
		return sprintf(
1097
			'<div class="jetpack-block__notice %1$s %3$s" style="border-left:5px solid %4$s;padding:1em;background-color:#f8f9f9;">%2$s</div>',
1098
			esc_attr( $status ),
1099
			wp_kses(
1100
				$message,
1101
				array(
1102
					'br' => array(),
1103
					'p'  => array(),
1104
				)
1105
			),
1106
			esc_attr( $classes ),
1107
			sanitize_hex_color( $color )
1108
		);
1109
	}
1110
1111
	/**
1112
	 * Set the availability of the block as the editor
1113
	 * is loaded.
1114
	 *
1115
	 * @param string $slug Slug of the block.
1116
	 */
1117
	public static function set_availability_for_plan( $slug ) {
1118
		$is_available = true;
1119
		$plan         = '';
1120
		$slug         = self::remove_extension_prefix( $slug );
1121
1122
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
1123
			if ( ! class_exists( 'Store_Product_List' ) ) {
1124
				require WP_CONTENT_DIR . '/admin-plugins/wpcom-billing/store-product-list.php';
1125
			}
1126
			$features_data = Store_Product_List::get_site_specific_features_data();
1127
			$is_available  = in_array( $slug, $features_data['active'], true );
1128
			if ( ! empty( $features_data['available'][ $slug ] ) ) {
1129
				$plan = $features_data['available'][ $slug ][0];
1130
			}
1131
		} elseif ( ! jetpack_is_atomic_site() ) {
1132
			/*
1133
			 * If it's Atomic then assume all features are available
1134
			 * otherwise check against the Jetpack plan.
1135
			 */
1136
			$is_available = Jetpack_Plan::supports( $slug );
1137
			$plan         = Jetpack_Plan::get_minimum_plan_for_feature( $slug );
1138
		}
1139
		if ( $is_available ) {
1140
			self::set_extension_available( $slug );
1141
		} else {
1142
			self::set_extension_unavailable(
1143
				$slug,
1144
				'missing_plan',
1145
				array(
1146
					'required_feature' => $slug,
1147
					'required_plan'    => $plan,
1148
				)
1149
			);
1150
		}
1151
	}
1152
1153
	/**
1154
	 * Wraps the suplied render_callback in a function to check
1155
	 * the availability of the block before rendering it.
1156
	 *
1157
	 * @param string   $slug The block slug, used to check for availability.
1158
	 * @param callable $render_callback The render_callback that will be called if the block is available.
1159
	 */
1160
	public static function get_render_callback_with_availability_check( $slug, $render_callback ) {
1161
		return function ( $prepared_attributes, $block_content ) use ( $render_callback, $slug ) {
1162
			$availability = self::get_availability();
1163
			$bare_slug    = self::remove_extension_prefix( $slug );
1164
			if ( isset( $availability[ $bare_slug ] ) && $availability[ $bare_slug ]['available'] ) {
1165
				return call_user_func( $render_callback, $prepared_attributes, $block_content );
1166
			} elseif ( isset( $availability[ $bare_slug ]['details']['required_plan'] ) ) {
1167
				return self::upgrade_nudge( $availability[ $bare_slug ]['details']['required_plan'] );
1168
			}
1169
1170
			return null;
1171
		};
1172
	}
1173
}
1174