Completed
Push — instant-search-master ( 8be3b4...336413 )
by
unknown
06:37 queued 10s
created

Jetpack_WPCOM_Block_Editor   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 382
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
dl 0
loc 382
rs 7.92
c 0
b 0
f 0
wmc 51
lcom 1
cbo 5

16 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 9 2
A __construct() 0 11 2
A is_iframed_block_editor() 0 6 3
A disable_send_frame_options_header() 0 6 2
A add_iframed_body_class() 0 8 2
B allow_block_editor_login() 0 29 6
A add_login_message() 0 6 1
A add_login_html() 0 11 1
A do_redirect() 0 4 1
A framing_allowed() 0 13 4
B verify_frame_nonce() 0 46 8
A filter_salt() 0 11 3
B enqueue_block_editor_assets() 0 87 8
A enqueue_block_assets() 0 5 1
A has_justified_block() 0 12 3
A add_tinymce_plugins() 0 15 4

How to fix   Complexity   

Complex Class

Complex classes like Jetpack_WPCOM_Block_Editor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Jetpack_WPCOM_Block_Editor, and based on these observations, apply Extract Interface, too.

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_block_editor_assets' ), 9 );
45
		add_action( 'enqueue_block_assets', array( $this, 'enqueue_block_assets' ) );
46
		add_filter( 'mce_external_plugins', array( $this, 'add_tinymce_plugins' ) );
47
	}
48
49
	/**
50
	 * Checks if we are embedding the block editor in an iframe in WordPress.com.
51
	 *
52
	 * @return bool Whether the current request is from the iframed block editor.
53
	 */
54
	public function is_iframed_block_editor() {
55
		global $pagenow;
56
57
		// phpcs:ignore WordPress.Security.NonceVerification
58
		return ( 'post.php' === $pagenow || 'post-new.php' === $pagenow ) && ! empty( $_GET['frame-nonce'] );
59
	}
60
61
	/**
62
	 * Prevents frame options header from firing if this is a whitelisted iframe request.
63
	 */
64
	public function disable_send_frame_options_header() {
65
		// phpcs:ignore WordPress.Security.NonceVerification
66
		if ( $this->framing_allowed( $_GET['frame-nonce'] ) ) {
67
			remove_action( 'admin_init', 'send_frame_options_header' );
68
		}
69
	}
70
71
	/**
72
	 * Adds custom admin body class if this is a whitelisted iframe request.
73
	 *
74
	 * @param string $classes Admin body classes.
75
	 * @return string
76
	 */
77
	public function add_iframed_body_class( $classes ) {
78
		// phpcs:ignore WordPress.Security.NonceVerification
79
		if ( $this->framing_allowed( $_GET['frame-nonce'] ) ) {
80
			$classes .= ' is-iframed ';
81
		}
82
83
		return $classes;
84
	}
85
86
	/**
87
	 * Allows to iframe the login page if a user is logged out
88
	 * while trying to access the block editor from wordpress.com.
89
	 */
90
	public function allow_block_editor_login() {
91
		// phpcs:ignore WordPress.Security.NonceVerification
92
		if ( empty( $_REQUEST['redirect_to'] ) ) {
93
			return;
94
		}
95
96
		// phpcs:ignore WordPress.Security.NonceVerification
97
		$query = wp_parse_url( urldecode( $_REQUEST['redirect_to'] ), PHP_URL_QUERY );
98
		$args  = wp_parse_args( $query );
99
100
		// Check nonce and make sure this is a Gutenframe request.
101
		if ( ! empty( $args['frame-nonce'] ) && $this->framing_allowed( $args['frame-nonce'] ) ) {
102
103
			// If SSO is active, we'll let WordPress.com handle authentication...
104
			if ( Jetpack::is_module_active( 'sso' ) ) {
105
				// ...but only if it's not an Atomic site. They already do that.
106
				if ( ! jetpack_is_atomic_site() ) {
107
					add_filter( 'jetpack_sso_bypass_login_forward_wpcom', '__return_true' );
108
				}
109
			} else {
110
				$_REQUEST['interim-login'] = true;
111
				add_action( 'wp_login', array( $this, 'do_redirect' ) );
112
				add_action( 'login_form', array( $this, 'add_login_html' ) );
113
				add_filter( 'wp_login_errors', array( $this, 'add_login_message' ) );
114
				remove_action( 'login_init', 'send_frame_options_header' );
115
				wp_add_inline_style( 'login', '.interim-login #login{padding-top:8%}' );
116
			}
117
		}
118
	}
119
120
	/**
121
	 * Adds a login message.
122
	 *
123
	 * Intended to soften the expectation mismatch of ending up with a login screen rather than the editor.
124
	 *
125
	 * @param WP_Error $errors WP Error object.
126
	 * @return \WP_Error
127
	 */
128
	public function add_login_message( $errors ) {
129
		$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...
130
		$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...
131
132
		return $errors;
133
	}
134
135
	/**
136
	 * Maintains the `redirect_to` parameter in login form links.
137
	 * Adds visual feedback of login in progress.
138
	 */
139
	public function add_login_html() {
140
		?>
141
		<input type="hidden" name="redirect_to" value="<?php echo esc_url( $_REQUEST['redirect_to'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>" />
142
		<script type="application/javascript">
143
			document.getElementById( 'loginform' ).addEventListener( 'submit' , function() {
144
				document.getElementById( 'wp-submit' ).setAttribute( 'disabled', 'disabled' );
145
				document.getElementById( 'wp-submit' ).value = '<?php echo esc_js( __( 'Logging In...', 'jetpack' ) ); ?>';
146
			} );
147
		</script>
148
		<?php
149
	}
150
151
	/**
152
	 * Does the redirect to the block editor.
153
	 */
154
	public function do_redirect() {
155
		wp_safe_redirect( $GLOBALS['redirect_to'] );
156
		exit;
157
	}
158
159
	/**
160
	 * Checks whether this is a whitelisted iframe request.
161
	 *
162
	 * @param string $nonce Nonce to verify.
163
	 * @return bool
164
	 */
165
	public function framing_allowed( $nonce ) {
166
		$verified = $this->verify_frame_nonce( $nonce, 'frame-' . Jetpack_Options::get_option( 'id' ) );
167
168
		if ( is_wp_error( $verified ) ) {
169
			wp_die( $verified ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
170
		}
171
172
		if ( $verified && ! defined( 'IFRAME_REQUEST' ) ) {
173
			define( 'IFRAME_REQUEST', true );
174
		}
175
176
		return (bool) $verified;
177
	}
178
179
	/**
180
	 * Verify that correct nonce was used with time limit.
181
	 *
182
	 * The user is given an amount of time to use the token, so therefore, since the
183
	 * UID and $action remain the same, the independent variable is the time.
184
	 *
185
	 * @param string $nonce Nonce that was used in the form to verify.
186
	 * @param string $action Should give context to what is taking place and be the same when nonce was created.
187
	 * @return boolean|WP_Error Whether the nonce is valid.
188
	 */
189
	public function verify_frame_nonce( $nonce, $action ) {
190
		if ( empty( $nonce ) ) {
191
			return false;
192
		}
193
194
		list( $expiration, $user_id, $hash ) = explode( ':', $nonce, 3 );
195
196
		$this->nonce_user_id = (int) $user_id;
197
		if ( ! $this->nonce_user_id ) {
198
			return false;
199
		}
200
201
		$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...
202
		if ( ! $token ) {
203
			return false;
204
		}
205
206
		/*
207
		 * Failures must return `false` (blocking the iframe) prior to the
208
		 * signature verification.
209
		 */
210
211
		add_filter( 'salt', array( $this, 'filter_salt' ), 10, 2 );
212
		$expected_hash = wp_hash( "$expiration|$action|{$this->nonce_user_id}", 'jetpack_frame_nonce' );
213
		remove_filter( 'salt', array( $this, 'filter_salt' ) );
214
215
		if ( ! hash_equals( $hash, $expected_hash ) ) {
216
			return false;
217
		}
218
219
		/*
220
		 * Failures may return `WP_Error` (showing an error in the iframe) after the
221
		 * signature verification passes.
222
		 */
223
224
		if ( time() > $expiration ) {
225
			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...
226
		}
227
228
		// Check if it matches the current user, unless they're trying to log in.
229
		if ( get_current_user_id() !== $this->nonce_user_id && ! doing_action( 'login_init' ) ) {
230
			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...
231
		}
232
233
		return true;
234
	}
235
236
	/**
237
	 * Filters the WordPress salt.
238
	 *
239
	 * @param string $salt Salt for the given scheme.
240
	 * @param string $scheme Authentication scheme.
241
	 * @return string
242
	 */
243
	public function filter_salt( $salt, $scheme ) {
244
		if ( 'jetpack_frame_nonce' === $scheme ) {
245
			$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...
246
247
			if ( $token ) {
248
				$salt = $token->secret;
249
			}
250
		}
251
252
		return $salt;
253
	}
254
255
	/**
256
	 * Enqueues the WordPress.com block editor integration assets for the editor.
257
	 */
258
	public function enqueue_block_editor_assets() {
259
		$debug   = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG;
260
		$version = gmdate( 'Ymd' );
261
262
		wp_enqueue_script(
263
			'wpcom-block-editor-default-editor-script',
264
			$debug
265
				? '//widgets.wp.com/wpcom-block-editor/default.editor.js?minify=false'
266
				: '//widgets.wp.com/wpcom-block-editor/default.editor.min.js',
267
			array(
268
				'jquery',
269
				'lodash',
270
				'wp-compose',
271
				'wp-data',
272
				'wp-editor',
273
				'wp-element',
274
				'wp-rich-text',
275
			),
276
			$version,
277
			true
278
		);
279
280
		wp_localize_script(
281
			'wpcom-block-editor-default-editor-script',
282
			'wpcomGutenberg',
283
			array(
284
				'switchToClassic' => array(
285
					'isVisible' => $this->is_iframed_block_editor(),
286
					'label'     => __( 'Switch to Classic Editor', 'jetpack' ),
287
					'url'       => Jetpack_Calypsoify::getInstance()->get_switch_to_classic_editor_url(),
288
				),
289
				'richTextToolbar' => array(
290
					'justify'   => __( 'Justify', 'jetpack' ),
291
					'underline' => __( 'Underline', 'jetpack' ),
292
				),
293
			)
294
		);
295
296
		if ( jetpack_is_atomic_site() ) {
297
			wp_enqueue_script(
298
				'wpcom-block-editor-wpcom-editor-script',
299
				$debug
300
					? '//widgets.wp.com/wpcom-block-editor/wpcom.editor.js?minify=false'
301
					: '//widgets.wp.com/wpcom-block-editor/wpcom.editor.min.js',
302
				array(
303
					'lodash',
304
					'wp-blocks',
305
					'wp-data',
306
					'wp-dom-ready',
307
					'wp-plugins',
308
				),
309
				$version,
310
				true
311
			);
312
		}
313
314
		if ( $this->is_iframed_block_editor() ) {
315
			wp_enqueue_script(
316
				'wpcom-block-editor-calypso-editor-script',
317
				$debug
318
					? '//widgets.wp.com/wpcom-block-editor/calypso.editor.js?minify=false'
319
					: '//widgets.wp.com/wpcom-block-editor/calypso.editor.min.js',
320
				array(
321
					'calypsoify_wpadminmods_js',
322
					'jquery',
323
					'lodash',
324
					'react',
325
					'wp-blocks',
326
					'wp-data',
327
					'wp-hooks',
328
					'wp-tinymce',
329
					'wp-url',
330
				),
331
				$version,
332
				true
333
			);
334
335
			wp_enqueue_style(
336
				'wpcom-block-editor-calypso-editor-styles',
337
				$debug
338
					? '//widgets.wp.com/wpcom-block-editor/calypso.editor.css?minify=false'
339
					: '//widgets.wp.com/wpcom-block-editor/calypso.editor.min.css',
340
				array(),
341
				$version
342
			);
343
		}
344
	}
345
346
	/**
347
	 * Enqueues the WordPress.com block editor integration assets for both editor and front-end.
348
	 */
349
	public function enqueue_block_assets() {
350
		// These styles are manually copied from //widgets.wp.com/wpcom-block-editor/default.view.css in order to
351
		// improve the performance by avoiding an extra network request to download the CSS file on every page.
352
		wp_add_inline_style( 'wp-block-library', '.has-text-align-justify{text-align:justify;}' );
353
	}
354
355
	/**
356
	 * Determines if the current $post contains a justified paragraph block.
357
	 *
358
	 * @return boolean true if justified paragraph is found, false otherwise.
359
	 */
360
	public function has_justified_block() {
361
		global $post;
362
		if ( ! $post instanceof WP_Post ) {
0 ignored issues
show
Bug introduced by
The class WP_Post does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
363
			return false;
364
		};
365
366
		if ( ! has_blocks( $post ) ) {
367
			return false;
368
		}
369
370
		return false !== strpos( $post->post_content, '<!-- wp:paragraph {"align":"justify"' );
371
	}
372
373
	/**
374
	 * Register the Tiny MCE plugins for the WordPress.com block editor integration.
375
	 *
376
	 * @param array $plugin_array An array of external Tiny MCE plugins.
377
	 * @return array External TinyMCE plugins.
378
	 */
379
	public function add_tinymce_plugins( $plugin_array ) {
380
		if ( $this->is_iframed_block_editor() ) {
381
			$debug = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG;
382
383
			$plugin_array['gutenberg-wpcom-iframe-media-modal'] = add_query_arg(
384
				'v',
385
				gmdate( 'YW' ),
386
				$debug
387
					? '//widgets.wp.com/wpcom-block-editor/calypso.tinymce.js?minify=false'
388
					: '//widgets.wp.com/wpcom-block-editor/calypso.tinymce.min.js'
389
			);
390
		}
391
392
		return $plugin_array;
393
	}
394
}
395
396
Jetpack_WPCOM_Block_Editor::init();
397