Completed
Push — add/ratings ( 93b123...83fca1 )
by Andrés
34:53 queued 28:17
created

Jetpack_WPCOM_Block_Editor::enqueue_scripts()   B

Complexity

Conditions 6
Paths 24

Size

Total Lines 78

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 24
nop 0
dl 0
loc 78
rs 7.8577
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * WordPress.com Block Editor
4
 * Allow new block editor posts to be composed on WordPress.com.
5
 * This is auto-loaded as of Jetpack v7.4 for sites connected to WordPress.com only.
6
 *
7
 * @package Jetpack
8
 */
9
10
/**
11
 * WordPress.com Block editor for Jetpack
12
 */
13
class Jetpack_WPCOM_Block_Editor {
14
	/**
15
	 * ID of the user who signed the nonce.
16
	 *
17
	 * @var int
18
	 */
19
	private $nonce_user_id;
20
21
	/**
22
	 * Singleton
23
	 */
24
	public static function init() {
25
		static $instance = false;
26
27
		if ( ! $instance ) {
28
			$instance = new Jetpack_WPCOM_Block_Editor();
29
		}
30
31
		return $instance;
32
	}
33
34
	/**
35
	 * Jetpack_WPCOM_Block_Editor constructor.
36
	 */
37
	private function __construct() {
38
		if ( $this->is_iframed_block_editor() ) {
39
			add_action( 'admin_init', array( $this, 'disable_send_frame_options_header' ), 9 );
40
			add_filter( 'admin_body_class', array( $this, 'add_iframed_body_class' ) );
41
		}
42
43
		add_action( 'login_init', array( $this, 'allow_block_editor_login' ), 1 );
44
		add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_scripts' ), 9 );
45
		add_filter( 'mce_external_plugins', array( $this, 'add_tinymce_plugins' ) );
46
	}
47
48
	/**
49
	 * Checks if we are embedding the block editor in an iframe in WordPress.com.
50
	 *
51
	 * @return bool Whether the current request is from the iframed block editor.
52
	 */
53
	public function is_iframed_block_editor() {
54
		global $pagenow;
55
56
		// phpcs:ignore WordPress.Security.NonceVerification
57
		return ( 'post.php' === $pagenow || 'post-new.php' === $pagenow ) && ! empty( $_GET['frame-nonce'] );
58
	}
59
60
	/**
61
	 * Prevents frame options header from firing if this is a whitelisted iframe request.
62
	 */
63
	public function disable_send_frame_options_header() {
64
		// phpcs:ignore WordPress.Security.NonceVerification
65
		if ( $this->framing_allowed( $_GET['frame-nonce'] ) ) {
66
			remove_action( 'admin_init', 'send_frame_options_header' );
67
		}
68
	}
69
70
	/**
71
	 * Adds custom admin body class if this is a whitelisted iframe request.
72
	 *
73
	 * @param string $classes Admin body classes.
74
	 * @return string
75
	 */
76
	public function add_iframed_body_class( $classes ) {
77
		// phpcs:ignore WordPress.Security.NonceVerification
78
		if ( $this->framing_allowed( $_GET['frame-nonce'] ) ) {
79
			$classes .= ' is-iframed ';
80
		}
81
82
		return $classes;
83
	}
84
85
	/**
86
	 * Allows to iframe the login page if a user is logged out
87
	 * while trying to access the block editor from wordpress.com.
88
	 */
89
	public function allow_block_editor_login() {
90
		// phpcs:ignore WordPress.Security.NonceVerification
91
		if ( empty( $_REQUEST['redirect_to'] ) ) {
92
			return;
93
		}
94
95
		// phpcs:ignore WordPress.Security.NonceVerification
96
		$query = wp_parse_url( urldecode( $_REQUEST['redirect_to'] ), PHP_URL_QUERY );
97
		$args  = wp_parse_args( $query );
98
99
		// Check nonce and make sure this is a Gutenframe request.
100
		if ( ! empty( $args['frame-nonce'] ) && $this->framing_allowed( $args['frame-nonce'] ) ) {
101
102
			// If SSO is active, we'll let WordPress.com handle authentication...
103
			if ( Jetpack::is_module_active( 'sso' ) ) {
104
				// ...but only if it's not an Atomic site. They already do that.
105
				if ( ! jetpack_is_atomic_site() ) {
106
					add_filter( 'jetpack_sso_bypass_login_forward_wpcom', '__return_true' );
107
				}
108
			} else {
109
				$_REQUEST['interim-login'] = true;
110
				add_action( 'wp_login', array( $this, 'do_redirect' ) );
111
				add_action( 'login_form', array( $this, 'add_login_html' ) );
112
				add_filter( 'wp_login_errors', array( $this, 'add_login_message' ) );
113
				remove_action( 'login_init', 'send_frame_options_header' );
114
				wp_add_inline_style( 'login', '.interim-login #login{padding-top:8%}' );
115
			}
116
		}
117
	}
118
119
	/**
120
	 * Adds a login message.
121
	 *
122
	 * Intended to soften the expectation mismatch of ending up with a login screen rather than the editor.
123
	 *
124
	 * @param WP_Error $errors WP Error object.
125
	 * @return \WP_Error
126
	 */
127
	public function add_login_message( $errors ) {
128
		$errors->remove( 'expired' );
0 ignored issues
show
Bug introduced by
The method remove() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
129
		$errors->add( 'info', __( 'Before we continue, please log in to your Jetpack site.', 'jetpack' ), 'message' );
0 ignored issues
show
Bug introduced by
The method add() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
130
131
		return $errors;
132
	}
133
134
	/**
135
	 * Maintains the `redirect_to` parameter in login form links.
136
	 * Adds visual feedback of login in progress.
137
	 */
138
	public function add_login_html() {
139
		?>
140
		<input type="hidden" name="redirect_to" value="<?php echo esc_url( $_REQUEST['redirect_to'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>" />
141
		<script type="application/javascript">
142
			document.getElementById( 'loginform' ).addEventListener( 'submit' , function() {
143
				document.getElementById( 'wp-submit' ).setAttribute( 'disabled', 'disabled' );
144
				document.getElementById( 'wp-submit' ).value = '<?php echo esc_js( __( 'Logging In...', 'jetpack' ) ); ?>';
145
			} );
146
		</script>
147
		<?php
148
	}
149
150
	/**
151
	 * Does the redirect to the block editor.
152
	 */
153
	public function do_redirect() {
154
		wp_safe_redirect( $GLOBALS['redirect_to'] );
155
		exit;
156
	}
157
158
	/**
159
	 * Checks whether this is a whitelisted iframe request.
160
	 *
161
	 * @param string $nonce Nonce to verify.
162
	 * @return bool
163
	 */
164
	public function framing_allowed( $nonce ) {
165
		$verified = $this->verify_frame_nonce( $nonce, 'frame-' . Jetpack_Options::get_option( 'id' ) );
166
167
		if ( is_wp_error( $verified ) ) {
168
			wp_die( $verified ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
169
		}
170
171
		if ( $verified && ! defined( 'IFRAME_REQUEST' ) ) {
172
			define( 'IFRAME_REQUEST', true );
173
		}
174
175
		return (bool) $verified;
176
	}
177
178
	/**
179
	 * Verify that correct nonce was used with time limit.
180
	 *
181
	 * The user is given an amount of time to use the token, so therefore, since the
182
	 * UID and $action remain the same, the independent variable is the time.
183
	 *
184
	 * @param string $nonce Nonce that was used in the form to verify.
185
	 * @param string $action Should give context to what is taking place and be the same when nonce was created.
186
	 * @return boolean|WP_Error Whether the nonce is valid.
187
	 */
188
	public function verify_frame_nonce( $nonce, $action ) {
189
		if ( empty( $nonce ) ) {
190
			return false;
191
		}
192
193
		list( $expiration, $user_id, $hash ) = explode( ':', $nonce, 3 );
194
195
		$this->nonce_user_id = (int) $user_id;
196
		if ( ! $this->nonce_user_id ) {
197
			return false;
198
		}
199
200
		$token = Jetpack_Data::get_access_token( $this->nonce_user_id );
0 ignored issues
show
Documentation introduced by
$this->nonce_user_id is of type integer, but the function expects a boolean.

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...
Deprecated Code introduced by
The method Jetpack_Data::get_access_token() has been deprecated with message: 7.5 Use Connection_Manager 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...
201
		if ( ! $token ) {
202
			return false;
203
		}
204
205
		/*
206
		 * Failures must return `false` (blocking the iframe) prior to the
207
		 * signature verification.
208
		 */
209
210
		add_filter( 'salt', array( $this, 'filter_salt' ), 10, 2 );
211
		$expected_hash = wp_hash( "$expiration|$action|{$this->nonce_user_id}", 'jetpack_frame_nonce' );
212
		remove_filter( 'salt', array( $this, 'filter_salt' ) );
213
214
		if ( ! hash_equals( $hash, $expected_hash ) ) {
215
			return false;
216
		}
217
218
		/*
219
		 * Failures may return `WP_Error` (showing an error in the iframe) after the
220
		 * signature verification passes.
221
		 */
222
223
		if ( time() > $expiration ) {
224
			return new WP_Error( 'nonce_invalid_expired', 'Expired nonce.', array( 'status' => 401 ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'nonce_invalid_expired'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
225
		}
226
227
		// Check if it matches the current user, unless they're trying to log in.
228
		if ( get_current_user_id() !== $this->nonce_user_id && ! doing_action( 'login_init' ) ) {
229
			return new WP_Error( 'nonce_invalid_user_mismatch', 'User ID mismatch.', array( 'status' => 401 ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'nonce_invalid_user_mismatch'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
230
		}
231
232
		return true;
233
	}
234
235
	/**
236
	 * Filters the WordPress salt.
237
	 *
238
	 * @param string $salt Salt for the given scheme.
239
	 * @param string $scheme Authentication scheme.
240
	 * @return string
241
	 */
242
	public function filter_salt( $salt, $scheme ) {
243
		if ( 'jetpack_frame_nonce' === $scheme ) {
244
			$token = Jetpack_Data::get_access_token( $this->nonce_user_id );
0 ignored issues
show
Documentation introduced by
$this->nonce_user_id is of type integer, but the function expects a boolean.

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...
Deprecated Code introduced by
The method Jetpack_Data::get_access_token() has been deprecated with message: 7.5 Use Connection_Manager 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...
245
246
			if ( $token ) {
247
				$salt = $token->secret;
248
			}
249
		}
250
251
		return $salt;
252
	}
253
254
	/**
255
	 * Enqueue the scripts for the WordPress.com block editor integration.
256
	 */
257
	public function enqueue_scripts() {
258
		$debug   = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG;
259
		$version = gmdate( 'Ymd' );
260
261
		$src_common = $debug
262
			? '//widgets.wp.com/wpcom-block-editor/common.js?minify=false'
263
			: '//widgets.wp.com/wpcom-block-editor/common.min.js';
264
265
		wp_enqueue_script(
266
			'wpcom-block-editor-common',
267
			$src_common,
268
			array(
269
				'jquery',
270
				'lodash',
271
				'wp-blocks',
272
				'wp-compose',
273
				'wp-data',
274
				'wp-dom-ready',
275
				'wp-editor',
276
				'wp-nux',
277
				'wp-plugins',
278
				'wp-polyfill',
279
				'wp-rich-text',
280
			),
281
			$version,
282
			true
283
		);
284
		wp_localize_script(
285
			'wpcom-block-editor-common',
286
			'wpcomGutenberg',
287
			array(
288
				'switchToClassic' => array(
289
					'isVisible' => $this->is_iframed_block_editor(),
290
					'label'     => __( 'Switch to Classic Editor', 'jetpack' ),
291
					'url'       => Jetpack_Calypsoify::getInstance()->get_switch_to_classic_editor_url(),
292
				),
293
				'richTextToolbar' => array(
294
					'justify'   => __( 'Justify', 'jetpack' ),
295
					'underline' => __( 'Underline', 'jetpack' ),
296
				),
297
			)
298
		);
299
300
		$src_styles = $debug
301
			? '//widgets.wp.com/wpcom-block-editor/common.css?minify=false'
302
			: '//widgets.wp.com/wpcom-block-editor/common.min.css';
303
		wp_enqueue_style(
304
			'wpcom-block-editor-styles',
305
			$src_styles,
306
			array(),
307
			$version
308
		);
309
310
		if ( $this->is_iframed_block_editor() ) {
311
			$src_calypso_iframe_bridge = $debug
312
				? '//widgets.wp.com/wpcom-block-editor/calypso-iframe-bridge-server.js?minify=false'
313
				: '//widgets.wp.com/wpcom-block-editor/calypso-iframe-bridge-server.min.js';
314
315
			wp_enqueue_script(
316
				'wpcom-block-editor-calypso-iframe-bridge',
317
				$src_calypso_iframe_bridge,
318
				array(
319
					'calypsoify_wpadminmods_js',
320
					'jquery',
321
					'lodash',
322
					'react',
323
					'wp-blocks',
324
					'wp-data',
325
					'wp-hooks',
326
					'wp-polyfill',
327
					'wp-tinymce',
328
					'wp-url',
329
				),
330
				$version,
331
				true
332
			);
333
		}
334
	}
335
336
	/**
337
	 * Register the Tiny MCE plugins for the WordPress.com block editor integration.
338
	 *
339
	 * @param array $plugin_array An array of external Tiny MCE plugins.
340
	 * @return array External TinyMCE plugins.
341
	 */
342
	public function add_tinymce_plugins( $plugin_array ) {
343
		if ( $this->is_iframed_block_editor() ) {
344
			$debug               = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG;
345
			$src_calypso_tinymce = $debug
346
				? '//widgets.wp.com/wpcom-block-editor/calypso-tinymce.js?minify=false'
347
				: '//widgets.wp.com/wpcom-block-editor/calypso-tinymce.min.js';
348
349
			$plugin_array['gutenberg-wpcom-iframe-media-modal'] = add_query_arg(
350
				'v',
351
				gmdate( 'YW' ),
352
				$src_calypso_tinymce
353
			);
354
		}
355
356
		return $plugin_array;
357
	}
358
}
359
360
Jetpack_WPCOM_Block_Editor::init();
361