Completed
Push — add/setup-wizard-site-income-q... ( 458557...1155d5 )
by
unknown
17:17 queued 10:10
created

Jetpack_Gutenberg::upgrade_nudge()   A

Complexity

Conditions 5
Paths 2

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

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