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

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
334
	}
335
336
	/**
337
	 * Return the Gutenberg extensions (blocks and plugins) directory
338
	 *
339
	 * @return string The Gutenberg extensions directory
340
	 */
341
	public static function get_blocks_directory() {
342
		/**
343
		 * Filter to select Gutenberg blocks directory
344
		 *
345
		 * @since 6.9.0
346
		 *
347
		 * @param string default: '_inc/blocks/'
348
		 */
349
		return apply_filters( 'jetpack_blocks_directory', '_inc/blocks/' );
350
	}
351
352
	/**
353
	 * Checks for a given .json file in the blocks folder.
354
	 *
355
	 * @param string $preset The name of the .json file to look for.
356
	 *
357
	 * @return bool True if the file is found.
358
	 */
359
	public static function preset_exists( $preset ) {
360
		return file_exists( JETPACK__PLUGIN_DIR . self::get_blocks_directory() . $preset . '.json' );
361
	}
362
363
	/**
364
	 * Decodes JSON loaded from a preset file in the blocks folder
365
	 *
366
	 * @param string $preset The name of the .json file to load.
367
	 *
368
	 * @return mixed Returns an object if the file is present, or false if a valid .json file is not present.
369
	 */
370
	public static function get_preset( $preset ) {
371
		return json_decode(
372
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
373
			file_get_contents( JETPACK__PLUGIN_DIR . self::get_blocks_directory() . $preset . '.json' )
374
		);
375
	}
376
377
	/**
378
	 * Returns a list of Jetpack Gutenberg extensions (blocks and plugins), based on index.json
379
	 *
380
	 * @return array A list of blocks: eg [ 'publicize', 'markdown' ]
381
	 */
382
	public static function get_jetpack_gutenberg_extensions_allowed_list() {
383
		$preset_extensions_manifest = self::preset_exists( 'index' )
384
			? self::get_preset( 'index' )
385
			: (object) array();
386
		$blocks_variation           = self::blocks_variation();
387
388
		return self::get_extensions_preset_for_variation( $preset_extensions_manifest, $blocks_variation );
389
	}
390
391
	/**
392
	 * Returns a list of Jetpack Gutenberg extensions (blocks and plugins), based on index.json
393
	 *
394
	 * @deprecated 8.7.0 Use get_jetpack_gutenberg_extensions_allowed_list()
395
	 *
396
	 * @return array A list of blocks: eg [ 'publicize', 'markdown' ]
397
	 */
398
	public static function get_jetpack_gutenberg_extensions_whitelist() {
399
		_deprecated_function( __FUNCTION__, 'jetpack-8.7.0', 'Jetpack_Gutenberg::get_jetpack_gutenberg_extensions_allowed_list' );
400
		return self::get_jetpack_gutenberg_extensions_allowed_list();
401
	}
402
403
	/**
404
	 * Returns a diff from a combined list of allowed extensions and extensions determined to be excluded
405
	 *
406
	 * @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...
407
	 *
408
	 * @return array A list of blocks: eg array( 'publicize', 'markdown' )
409
	 */
410
	public static function get_available_extensions( $allowed_extensions = null ) {
411
		$exclusions         = get_option( 'jetpack_excluded_extensions', array() );
412
		$allowed_extensions = is_null( $allowed_extensions ) ? self::get_jetpack_gutenberg_extensions_allowed_list() : $allowed_extensions;
413
414
		return array_diff( $allowed_extensions, $exclusions );
415
	}
416
417
	/**
418
	 * Return true if the extension has been registered and there's nothing in the availablilty array.
419
	 *
420
	 * @param string $extension The name of the extension.
421
	 *
422
	 * @return bool whether the extension has been registered and there's nothing in the availablilty array.
423
	 */
424
	public static function is_registered_and_no_entry_in_availability( $extension ) {
425
		return self::is_registered( 'jetpack/' . $extension ) && ! isset( self::$availability[ $extension ] );
426
	}
427
428
	/**
429
	 * Return true if the extension has a true entry in the availablilty array.
430
	 *
431
	 * @param string $extension The name of the extension.
432
	 *
433
	 * @return bool whether the extension has a true entry in the availablilty array.
434
	 */
435
	public static function is_available( $extension ) {
436
		return isset( self::$availability[ $extension ] ) && true === self::$availability[ $extension ];
437
	}
438
439
	/**
440
	 * Get the availability of each block / plugin, or return the cached availability
441
	 * if it has already been calculated. Avoids re-registering extensions when not
442
	 * necessary.
443
	 *
444
	 * @return array A list of block and plugins and their availability status.
445
	 */
446
	public static function get_cached_availability() {
447
		if ( null === self::$cached_availability ) {
448
			self::$cached_availability = self::get_availability();
449
		}
450
		return self::$cached_availability;
451
	}
452
453
	/**
454
	 * Get availability of each block / plugin.
455
	 *
456
	 * @return array A list of block and plugins and their availablity status
457
	 */
458
	public static function get_availability() {
459
		/**
460
		 * Fires before Gutenberg extensions availability is computed.
461
		 *
462
		 * In the function call you supply, use `Blocks::jetpack_register_block()` to set a block as available.
463
		 * Alternatively, use `Jetpack_Gutenberg::set_extension_available()` (for a non-block plugin), and
464
		 * `Jetpack_Gutenberg::set_extension_unavailable()` (if the block or plugin should not be registered
465
		 * but marked as unavailable).
466
		 *
467
		 * @since 7.0.0
468
		 */
469
		do_action( 'jetpack_register_gutenberg_extensions' );
470
471
		$available_extensions = array();
472
473
		foreach ( self::$extensions as $extension ) {
474
			$is_available                       = self::is_registered_and_no_entry_in_availability( $extension ) || self::is_available( $extension );
475
			$available_extensions[ $extension ] = array(
476
				'available' => $is_available,
477
			);
478
479
			if ( ! $is_available ) {
480
				$reason  = isset( self::$availability[ $extension ] ) ? self::$availability[ $extension ]['reason'] : 'missing_module';
481
				$details = isset( self::$availability[ $extension ] ) ? self::$availability[ $extension ]['details'] : array();
482
				$available_extensions[ $extension ]['unavailable_reason'] = $reason;
483
				$available_extensions[ $extension ]['details']            = $details;
484
			}
485
		}
486
487
		return $available_extensions;
488
	}
489
490
	/**
491
	 * Check if an extension/block is already registered
492
	 *
493
	 * @since 7.2
494
	 *
495
	 * @param string $slug Name of extension/block to check.
496
	 *
497
	 * @return bool
498
	 */
499
	public static function is_registered( $slug ) {
500
		return WP_Block_Type_Registry::get_instance()->is_registered( $slug );
501
	}
502
503
	/**
504
	 * Check if Gutenberg editor is available
505
	 *
506
	 * @since 6.7.0
507
	 *
508
	 * @return bool
509
	 */
510
	public static function is_gutenberg_available() {
511
		return true;
512
	}
513
514
	/**
515
	 * Check whether conditions indicate Gutenberg Extensions (blocks and plugins) should be loaded
516
	 *
517
	 * Loading blocks and plugins is enabled by default and may be disabled via filter:
518
	 *   add_filter( 'jetpack_gutenberg', '__return_false' );
519
	 *
520
	 * @since 6.9.0
521
	 *
522
	 * @return bool
523
	 */
524
	public static function should_load() {
525
		if ( ! Jetpack::is_active() && ! ( new Status() )->is_offline_mode() ) {
526
			return false;
527
		}
528
529
		/**
530
		 * Filter to disable Gutenberg blocks
531
		 *
532
		 * @since 6.5.0
533
		 *
534
		 * @param bool true Whether to load Gutenberg blocks
535
		 */
536
		return (bool) apply_filters( 'jetpack_gutenberg', true );
537
	}
538
539
	/**
540
	 * Only enqueue block assets when needed.
541
	 *
542
	 * @param string $type Slug of the block.
543
	 * @param array  $script_dependencies Script dependencies. Will be merged with automatically
544
	 *                                    detected script dependencies from the webpack build.
545
	 *
546
	 * @return void
547
	 */
548
	public static function load_assets_as_required( $type, $script_dependencies = array() ) {
549
		if ( is_admin() ) {
550
			// A block's view assets will not be required in wp-admin.
551
			return;
552
		}
553
554
		$type = sanitize_title_with_dashes( $type );
555
		self::load_styles_as_required( $type );
556
		self::load_scripts_as_required( $type, $script_dependencies );
557
	}
558
559
	/**
560
	 * Only enqueue block sytles when needed.
561
	 *
562
	 * @param string $type Slug of the block.
563
	 *
564
	 * @since 7.2.0
565
	 *
566
	 * @return void
567
	 */
568
	public static function load_styles_as_required( $type ) {
569
		if ( is_admin() ) {
570
			// A block's view assets will not be required in wp-admin.
571
			return;
572
		}
573
574
		// Enqueue styles.
575
		$style_relative_path = self::get_blocks_directory() . $type . '/view' . ( is_rtl() ? '.rtl' : '' ) . '.css';
576
		if ( self::block_has_asset( $style_relative_path ) ) {
577
			$style_version = self::get_asset_version( $style_relative_path );
578
			$view_style    = plugins_url( $style_relative_path, JETPACK__PLUGIN_FILE );
579
			wp_enqueue_style( 'jetpack-block-' . $type, $view_style, array(), $style_version );
580
		}
581
582
	}
583
584
	/**
585
	 * Only enqueue block scripts when needed.
586
	 *
587
	 * @param string $type Slug of the block.
588
	 * @param array  $script_dependencies Script dependencies. Will be merged with automatically
589
	 *                             detected script dependencies from the webpack build.
590
	 *
591
	 * @since 7.2.0
592
	 *
593
	 * @return void
594
	 */
595
	public static function load_scripts_as_required( $type, $script_dependencies = array() ) {
596
		if ( is_admin() ) {
597
			// A block's view assets will not be required in wp-admin.
598
			return;
599
		}
600
601
		// Enqueue script.
602
		$script_relative_path  = self::get_blocks_directory() . $type . '/view.js';
603
		$script_deps_path      = JETPACK__PLUGIN_DIR . self::get_blocks_directory() . $type . '/view.asset.php';
604
		$script_dependencies[] = 'wp-polyfill';
605
		if ( file_exists( $script_deps_path ) ) {
606
			$asset_manifest      = include $script_deps_path;
607
			$script_dependencies = array_unique( array_merge( $script_dependencies, $asset_manifest['dependencies'] ) );
608
		}
609
610
		if ( ! Blocks::is_amp_request() && self::block_has_asset( $script_relative_path ) ) {
611
			$script_version = self::get_asset_version( $script_relative_path );
612
			$view_script    = plugins_url( $script_relative_path, JETPACK__PLUGIN_FILE );
613
			wp_enqueue_script( 'jetpack-block-' . $type, $view_script, $script_dependencies, $script_version, false );
614
		}
615
616
		wp_localize_script(
617
			'jetpack-block-' . $type,
618
			'Jetpack_Block_Assets_Base_Url',
619
			array(
620
				'url' => plugins_url( self::get_blocks_directory(), JETPACK__PLUGIN_FILE ),
621
			)
622
		);
623
	}
624
625
	/**
626
	 * Check if an asset exists for a block.
627
	 *
628
	 * @param string $file Path of the file we are looking for.
629
	 *
630
	 * @return bool $block_has_asset Does the file exist.
631
	 */
632
	public static function block_has_asset( $file ) {
633
		return file_exists( JETPACK__PLUGIN_DIR . $file );
634
	}
635
636
	/**
637
	 * Get the version number to use when loading the file. Allows us to bypass cache when developing.
638
	 *
639
	 * @param string $file Path of the file we are looking for.
640
	 *
641
	 * @return string $script_version Version number.
642
	 */
643
	public static function get_asset_version( $file ) {
644
		return Jetpack::is_development_version() && self::block_has_asset( $file )
645
			? filemtime( JETPACK__PLUGIN_DIR . $file )
646
			: JETPACK__VERSION;
647
	}
648
649
	/**
650
	 * Load Gutenberg editor assets
651
	 *
652
	 * @since 6.7.0
653
	 *
654
	 * @return void
655
	 */
656
	public static function enqueue_block_editor_assets() {
657
		if ( ! self::should_load() ) {
658
			return;
659
		}
660
661
		$status = new Status();
662
663
		// Required for Analytics. See _inc/lib/admin-pages/class.jetpack-admin-page.php.
664
		if ( ! $status->is_offline_mode() && Jetpack::is_active() ) {
665
			wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true );
666
		}
667
668
		$rtl              = is_rtl() ? '.rtl' : '';
669
		$blocks_dir       = self::get_blocks_directory();
670
		$blocks_variation = self::blocks_variation();
671
672
		if ( 'production' !== $blocks_variation ) {
673
			$blocks_env = '-' . esc_attr( $blocks_variation );
674
		} else {
675
			$blocks_env = '';
676
		}
677
678
		$editor_script = plugins_url( "{$blocks_dir}editor{$blocks_env}.js", JETPACK__PLUGIN_FILE );
679
		$editor_style  = plugins_url( "{$blocks_dir}editor{$blocks_env}{$rtl}.css", JETPACK__PLUGIN_FILE );
680
681
		$editor_deps_path = JETPACK__PLUGIN_DIR . $blocks_dir . "editor{$blocks_env}.asset.php";
682
		$editor_deps      = array( 'wp-polyfill' );
683
		if ( file_exists( $editor_deps_path ) ) {
684
			$asset_manifest = include $editor_deps_path;
685
			$editor_deps    = $asset_manifest['dependencies'];
686
		}
687
688
		$version = Jetpack::is_development_version() && file_exists( JETPACK__PLUGIN_DIR . $blocks_dir . 'editor.js' )
689
			? filemtime( JETPACK__PLUGIN_DIR . $blocks_dir . 'editor.js' )
690
			: JETPACK__VERSION;
691
692
		wp_enqueue_script(
693
			'jetpack-blocks-editor',
694
			$editor_script,
695
			$editor_deps,
696
			$version,
697
			false
698
		);
699
700
		wp_localize_script(
701
			'jetpack-blocks-editor',
702
			'Jetpack_Block_Assets_Base_Url',
703
			array(
704
				'url' => plugins_url( $blocks_dir . '/', JETPACK__PLUGIN_FILE ),
705
			)
706
		);
707
708
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
709
			$user                      = wp_get_current_user();
710
			$user_data                 = array(
711
				'userid'   => $user->ID,
712
				'username' => $user->user_login,
713
			);
714
			$blog_id                   = get_current_blog_id();
715
			$is_current_user_connected = true;
716
		} else {
717
			$user_data                 = Jetpack_Tracks_Client::get_connected_user_tracks_identity();
718
			$blog_id                   = Jetpack_Options::get_option( 'id', 0 );
719
			$is_current_user_connected = Jetpack::is_user_connected();
720
		}
721
722
		wp_localize_script(
723
			'jetpack-blocks-editor',
724
			'Jetpack_Editor_Initial_State',
725
			array(
726
				'available_blocks' => self::get_availability(),
727
				'jetpack'          => array(
728
					'is_active'                 => Jetpack::is_active(),
729
					'is_current_user_connected' => $is_current_user_connected,
730
					/** This filter is documented in class.jetpack-gutenberg.php */
731
					'enable_upgrade_nudge'      => apply_filters( 'jetpack_block_editor_enable_upgrade_nudge', false ),
732
				),
733
				'siteFragment'     => $status->get_site_suffix(),
734
				'adminUrl'         => esc_url( admin_url() ),
735
				'tracksUserData'   => $user_data,
736
				'wpcomBlogId'      => $blog_id,
737
				'allowedMimeTypes' => wp_get_mime_types(),
738
			)
739
		);
740
741
		wp_set_script_translations( 'jetpack-blocks-editor', 'jetpack' );
742
743
		wp_enqueue_style( 'jetpack-blocks-editor', $editor_style, array(), $version );
744
	}
745
746
	/**
747
	 * Some blocks do not depend on a specific module,
748
	 * and can consequently be loaded outside of the usual modules.
749
	 * We will look for such modules in the extensions/ directory.
750
	 *
751
	 * @since 7.1.0
752
	 */
753
	public static function load_independent_blocks() {
754
		if ( self::should_load() ) {
755
			/**
756
			 * Look for files that match our list of available Jetpack Gutenberg extensions (blocks and plugins).
757
			 * If available, load them.
758
			 */
759
			foreach ( self::$extensions as $extension ) {
760
				$extension_file_glob = glob( JETPACK__PLUGIN_DIR . 'extensions/*/' . $extension . '/' . $extension . '.php' );
761
				if ( ! empty( $extension_file_glob ) ) {
762
					include_once $extension_file_glob[0];
763
				}
764
			}
765
		}
766
	}
767
768
	/**
769
	 * Loads PHP components of extended-blocks.
770
	 *
771
	 * @since 8.9.0
772
	 */
773
	public static function load_extended_blocks() {
774
		if ( self::should_load() ) {
775
			$extended_blocks = glob( JETPACK__PLUGIN_DIR . 'extensions/extended-blocks/*' );
776
777
			foreach ( $extended_blocks as $block ) {
778
				$name = basename( $block );
779
				$path = JETPACK__PLUGIN_DIR . 'extensions/extended-blocks/' . $name . '/' . $name . '.php';
780
781
				if ( file_exists( $path ) ) {
782
					include_once $path;
783
				}
784
			}
785
		}
786
	}
787
788
	/**
789
	 * Get CSS classes for a block.
790
	 *
791
	 * @since 7.7.0
792
	 *
793
	 * @param string $slug  Block slug.
794
	 * @param array  $attr  Block attributes.
795
	 * @param array  $extra Potential extra classes you may want to provide.
796
	 *
797
	 * @return string $classes List of CSS classes for a block.
798
	 */
799
	public static function block_classes( $slug, $attr, $extra = array() ) {
800
		_deprecated_function( __METHOD__, '9.0.0', 'Automattic\\Jetpack\\Blocks::classes' );
801
		return Blocks::classes( $slug, $attr, $extra );
802
	}
803
804
	/**
805
	 * Determine whether a site should use the default set of blocks, or a custom set.
806
	 * Possible variations are currently beta, experimental, and production.
807
	 *
808
	 * @since 8.1.0
809
	 *
810
	 * @return string $block_varation production|beta|experimental
811
	 */
812
	public static function blocks_variation() {
813
		// Default to production blocks.
814
		$block_varation = 'production';
815
816
		if ( Constants::is_true( 'JETPACK_BETA_BLOCKS' ) ) {
817
			$block_varation = 'beta';
818
		}
819
820
		/*
821
		 * Switch to experimental blocks if you use the JETPACK_EXPERIMENTAL_BLOCKS constant.
822
		 */
823
		if ( Constants::is_true( 'JETPACK_EXPERIMENTAL_BLOCKS' ) ) {
824
			$block_varation = 'experimental';
825
		}
826
827
		/**
828
		 * Allow customizing the variation of blocks in use on a site.
829
		 *
830
		 * @since 8.1.0
831
		 *
832
		 * @param string $block_variation Can be beta, experimental, and production. Defaults to production.
833
		 */
834
		return apply_filters( 'jetpack_blocks_variation', $block_varation );
835
	}
836
837
	/**
838
	 * Get a list of extensions available for the variation you chose.
839
	 *
840
	 * @since 8.1.0
841
	 *
842
	 * @param obj    $preset_extensions_manifest List of extensions available in Jetpack.
843
	 * @param string $blocks_variation           Subset of blocks. production|beta|experimental.
844
	 *
845
	 * @return array $preset_extensions Array of extensions for that variation
846
	 */
847
	public static function get_extensions_preset_for_variation( $preset_extensions_manifest, $blocks_variation ) {
848
		$preset_extensions = isset( $preset_extensions_manifest->{ $blocks_variation } )
849
				? (array) $preset_extensions_manifest->{ $blocks_variation }
850
				: array();
851
852
		/*
853
		 * Experimental and Beta blocks need the production blocks as well.
854
		 */
855 View Code Duplication
		if (
856
			'experimental' === $blocks_variation
857
			|| 'beta' === $blocks_variation
858
		) {
859
			$production_extensions = isset( $preset_extensions_manifest->production )
860
				? (array) $preset_extensions_manifest->production
861
				: array();
862
863
			$preset_extensions = array_unique( array_merge( $preset_extensions, $production_extensions ) );
864
		}
865
866
		/*
867
		 * Beta blocks need the experimental blocks as well.
868
		 *
869
		 * If you've chosen to see Beta blocks,
870
		 * we want to make all blocks available to you:
871
		 * - Production
872
		 * - Experimental
873
		 * - Beta
874
		 */
875 View Code Duplication
		if ( 'beta' === $blocks_variation ) {
876
			$production_extensions = isset( $preset_extensions_manifest->experimental )
877
				? (array) $preset_extensions_manifest->experimental
878
				: array();
879
880
			$preset_extensions = array_unique( array_merge( $preset_extensions, $production_extensions ) );
881
		}
882
883
		return $preset_extensions;
884
	}
885
886
	/**
887
	 * Validate a URL used in a SSR block.
888
	 *
889
	 * @since 8.3.0
890
	 *
891
	 * @param string $url      URL saved as an attribute in block.
892
	 * @param array  $allowed  Array of allowed hosts for that block, or regexes to check against.
893
	 * @param bool   $is_regex Array of regexes matching the URL that could be used in block.
894
	 *
895
	 * @return bool|string
896
	 */
897
	public static function validate_block_embed_url( $url, $allowed = array(), $is_regex = false ) {
898
		if (
899
			empty( $url )
900
			|| ! is_array( $allowed )
901
			|| empty( $allowed )
902
		) {
903
			return false;
904
		}
905
906
		$url_components = wp_parse_url( $url );
907
908
		// Bail early if we cannot find a host.
909
		if ( empty( $url_components['host'] ) ) {
910
			return false;
911
		}
912
913
		// Normalize URL.
914
		$url = sprintf(
915
			'%s://%s%s%s',
916
			isset( $url_components['scheme'] ) ? $url_components['scheme'] : 'https',
917
			$url_components['host'],
918
			isset( $url_components['path'] ) ? $url_components['path'] : '/',
919
			isset( $url_components['query'] ) ? '?' . $url_components['query'] : ''
920
		);
921
922
		if ( ! empty( $url_components['fragment'] ) ) {
923
			$url = $url . '#' . rawurlencode( $url_components['fragment'] );
924
		}
925
926
		/*
927
		 * If we're using an allowed list of hosts,
928
		 * check if the URL belongs to one of the domains allowed for that block.
929
		 */
930
		if (
931
			false === $is_regex
932
			&& in_array( $url_components['host'], $allowed, true )
933
		) {
934
			return $url;
935
		}
936
937
		/*
938
		 * If we are using an array of regexes to check against,
939
		 * loop through that.
940
		 */
941
		if ( true === $is_regex ) {
942
			foreach ( $allowed as $regex ) {
943
				if ( 1 === preg_match( $regex, $url ) ) {
944
					return $url;
945
				}
946
			}
947
		}
948
949
		return false;
950
	}
951
952
	/**
953
	 * Determines whether a preview of the block with an upgrade nudge should
954
	 * be displayed for admins on the site frontend.
955
	 *
956
	 * @since 8.4.0
957
	 *
958
	 * @param array $availability_for_block The availability for the block.
959
	 *
960
	 * @return bool
961
	 */
962
	public static function should_show_frontend_preview( $availability_for_block ) {
963
		return (
964
			isset( $availability_for_block['details']['required_plan'] )
965
			&& current_user_can( 'manage_options' )
966
			&& ! is_feed()
967
		);
968
	}
969
970
	/**
971
	 * Output an UpgradeNudge Component on the frontend of a site.
972
	 *
973
	 * @since 8.4.0
974
	 *
975
	 * @param string $plan The plan that users need to purchase to make the block work.
976
	 *
977
	 * @return string
978
	 */
979
	public static function upgrade_nudge( $plan ) {
980
		jetpack_require_lib( 'components' );
981
		return Jetpack_Components::render_upgrade_nudge(
982
			array(
983
				'plan' => $plan,
984
			)
985
		);
986
	}
987
988
	/**
989
	 * Output a notice within a block.
990
	 *
991
	 * @since 8.6.0
992
	 *
993
	 * @param string $message Notice we want to output.
994
	 * @param string $status  Status of the notice. Can be one of success, info, warning, error. info by default.
995
	 * @param string $classes List of CSS classes.
996
	 *
997
	 * @return string
998
	 */
999
	public static function notice( $message, $status = 'info', $classes = '' ) {
1000
		if (
1001
			empty( $message )
1002
			|| ! in_array( $status, array( 'success', 'info', 'warning', 'error' ), true )
1003
		) {
1004
			return '';
1005
		}
1006
1007
		$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...
1008
		switch ( $status ) {
1009
			case 'success':
1010
				$color = '#46b450';
1011
				break;
1012
			case 'warning':
1013
				$color = '#ffb900';
1014
				break;
1015
			case 'error':
1016
				$color = '#dc3232';
1017
				break;
1018
			case 'info':
1019
			default:
1020
				$color = '#00a0d2';
1021
				break;
1022
		}
1023
1024
		return sprintf(
1025
			'<div class="jetpack-block__notice %1$s %3$s" style="border-left:5px solid %4$s;padding:1em;background-color:#f8f9f9;">%2$s</div>',
1026
			esc_attr( $status ),
1027
			wp_kses(
1028
				$message,
1029
				array(
1030
					'br' => array(),
1031
					'p'  => array(),
1032
				)
1033
			),
1034
			esc_attr( $classes ),
1035
			sanitize_hex_color( $color )
1036
		);
1037
	}
1038
1039
	/**
1040
	 * Set the availability of the block as the editor
1041
	 * is loaded.
1042
	 *
1043
	 * @param string $slug Slug of the block.
1044
	 */
1045
	public static function set_availability_for_plan( $slug ) {
1046
		$is_available = true;
1047
		$plan         = '';
1048
		$slug         = self::remove_extension_prefix( $slug );
1049
1050
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
1051
			if ( ! class_exists( 'Store_Product_List' ) ) {
1052
				require WP_CONTENT_DIR . '/admin-plugins/wpcom-billing/store-product-list.php';
1053
			}
1054
			$features_data = Store_Product_List::get_site_specific_features_data();
1055
			$is_available  = in_array( $slug, $features_data['active'], true );
1056
			if ( ! empty( $features_data['available'][ $slug ] ) ) {
1057
				$plan = $features_data['available'][ $slug ][0];
1058
			}
1059
		} elseif ( ! jetpack_is_atomic_site() ) {
1060
			/*
1061
			 * If it's Atomic then assume all features are available
1062
			 * otherwise check against the Jetpack plan.
1063
			 */
1064
			$is_available = Jetpack_Plan::supports( $slug );
1065
			$plan         = Jetpack_Plan::get_minimum_plan_for_feature( $slug );
1066
		}
1067
		if ( $is_available ) {
1068
			self::set_extension_available( $slug );
1069
		} else {
1070
			self::set_extension_unavailable(
1071
				$slug,
1072
				'missing_plan',
1073
				array(
1074
					'required_feature' => $slug,
1075
					'required_plan'    => $plan,
1076
				)
1077
			);
1078
		}
1079
	}
1080
1081
	/**
1082
	 * Wraps the suplied render_callback in a function to check
1083
	 * the availability of the block before rendering it.
1084
	 *
1085
	 * @param string   $slug The block slug, used to check for availability.
1086
	 * @param callable $render_callback The render_callback that will be called if the block is available.
1087
	 */
1088
	public static function get_render_callback_with_availability_check( $slug, $render_callback ) {
1089
		return function ( $prepared_attributes, $block_content ) use ( $render_callback, $slug ) {
1090
			$availability = self::get_cached_availability();
1091
			$bare_slug    = self::remove_extension_prefix( $slug );
1092
			if ( isset( $availability[ $bare_slug ] ) && $availability[ $bare_slug ]['available'] ) {
1093
				return call_user_func( $render_callback, $prepared_attributes, $block_content );
1094
			}
1095
1096
			// A preview of the block is rendered for admins on the frontend with an upgrade nudge.
1097
			if (
1098
				isset( $availability[ $bare_slug ] ) &&
1099
				self::should_show_frontend_preview( $availability[ $bare_slug ] )
1100
			) {
1101
				$upgrade_nudge = self::upgrade_nudge( $availability[ $bare_slug ]['details']['required_plan'] );
1102
				$block_preview = call_user_func( $render_callback, $prepared_attributes, $block_content );
1103
				return $upgrade_nudge . $block_preview;
1104
			}
1105
1106
			return null;
1107
		};
1108
	}
1109
}
1110