Completed
Push — fix/tiled-gallery-amp-mosaic-c... ( b708e7...398dc2 )
by Yaroslav
08:06
created

Jetpack_Gutenberg::set_availability_for_plan()   B

Complexity

Conditions 7
Paths 12

Size

Total Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
nc 12
nop 1
dl 0
loc 35
rs 8.4266
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
 * Wrapper function to safely register a gutenberg block type
14
 *
15
 * @param string $slug Slug of the block.
16
 * @param array  $args Arguments that are passed into register_block_type.
17
 *
18
 * @see register_block_type
19
 *
20
 * @since 6.7.0
21
 *
22
 * @return WP_Block_Type|false The registered block type on success, or false on failure.
23
 */
24
function jetpack_register_block( $slug, $args = array() ) {
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
30
	if ( isset( $args['version_requirements'] )
31
		&& ! Jetpack_Gutenberg::is_gutenberg_version_available( $args['version_requirements'], $slug ) ) {
32
		return false;
33
	}
34
35
	// Checking whether block is registered to ensure it isn't registered twice.
36
	if ( Jetpack_Gutenberg::is_registered( $slug ) ) {
37
		return false;
38
	}
39
40
	$feature_name = Jetpack_Gutenberg::remove_extension_prefix( $slug );
41
	// If the block is dynamic, and a Jetpack block, wrap the render_callback to check availability.
42
	if (
43
		isset( $args['plan_check'], $args['render_callback'] )
44
		&& true === $args['plan_check']
45
	) {
46
		$args['render_callback'] = Jetpack_Gutenberg::get_render_callback_with_availability_check( $feature_name, $args['render_callback'] );
47
		$method_name             = 'set_availability_for_plan';
48
	} else {
49
		$method_name = 'set_extension_available';
50
	}
51
52
	add_action(
53
		'jetpack_register_gutenberg_extensions',
54
		function() use ( $feature_name, $method_name ) {
55
			call_user_func( array( 'Jetpack_Gutenberg', $method_name ), $feature_name );
56
		}
57
	);
58
59
	return register_block_type( $slug, $args );
60
}
61
62
/**
63
 * Helper function to register a Jetpack Gutenberg plugin
64
 *
65
 * @deprecated 7.1.0 Use Jetpack_Gutenberg::set_extension_available() instead
66
 *
67
 * @param string $slug Slug of the plugin.
68
 *
69
 * @since 6.9.0
70
 *
71
 * @return void
72
 */
73
function jetpack_register_plugin( $slug ) {
74
	_deprecated_function( __FUNCTION__, '7.1', 'Jetpack_Gutenberg::set_extension_available' );
75
76
	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...
77
}
78
79
/**
80
 * Set the reason why an extension (block or plugin) is unavailable
81
 *
82
 * @deprecated 7.1.0 Use Jetpack_Gutenberg::set_extension_unavailable() instead
83
 *
84
 * @param string $slug Slug of the block.
85
 * @param string $reason A string representation of why the extension is unavailable.
86
 *
87
 * @since 7.0.0
88
 *
89
 * @return void
90
 */
91
function jetpack_set_extension_unavailability_reason( $slug, $reason ) {
92
	_deprecated_function( __FUNCTION__, '7.1', 'Jetpack_Gutenberg::set_extension_unavailable' );
93
94
	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...
95
}
96
97
/**
98
 * General Gutenberg editor specific functionality
99
 */
100
class Jetpack_Gutenberg {
101
102
	/**
103
	 * Only these extensions can be registered. Used to control availability of beta blocks.
104
	 *
105
	 * @var array Extensions allowed list.
106
	 */
107
	private static $extensions = array();
108
109
	/**
110
	 * Keeps track of the reasons why a given extension is unavailable.
111
	 *
112
	 * @var array Extensions availability information
113
	 */
114
	private static $availability = array();
115
116
	/**
117
	 * Check to see if a minimum version of Gutenberg is available. Because a Gutenberg version is not available in
118
	 * php if the Gutenberg plugin is not installed, if we know which minimum WP release has the required version we can
119
	 * optionally fall back to that.
120
	 *
121
	 * @param array  $version_requirements An array containing the required Gutenberg version and, if known, the WordPress version that was released with this minimum version.
122
	 * @param string $slug The slug of the block or plugin that has the gutenberg version requirement.
123
	 *
124
	 * @since 8.3.0
125
	 *
126
	 * @return boolean True if the version of gutenberg required by the block or plugin is available.
127
	 */
128
	public static function is_gutenberg_version_available( $version_requirements, $slug ) {
129
		global $wp_version;
130
131
		// Bail if we don't at least have the gutenberg version requirement, the WP version is optional.
132
		if ( empty( $version_requirements['gutenberg'] ) ) {
133
			return false;
134
		}
135
136
		// If running a local dev build of gutenberg plugin GUTENBERG_DEVELOPMENT_MODE is set so assume correct version.
137
		if ( defined( 'GUTENBERG_DEVELOPMENT_MODE' ) && GUTENBERG_DEVELOPMENT_MODE ) {
138
			return true;
139
		}
140
141
		$version_available = false;
142
143
		// If running a production build of the gutenberg plugin then GUTENBERG_VERSION is set, otherwise if WP version
144
		// with required version of Gutenberg is known check that.
145
		if ( defined( 'GUTENBERG_VERSION' ) ) {
146
			$version_available = version_compare( GUTENBERG_VERSION, $version_requirements['gutenberg'], '>=' );
147
		} elseif ( ! empty( $version_requirements['wp'] ) ) {
148
			$version_available = version_compare( $wp_version, $version_requirements['wp'], '>=' );
149
		}
150
151
		if ( ! $version_available ) {
152
			self::set_extension_unavailable(
153
				$slug,
154
				'incorrect_gutenberg_version',
155
				array(
156
					'required_feature' => $slug,
157
					'required_version' => $version_requirements,
158
					'current_version'  => array(
159
						'wp'        => $wp_version,
160
						'gutenberg' => defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : null,
161
					),
162
				)
163
			);
164
		}
165
166
		return $version_available;
167
	}
168
169
	/**
170
	 * Prepend the 'jetpack/' prefix to a block name
171
	 *
172
	 * @param string $block_name The block name.
173
	 *
174
	 * @return string The prefixed block name.
175
	 */
176
	private static function prepend_block_prefix( $block_name ) {
177
		return 'jetpack/' . $block_name;
178
	}
179
180
	/**
181
	 * Remove the 'jetpack/' or jetpack-' prefix from an extension name
182
	 *
183
	 * @param string $extension_name The extension name.
184
	 *
185
	 * @return string The unprefixed extension name.
186
	 */
187
	public static function remove_extension_prefix( $extension_name ) {
188
		if ( 0 === strpos( $extension_name, 'jetpack/' ) || 0 === strpos( $extension_name, 'jetpack-' ) ) {
189
			return substr( $extension_name, strlen( 'jetpack/' ) );
190
		}
191
		return $extension_name;
192
	}
193
194
	/**
195
	 * Whether two arrays share at least one item
196
	 *
197
	 * @param array $a An array.
198
	 * @param array $b Another array.
199
	 *
200
	 * @return boolean True if $a and $b share at least one item
201
	 */
202
	protected static function share_items( $a, $b ) {
203
		return count( array_intersect( $a, $b ) ) > 0;
204
	}
205
206
	/**
207
	 * Register a block
208
	 *
209
	 * @deprecated 7.1.0 Use jetpack_register_block() instead
210
	 *
211
	 * @param string $slug Slug of the block.
212
	 * @param array  $args Arguments that are passed into register_block_type().
213
	 */
214
	public static function register_block( $slug, $args ) {
215
		_deprecated_function( __METHOD__, '7.1', 'jetpack_register_block' );
216
217
		jetpack_register_block( 'jetpack/' . $slug, $args );
218
	}
219
220
	/**
221
	 * Register a plugin
222
	 *
223
	 * @deprecated 7.1.0 Use Jetpack_Gutenberg::set_extension_available() instead
224
	 *
225
	 * @param string $slug Slug of the plugin.
226
	 */
227
	public static function register_plugin( $slug ) {
228
		_deprecated_function( __METHOD__, '7.1', 'Jetpack_Gutenberg::set_extension_available' );
229
230
		self::set_extension_available( $slug );
231
	}
232
233
	/**
234
	 * Register a block
235
	 *
236
	 * @deprecated 7.0.0 Use jetpack_register_block() instead
237
	 *
238
	 * @param string $slug Slug of the block.
239
	 * @param array  $args Arguments that are passed into the register_block_type.
240
	 * @param array  $availability array containing if a block is available and the reason when it is not.
241
	 */
242
	public static function register( $slug, $args, $availability ) {
243
		_deprecated_function( __METHOD__, '7.0', 'jetpack_register_block' );
244
245
		if ( isset( $availability['available'] ) && ! $availability['available'] ) {
246
			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...
247
		} else {
248
			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...
249
		}
250
	}
251
252
	/**
253
	 * Set a (non-block) extension as available
254
	 *
255
	 * @param string $slug Slug of the extension.
256
	 */
257
	public static function set_extension_available( $slug ) {
258
		self::$availability[ self::remove_extension_prefix( $slug ) ] = true;
259
	}
260
261
	/**
262
	 * Set the reason why an extension (block or plugin) is unavailable
263
	 *
264
	 * @param string $slug Slug of the extension.
265
	 * @param string $reason A string representation of why the extension is unavailable.
266
	 * @param array  $details A free-form array containing more information on why the extension is unavailable.
267
	 */
268
	public static function set_extension_unavailable( $slug, $reason, $details = array() ) {
269
		if (
270
			// Extensions that require a plan may be eligible for upgrades.
271
			'missing_plan' === $reason
272
			&& (
273
				/**
274
				 * Filter 'jetpack_block_editor_enable_upgrade_nudge' with `true` to enable or `false`
275
				 * to disable paid feature upgrade nudges in the block editor.
276
				 *
277
				 * When this is changed to default to `true`, you should also update `modules/memberships/class-jetpack-memberships.php`
278
				 * See https://github.com/Automattic/jetpack/pull/13394#pullrequestreview-293063378
279
				 *
280
				 * @since 7.7.0
281
				 *
282
				 * @param boolean
283
				 */
284
				! apply_filters( 'jetpack_block_editor_enable_upgrade_nudge', false )
285
				/** This filter is documented in _inc/lib/admin-pages/class.jetpack-react-page.php */
286
				|| ! apply_filters( 'jetpack_show_promotions', true )
287
			)
288
		) {
289
			// The block editor may apply an upgrade nudge if `missing_plan` is the reason.
290
			// Add a descriptive suffix to disable behavior but provide informative reason.
291
			$reason .= '__nudge_disabled';
292
		}
293
294
		self::$availability[ self::remove_extension_prefix( $slug ) ] = array(
295
			'reason'  => $reason,
296
			'details' => $details,
297
		);
298
	}
299
300
	/**
301
	 * Set the reason why an extension (block or plugin) is unavailable
302
	 *
303
	 * @deprecated 7.1.0 Use set_extension_unavailable() instead
304
	 *
305
	 * @param string $slug Slug of the extension.
306
	 * @param string $reason A string representation of why the extension is unavailable.
307
	 */
308
	public static function set_extension_unavailability_reason( $slug, $reason ) {
309
		_deprecated_function( __METHOD__, '7.1', 'Jetpack_Gutenberg::set_extension_unavailable' );
310
311
		self::set_extension_unavailable( $slug, $reason );
312
	}
313
314
	/**
315
	 * Set up a list of allowed block editor extensions
316
	 *
317
	 * @return void
318
	 */
319
	public static function init() {
320
		if ( ! self::should_load() ) {
321
			return;
322
		}
323
324
		/**
325
		 * Alternative to `JETPACK_BETA_BLOCKS`, set to `true` to load Beta Blocks.
326
		 *
327
		 * @since 6.9.0
328
		 *
329
		 * @param boolean
330
		 */
331
		if ( apply_filters( 'jetpack_load_beta_blocks', false ) ) {
332
			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...
333
		}
334
335
		/**
336
		 * Alternative to `JETPACK_EXPERIMENTAL_BLOCKS`, set to `true` to load Experimental Blocks.
337
		 *
338
		 * @since 8.4.0
339
		 *
340
		 * @param boolean
341
		 */
342
		if ( apply_filters( 'jetpack_load_experimental_blocks', false ) ) {
343
			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...
344
		}
345
346
		/**
347
		 * Filter the list of block editor extensions that are available through Jetpack.
348
		 *
349
		 * @since 7.0.0
350
		 *
351
		 * @param array
352
		 */
353
		self::$extensions = apply_filters( 'jetpack_set_available_extensions', self::get_available_extensions() );
354
355
		/**
356
		 * Filter the list of block editor plugins that are available through Jetpack.
357
		 *
358
		 * @deprecated 7.0.0 Use jetpack_set_available_extensions instead
359
		 *
360
		 * @since 6.8.0
361
		 *
362
		 * @param array
363
		 */
364
		self::$extensions = apply_filters( 'jetpack_set_available_blocks', self::$extensions );
365
366
		/**
367
		 * Filter the list of block editor plugins that are available through Jetpack.
368
		 *
369
		 * @deprecated 7.0.0 Use jetpack_set_available_extensions instead
370
		 *
371
		 * @since 6.9.0
372
		 *
373
		 * @param array
374
		 */
375
		self::$extensions = apply_filters( 'jetpack_set_available_plugins', self::$extensions );
376
	}
377
378
	/**
379
	 * Resets the class to its original state
380
	 *
381
	 * Used in unit tests
382
	 *
383
	 * @return void
384
	 */
385
	public static function reset() {
386
		self::$extensions   = array();
387
		self::$availability = array();
388
	}
389
390
	/**
391
	 * Return the Gutenberg extensions (blocks and plugins) directory
392
	 *
393
	 * @return string The Gutenberg extensions directory
394
	 */
395
	public static function get_blocks_directory() {
396
		/**
397
		 * Filter to select Gutenberg blocks directory
398
		 *
399
		 * @since 6.9.0
400
		 *
401
		 * @param string default: '_inc/blocks/'
402
		 */
403
		return apply_filters( 'jetpack_blocks_directory', '_inc/blocks/' );
404
	}
405
406
	/**
407
	 * Checks for a given .json file in the blocks folder.
408
	 *
409
	 * @param string $preset The name of the .json file to look for.
410
	 *
411
	 * @return bool True if the file is found.
412
	 */
413
	public static function preset_exists( $preset ) {
414
		return file_exists( JETPACK__PLUGIN_DIR . self::get_blocks_directory() . $preset . '.json' );
415
	}
416
417
	/**
418
	 * Decodes JSON loaded from a preset file in the blocks folder
419
	 *
420
	 * @param string $preset The name of the .json file to load.
421
	 *
422
	 * @return mixed Returns an object if the file is present, or false if a valid .json file is not present.
423
	 */
424
	public static function get_preset( $preset ) {
425
		return json_decode(
426
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
427
			file_get_contents( JETPACK__PLUGIN_DIR . self::get_blocks_directory() . $preset . '.json' )
428
		);
429
	}
430
431
	/**
432
	 * Returns a list of Jetpack Gutenberg extensions (blocks and plugins), based on index.json
433
	 *
434
	 * @return array A list of blocks: eg [ 'publicize', 'markdown' ]
435
	 */
436
	public static function get_jetpack_gutenberg_extensions_allowed_list() {
437
		$preset_extensions_manifest = self::preset_exists( 'index' )
438
			? self::get_preset( 'index' )
439
			: (object) array();
440
		$blocks_variation           = self::blocks_variation();
441
442
		return self::get_extensions_preset_for_variation( $preset_extensions_manifest, $blocks_variation );
443
	}
444
445
	/**
446
	 * Returns a list of Jetpack Gutenberg extensions (blocks and plugins), based on index.json
447
	 *
448
	 * @deprecated 8.7.0 Use get_jetpack_gutenberg_extensions_allowed_list()
449
	 *
450
	 * @return array A list of blocks: eg [ 'publicize', 'markdown' ]
451
	 */
452
	public static function get_jetpack_gutenberg_extensions_whitelist() {
453
		_deprecated_function( __FUNCTION__, 'jetpack-8.7.0', 'Jetpack_Gutenberg::get_jetpack_gutenberg_extensions_allowed_list' );
454
		return self::get_jetpack_gutenberg_extensions_allowed_list();
455
	}
456
457
	/**
458
	 * Returns a diff from a combined list of allowed extensions and extensions determined to be excluded
459
	 *
460
	 * @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...
461
	 *
462
	 * @return array A list of blocks: eg array( 'publicize', 'markdown' )
463
	 */
464
	public static function get_available_extensions( $allowed_extensions = null ) {
465
		$exclusions         = get_option( 'jetpack_excluded_extensions', array() );
466
		$allowed_extensions = is_null( $allowed_extensions ) ? self::get_jetpack_gutenberg_extensions_allowed_list() : $allowed_extensions;
467
468
		return array_diff( $allowed_extensions, $exclusions );
469
	}
470
471
	/**
472
	 * Return true if the extension has been registered and there's nothing in the availablilty array.
473
	 *
474
	 * @param string $extension The name of the extension.
475
	 *
476
	 * @return bool whether the extension has been registered and there's nothing in the availablilty array.
477
	 */
478
	public static function is_registered_and_no_entry_in_availability( $extension ) {
479
		return self::is_registered( 'jetpack/' . $extension ) && ! isset( self::$availability[ $extension ] );
480
	}
481
482
	/**
483
	 * Return true if the extension has a true entry in the availablilty array.
484
	 *
485
	 * @param string $extension The name of the extension.
486
	 *
487
	 * @return bool whether the extension has a true entry in the availablilty array.
488
	 */
489
	public static function is_available( $extension ) {
490
		return isset( self::$availability[ $extension ] ) && true === self::$availability[ $extension ];
491
	}
492
493
	/**
494
	 * Get availability of each block / plugin.
495
	 *
496
	 * @return array A list of block and plugins and their availablity status
497
	 */
498
	public static function get_availability() {
499
		/**
500
		 * Fires before Gutenberg extensions availability is computed.
501
		 *
502
		 * In the function call you supply, use `jetpack_register_block()` to set a block as available.
503
		 * Alternatively, use `Jetpack_Gutenberg::set_extension_available()` (for a non-block plugin), and
504
		 * `Jetpack_Gutenberg::set_extension_unavailable()` (if the block or plugin should not be registered
505
		 * but marked as unavailable).
506
		 *
507
		 * @since 7.0.0
508
		 */
509
		do_action( 'jetpack_register_gutenberg_extensions' );
510
511
		$available_extensions = array();
512
513
		foreach ( self::$extensions as $extension ) {
514
			$is_available                       = self::is_registered_and_no_entry_in_availability( $extension ) || self::is_available( $extension );
515
			$available_extensions[ $extension ] = array(
516
				'available' => $is_available,
517
			);
518
519
			if ( ! $is_available ) {
520
				$reason  = isset( self::$availability[ $extension ] ) ? self::$availability[ $extension ]['reason'] : 'missing_module';
521
				$details = isset( self::$availability[ $extension ] ) ? self::$availability[ $extension ]['details'] : array();
522
				$available_extensions[ $extension ]['unavailable_reason'] = $reason;
523
				$available_extensions[ $extension ]['details']            = $details;
524
			}
525
		}
526
527
		return $available_extensions;
528
	}
529
530
	/**
531
	 * Check if an extension/block is already registered
532
	 *
533
	 * @since 7.2
534
	 *
535
	 * @param string $slug Name of extension/block to check.
536
	 *
537
	 * @return bool
538
	 */
539
	public static function is_registered( $slug ) {
540
		return WP_Block_Type_Registry::get_instance()->is_registered( $slug );
541
	}
542
543
	/**
544
	 * Check if Gutenberg editor is available
545
	 *
546
	 * @since 6.7.0
547
	 *
548
	 * @return bool
549
	 */
550
	public static function is_gutenberg_available() {
551
		return true;
552
	}
553
554
	/**
555
	 * Check whether conditions indicate Gutenberg Extensions (blocks and plugins) should be loaded
556
	 *
557
	 * Loading blocks and plugins is enabled by default and may be disabled via filter:
558
	 *   add_filter( 'jetpack_gutenberg', '__return_false' );
559
	 *
560
	 * @since 6.9.0
561
	 *
562
	 * @return bool
563
	 */
564
	public static function should_load() {
565
		if ( ! Jetpack::is_active() && ! ( new Status() )->is_development_mode() ) {
566
			return false;
567
		}
568
569
		/**
570
		 * Filter to disable Gutenberg blocks
571
		 *
572
		 * @since 6.5.0
573
		 *
574
		 * @param bool true Whether to load Gutenberg blocks
575
		 */
576
		return (bool) apply_filters( 'jetpack_gutenberg', true );
577
	}
578
579
	/**
580
	 * Only enqueue block assets when needed.
581
	 *
582
	 * @param string $type Slug of the block.
583
	 * @param array  $script_dependencies Script dependencies. Will be merged with automatically
584
	 *                                    detected script dependencies from the webpack build.
585
	 *
586
	 * @return void
587
	 */
588
	public static function load_assets_as_required( $type, $script_dependencies = array() ) {
589
		if ( is_admin() ) {
590
			// A block's view assets will not be required in wp-admin.
591
			return;
592
		}
593
594
		$type = sanitize_title_with_dashes( $type );
595
		self::load_styles_as_required( $type );
596
		self::load_scripts_as_required( $type, $script_dependencies );
597
	}
598
599
	/**
600
	 * Only enqueue block sytles when needed.
601
	 *
602
	 * @param string $type Slug of the block.
603
	 *
604
	 * @since 7.2.0
605
	 *
606
	 * @return void
607
	 */
608
	public static function load_styles_as_required( $type ) {
609
		if ( is_admin() ) {
610
			// A block's view assets will not be required in wp-admin.
611
			return;
612
		}
613
614
		// Enqueue styles.
615
		$style_relative_path = self::get_blocks_directory() . $type . '/view' . ( is_rtl() ? '.rtl' : '' ) . '.css';
616
		if ( self::block_has_asset( $style_relative_path ) ) {
617
			$style_version = self::get_asset_version( $style_relative_path );
618
			$view_style    = plugins_url( $style_relative_path, JETPACK__PLUGIN_FILE );
619
			wp_enqueue_style( 'jetpack-block-' . $type, $view_style, array(), $style_version );
620
		}
621
622
	}
623
624
	/**
625
	 * Only enqueue block scripts when needed.
626
	 *
627
	 * @param string $type Slug of the block.
628
	 * @param array  $script_dependencies Script dependencies. Will be merged with automatically
629
	 *                             detected script dependencies from the webpack build.
630
	 *
631
	 * @since 7.2.0
632
	 *
633
	 * @return void
634
	 */
635
	public static function load_scripts_as_required( $type, $script_dependencies = array() ) {
636
		if ( is_admin() ) {
637
			// A block's view assets will not be required in wp-admin.
638
			return;
639
		}
640
641
		// Enqueue script.
642
		$script_relative_path  = self::get_blocks_directory() . $type . '/view.js';
643
		$script_deps_path      = JETPACK__PLUGIN_DIR . self::get_blocks_directory() . $type . '/view.asset.php';
644
		$script_dependencies[] = 'wp-polyfill';
645
		if ( file_exists( $script_deps_path ) ) {
646
			$asset_manifest      = include $script_deps_path;
647
			$script_dependencies = array_unique( array_merge( $script_dependencies, $asset_manifest['dependencies'] ) );
648
		}
649
650
		if ( ( ! class_exists( 'Jetpack_AMP_Support' ) || ! Jetpack_AMP_Support::is_amp_request() ) && self::block_has_asset( $script_relative_path ) ) {
651
			$script_version = self::get_asset_version( $script_relative_path );
652
			$view_script    = plugins_url( $script_relative_path, JETPACK__PLUGIN_FILE );
653
			wp_enqueue_script( 'jetpack-block-' . $type, $view_script, $script_dependencies, $script_version, false );
654
		}
655
656
		wp_localize_script(
657
			'jetpack-block-' . $type,
658
			'Jetpack_Block_Assets_Base_Url',
659
			plugins_url( self::get_blocks_directory(), JETPACK__PLUGIN_FILE )
660
		);
661
	}
662
663
	/**
664
	 * Check if an asset exists for a block.
665
	 *
666
	 * @param string $file Path of the file we are looking for.
667
	 *
668
	 * @return bool $block_has_asset Does the file exist.
669
	 */
670
	public static function block_has_asset( $file ) {
671
		return file_exists( JETPACK__PLUGIN_DIR . $file );
672
	}
673
674
	/**
675
	 * Get the version number to use when loading the file. Allows us to bypass cache when developing.
676
	 *
677
	 * @param string $file Path of the file we are looking for.
678
	 *
679
	 * @return string $script_version Version number.
680
	 */
681
	public static function get_asset_version( $file ) {
682
		return Jetpack::is_development_version() && self::block_has_asset( $file )
683
			? filemtime( JETPACK__PLUGIN_DIR . $file )
684
			: JETPACK__VERSION;
685
	}
686
687
	/**
688
	 * Load Gutenberg editor assets
689
	 *
690
	 * @since 6.7.0
691
	 *
692
	 * @return void
693
	 */
694
	public static function enqueue_block_editor_assets() {
695
		if ( ! self::should_load() ) {
696
			return;
697
		}
698
699
		// Required for Analytics. See _inc/lib/admin-pages/class.jetpack-admin-page.php.
700
		if ( ! ( new Status() )->is_development_mode() && Jetpack::is_active() ) {
701
			wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true );
702
		}
703
704
		$rtl              = is_rtl() ? '.rtl' : '';
705
		$blocks_dir       = self::get_blocks_directory();
706
		$blocks_variation = self::blocks_variation();
707
708
		if ( 'production' !== $blocks_variation ) {
709
			$blocks_env = '-' . esc_attr( $blocks_variation );
710
		} else {
711
			$blocks_env = '';
712
		}
713
714
		$editor_script = plugins_url( "{$blocks_dir}editor{$blocks_env}.js", JETPACK__PLUGIN_FILE );
715
		$editor_style  = plugins_url( "{$blocks_dir}editor{$blocks_env}{$rtl}.css", JETPACK__PLUGIN_FILE );
716
717
		$editor_deps_path = JETPACK__PLUGIN_DIR . $blocks_dir . "editor{$blocks_env}.asset.php";
718
		$editor_deps      = array( 'wp-polyfill' );
719
		if ( file_exists( $editor_deps_path ) ) {
720
			$asset_manifest = include $editor_deps_path;
721
			$editor_deps    = $asset_manifest['dependencies'];
722
		}
723
724
		$version = Jetpack::is_development_version() && file_exists( JETPACK__PLUGIN_DIR . $blocks_dir . 'editor.js' )
725
			? filemtime( JETPACK__PLUGIN_DIR . $blocks_dir . 'editor.js' )
726
			: JETPACK__VERSION;
727
728
		if ( method_exists( 'Jetpack', 'build_raw_urls' ) ) {
729
			$site_fragment = Jetpack::build_raw_urls( home_url() );
730
		} elseif ( class_exists( 'WPCOM_Masterbar' ) && method_exists( 'WPCOM_Masterbar', 'get_calypso_site_slug' ) ) {
731
			$site_fragment = WPCOM_Masterbar::get_calypso_site_slug( get_current_blog_id() );
732
		} else {
733
			$site_fragment = '';
734
		}
735
736
		wp_enqueue_script(
737
			'jetpack-blocks-editor',
738
			$editor_script,
739
			$editor_deps,
740
			$version,
741
			false
742
		);
743
744
		wp_localize_script(
745
			'jetpack-blocks-editor',
746
			'Jetpack_Block_Assets_Base_Url',
747
			plugins_url( $blocks_dir . '/', JETPACK__PLUGIN_FILE )
748
		);
749
750
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
751
			$user                      = wp_get_current_user();
752
			$user_data                 = array(
753
				'userid'   => $user->ID,
754
				'username' => $user->user_login,
755
			);
756
			$blog_id                   = get_current_blog_id();
757
			$is_current_user_connected = true;
758
		} else {
759
			$user_data                 = Jetpack_Tracks_Client::get_connected_user_tracks_identity();
760
			$blog_id                   = Jetpack_Options::get_option( 'id', 0 );
761
			$is_current_user_connected = Jetpack::is_user_connected();
762
		}
763
764
		wp_localize_script(
765
			'jetpack-blocks-editor',
766
			'Jetpack_Editor_Initial_State',
767
			array(
768
				'available_blocks' => self::get_availability(),
769
				'jetpack'          => array(
770
					'is_active'                 => Jetpack::is_active(),
771
					'is_current_user_connected' => $is_current_user_connected,
772
				),
773
				'siteFragment'     => $site_fragment,
774
				'tracksUserData'   => $user_data,
775
				'wpcomBlogId'      => $blog_id,
776
				'allowedMimeTypes' => wp_get_mime_types(),
777
			)
778
		);
779
780
		wp_set_script_translations( 'jetpack-blocks-editor', 'jetpack' );
781
782
		wp_enqueue_style( 'jetpack-blocks-editor', $editor_style, array(), $version );
783
	}
784
785
	/**
786
	 * Some blocks do not depend on a specific module,
787
	 * and can consequently be loaded outside of the usual modules.
788
	 * We will look for such modules in the extensions/ directory.
789
	 *
790
	 * @since 7.1.0
791
	 */
792
	public static function load_independent_blocks() {
793
		if ( self::should_load() ) {
794
			/**
795
			 * Look for files that match our list of available Jetpack Gutenberg extensions (blocks and plugins).
796
			 * If available, load them.
797
			 */
798
			foreach ( self::$extensions as $extension ) {
799
				$extension_file_glob = glob( JETPACK__PLUGIN_DIR . 'extensions/*/' . $extension . '/' . $extension . '.php' );
800
				if ( ! empty( $extension_file_glob ) ) {
801
					include_once $extension_file_glob[0];
802
				}
803
			}
804
		}
805
	}
806
807
	/**
808
	 * Get CSS classes for a block.
809
	 *
810
	 * @since 7.7.0
811
	 *
812
	 * @param string $slug  Block slug.
813
	 * @param array  $attr  Block attributes.
814
	 * @param array  $extra Potential extra classes you may want to provide.
815
	 *
816
	 * @return string $classes List of CSS classes for a block.
817
	 */
818
	public static function block_classes( $slug = '', $attr, $extra = array() ) {
819
		if ( empty( $slug ) ) {
820
			return '';
821
		}
822
823
		// Basic block name class.
824
		$classes = array(
825
			'wp-block-jetpack-' . $slug,
826
		);
827
828
		// Add alignment if provided.
829
		if (
830
			! empty( $attr['align'] )
831
			&& in_array( $attr['align'], array( 'left', 'center', 'right', 'wide', 'full' ), true )
832
		) {
833
			array_push( $classes, 'align' . $attr['align'] );
834
		}
835
836
		// Add custom classes if provided in the block editor.
837
		if ( ! empty( $attr['className'] ) ) {
838
			array_push( $classes, $attr['className'] );
839
		}
840
841
		// Add any extra classes.
842
		if ( is_array( $extra ) && ! empty( $extra ) ) {
843
			$classes = array_merge( $classes, array_filter( $extra ) );
844
		}
845
846
		return implode( ' ', $classes );
847
	}
848
849
	/**
850
	 * Determine whether a site should use the default set of blocks, or a custom set.
851
	 * Possible variations are currently beta, experimental, and production.
852
	 *
853
	 * @since 8.1.0
854
	 *
855
	 * @return string $block_varation production|beta|experimental
856
	 */
857
	public static function blocks_variation() {
858
		// Default to production blocks.
859
		$block_varation = 'production';
860
861
		if ( Constants::is_true( 'JETPACK_BETA_BLOCKS' ) ) {
862
			$block_varation = 'beta';
863
		}
864
865
		/*
866
		 * Switch to experimental blocks if you use the JETPACK_EXPERIMENTAL_BLOCKS constant.
867
		 */
868
		if ( Constants::is_true( 'JETPACK_EXPERIMENTAL_BLOCKS' ) ) {
869
			$block_varation = 'experimental';
870
		}
871
872
		/**
873
		 * Allow customizing the variation of blocks in use on a site.
874
		 *
875
		 * @since 8.1.0
876
		 *
877
		 * @param string $block_variation Can be beta, experimental, and production. Defaults to production.
878
		 */
879
		return apply_filters( 'jetpack_blocks_variation', $block_varation );
880
	}
881
882
	/**
883
	 * Get a list of extensions available for the variation you chose.
884
	 *
885
	 * @since 8.1.0
886
	 *
887
	 * @param obj    $preset_extensions_manifest List of extensions available in Jetpack.
888
	 * @param string $blocks_variation           Subset of blocks. production|beta|experimental.
889
	 *
890
	 * @return array $preset_extensions Array of extensions for that variation
891
	 */
892
	public static function get_extensions_preset_for_variation( $preset_extensions_manifest, $blocks_variation ) {
893
		$preset_extensions = isset( $preset_extensions_manifest->{ $blocks_variation } )
894
				? (array) $preset_extensions_manifest->{ $blocks_variation }
895
				: array();
896
897
		/*
898
		 * Experimental and Beta blocks need the production blocks as well.
899
		 */
900 View Code Duplication
		if (
901
			'experimental' === $blocks_variation
902
			|| 'beta' === $blocks_variation
903
		) {
904
			$production_extensions = isset( $preset_extensions_manifest->production )
905
				? (array) $preset_extensions_manifest->production
906
				: array();
907
908
			$preset_extensions = array_unique( array_merge( $preset_extensions, $production_extensions ) );
909
		}
910
911
		/*
912
		 * Beta blocks need the experimental blocks as well.
913
		 *
914
		 * If you've chosen to see Beta blocks,
915
		 * we want to make all blocks available to you:
916
		 * - Production
917
		 * - Experimental
918
		 * - Beta
919
		 */
920 View Code Duplication
		if ( 'beta' === $blocks_variation ) {
921
			$production_extensions = isset( $preset_extensions_manifest->experimental )
922
				? (array) $preset_extensions_manifest->experimental
923
				: array();
924
925
			$preset_extensions = array_unique( array_merge( $preset_extensions, $production_extensions ) );
926
		}
927
928
		return $preset_extensions;
929
	}
930
931
	/**
932
	 * Validate a URL used in a SSR block.
933
	 *
934
	 * @since 8.3.0
935
	 *
936
	 * @param string $url      URL saved as an attribute in block.
937
	 * @param array  $allowed  Array of allowed hosts for that block, or regexes to check against.
938
	 * @param bool   $is_regex Array of regexes matching the URL that could be used in block.
939
	 *
940
	 * @return bool|string
941
	 */
942
	public static function validate_block_embed_url( $url, $allowed = array(), $is_regex = false ) {
943
		if (
944
			empty( $url )
945
			|| ! is_array( $allowed )
946
			|| empty( $allowed )
947
		) {
948
			return false;
949
		}
950
951
		$url_components = wp_parse_url( $url );
952
953
		// Bail early if we cannot find a host.
954
		if ( empty( $url_components['host'] ) ) {
955
			return false;
956
		}
957
958
		// Normalize URL.
959
		$url = sprintf(
960
			'%s://%s%s%s',
961
			isset( $url_components['scheme'] ) ? $url_components['scheme'] : 'https',
962
			$url_components['host'],
963
			isset( $url_components['path'] ) ? $url_components['path'] : '/',
964
			isset( $url_components['query'] ) ? '?' . $url_components['query'] : ''
965
		);
966
967
		if ( ! empty( $url_components['fragment'] ) ) {
968
			$url = $url . '#' . rawurlencode( $url_components['fragment'] );
969
		}
970
971
		/*
972
		 * If we're using an allowed list of hosts,
973
		 * check if the URL belongs to one of the domains allowed for that block.
974
		 */
975
		if (
976
			false === $is_regex
977
			&& in_array( $url_components['host'], $allowed, true )
978
		) {
979
			return $url;
980
		}
981
982
		/*
983
		 * If we are using an array of regexes to check against,
984
		 * loop through that.
985
		 */
986
		if ( true === $is_regex ) {
987
			foreach ( $allowed as $regex ) {
988
				if ( 1 === preg_match( $regex, $url ) ) {
989
					return $url;
990
				}
991
			}
992
		}
993
994
		return false;
995
	}
996
997
	/**
998
	 * Output an UpgradeNudge Component on the frontend of a site.
999
	 *
1000
	 * @since 8.4.0
1001
	 *
1002
	 * @param string $plan The plan that users need to purchase to make the block work.
1003
	 *
1004
	 * @return string
1005
	 */
1006
	public static function upgrade_nudge( $plan ) {
1007
		if (
1008
			! current_user_can( 'manage_options' )
1009
			/** This filter is documented in class.jetpack-gutenberg.php */
1010
			|| ! apply_filters( 'jetpack_block_editor_enable_upgrade_nudge', false )
1011
			/** This filter is documented in _inc/lib/admin-pages/class.jetpack-react-page.php */
1012
			|| ! apply_filters( 'jetpack_show_promotions', true )
1013
			|| is_feed()
1014
		) {
1015
			return;
1016
		}
1017
1018
		jetpack_require_lib( 'components' );
1019
		return Jetpack_Components::render_upgrade_nudge(
1020
			array(
1021
				'plan' => $plan,
1022
			)
1023
		);
1024
	}
1025
1026
	/**
1027
	 * Output a notice within a block.
1028
	 *
1029
	 * @since 8.6.0
1030
	 *
1031
	 * @param string $message Notice we want to output.
1032
	 * @param string $status  Status of the notice. Can be one of success, info, warning, error. info by default.
1033
	 * @param string $classes List of CSS classes.
1034
	 *
1035
	 * @return string
1036
	 */
1037
	public static function notice( $message, $status = 'info', $classes = '' ) {
1038
		if (
1039
			empty( $message )
1040
			|| ! in_array( $status, array( 'success', 'info', 'warning', 'error' ), true )
1041
		) {
1042
			return '';
1043
		}
1044
1045
		$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...
1046
		switch ( $status ) {
1047
			case 'success':
1048
				$color = '#46b450';
1049
				break;
1050
			case 'warning':
1051
				$color = '#ffb900';
1052
				break;
1053
			case 'error':
1054
				$color = '#dc3232';
1055
				break;
1056
			case 'info':
1057
			default:
1058
				$color = '#00a0d2';
1059
				break;
1060
		}
1061
1062
		return sprintf(
1063
			'<div class="jetpack-block__notice %1$s %3$s" style="border-left:5px solid %4$s;padding:1em;background-color:#f8f9f9;">%2$s</div>',
1064
			esc_attr( $status ),
1065
			wp_kses(
1066
				$message,
1067
				array(
1068
					'br' => array(),
1069
					'p'  => array(),
1070
				)
1071
			),
1072
			esc_attr( $classes ),
1073
			sanitize_hex_color( $color )
1074
		);
1075
	}
1076
1077
	/**
1078
	 * Set the availability of the block as the editor
1079
	 * is loaded.
1080
	 *
1081
	 * @param string $slug Slug of the block.
1082
	 */
1083
	public static function set_availability_for_plan( $slug ) {
1084
		$is_available = true;
1085
		$plan         = '';
1086
		$slug         = self::remove_extension_prefix( $slug );
1087
1088
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
1089
			if ( ! class_exists( 'Store_Product_List' ) ) {
1090
				require WP_CONTENT_DIR . '/admin-plugins/wpcom-billing/store-product-list.php';
1091
			}
1092
			$features_data = Store_Product_List::get_site_specific_features_data();
1093
			$is_available  = in_array( $slug, $features_data['active'], true );
1094
			if ( ! empty( $features_data['available'][ $slug ] ) ) {
1095
				$plan = $features_data['available'][ $slug ][0];
1096
			}
1097
		} elseif ( ! jetpack_is_atomic_site() ) {
1098
			/*
1099
			 * If it's Atomic then assume all features are available
1100
			 * otherwise check against the Jetpack plan.
1101
			 */
1102
			$is_available = Jetpack_Plan::supports( $slug );
1103
			$plan         = Jetpack_Plan::get_minimum_plan_for_feature( $slug );
1104
		}
1105
		if ( $is_available ) {
1106
			self::set_extension_available( $slug );
1107
		} else {
1108
			self::set_extension_unavailable(
1109
				$slug,
1110
				'missing_plan',
1111
				array(
1112
					'required_feature' => $slug,
1113
					'required_plan'    => $plan,
1114
				)
1115
			);
1116
		}
1117
	}
1118
1119
	/**
1120
	 * Wraps the suplied render_callback in a function to check
1121
	 * the availability of the block before rendering it.
1122
	 *
1123
	 * @param string   $slug The block slug, used to check for availability.
1124
	 * @param callable $render_callback The render_callback that will be called if the block is available.
1125
	 */
1126
	public static function get_render_callback_with_availability_check( $slug, $render_callback ) {
1127
		return function ( $prepared_attributes, $block_content ) use ( $render_callback, $slug ) {
1128
			$availability = self::get_availability();
1129
			$bare_slug    = self::remove_extension_prefix( $slug );
1130
			if ( isset( $availability[ $bare_slug ] ) && $availability[ $bare_slug ]['available'] ) {
1131
				return call_user_func( $render_callback, $prepared_attributes, $block_content );
1132
			} elseif ( isset( $availability[ $bare_slug ]['details']['required_plan'] ) ) {
1133
				return self::upgrade_nudge( $availability[ $bare_slug ]['details']['required_plan'] );
1134
			}
1135
1136
			return null;
1137
		};
1138
	}
1139
}
1140