Completed
Push — renovate/webpack-cli-3.x ( 961c1b...02322b )
by
unknown
06:55
created

Jetpack_WPCOM_Block_Editor::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
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( 'init', array( $this, 'show_error_if_logged_out' ) );
40
			add_action( 'admin_init', array( $this, 'disable_send_frame_options_header' ), 9 );
41
			add_filter( 'admin_body_class', array( $this, 'add_iframed_body_class' ) );
42
		}
43
44
		add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_scripts' ) );
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
	 * Shows a custom message if the user is logged out.
62
	 *
63
	 * The iframed block editor can be only embedded in WordPress.com if the user is logged
64
	 * into the Jetpack site. So we abort the default redirection to the login page (which
65
	 * cannot be embedded in a iframe) and instead we explain that we need the user to log
66
	 * into Jetpack.
67
	 */
68
	public function show_error_if_logged_out() {
69
		if ( ! get_current_user_id() ) {
70
			/* translators: %s: Login URL */
71
			$message = __( 'Please <a href="%s" target="_blank" rel="noopener noreferrer">log into</a> your Jetpack-connected site to use the block editor on WordPress.com.', 'jetpack' );
72
73
			wp_die(
74
				sprintf( wp_kses_post( $message ), esc_url( wp_login_url() ) ),
75
				'',
76
				array( 'response' => 401 )
77
			);
78
		}
79
	}
80
81
	/**
82
	 * Prevents frame options header from firing if this is a whitelisted iframe request.
83
	 */
84
	public function disable_send_frame_options_header() {
85
		if ( $this->framing_allowed() ) {
86
			remove_action( 'admin_init', 'send_frame_options_header' );
87
		}
88
	}
89
90
	/**
91
	 * Adds custom admin body class if this is a whitelisted iframe request.
92
	 *
93
	 * @param string $classes Admin body classes.
94
	 * @return string
95
	 */
96
	public function add_iframed_body_class( $classes ) {
97
		if ( $this->framing_allowed() ) {
98
			$classes .= ' is-iframed ';
99
		}
100
101
		return $classes;
102
	}
103
104
	/**
105
	 * Checks whether this is a whitelisted iframe request.
106
	 *
107
	 * @return bool
108
	 */
109
	public function framing_allowed() {
110
		$verified = $this->verify_frame_nonce(
111
			$_GET['frame-nonce'], // phpcs:ignore WordPress.Security.NonceVerification
112
			'frame-' . Jetpack_Options::get_option( 'id' )
113
		);
114
115
		if ( is_wp_error( $verified ) ) {
116
			wp_die( $verified ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
117
		}
118
119
		if ( $verified && ! defined( 'IFRAME_REQUEST' ) ) {
120
			define( 'IFRAME_REQUEST', true );
121
		}
122
123
		return (bool) $verified;
124
	}
125
126
	/**
127
	 * Verify that correct nonce was used with time limit.
128
	 *
129
	 * The user is given an amount of time to use the token, so therefore, since the
130
	 * UID and $action remain the same, the independent variable is the time.
131
	 *
132
	 * @param string $nonce Nonce that was used in the form to verify.
133
	 * @param string $action Should give context to what is taking place and be the same when nonce was created.
134
	 * @return boolean|WP_Error Whether the nonce is valid.
135
	 */
136
	public function verify_frame_nonce( $nonce, $action ) {
137
		if ( empty( $nonce ) ) {
138
			return false;
139
		}
140
141
		list( $expiration, $user_id, $hash ) = explode( ':', $nonce, 3 );
142
143
		$this->nonce_user_id = (int) $user_id;
144
		if ( ! $this->nonce_user_id ) {
145
			return false;
146
		}
147
148
		$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...
149
		if ( ! $token ) {
150
			return false;
151
		}
152
153
		/*
154
		 * Failures must return `false` (blocking the iframe) prior to the
155
		 * signature verification.
156
		 */
157
158
		add_filter( 'salt', array( $this, 'filter_salt' ), 10, 2 );
159
		$expected_hash = wp_hash( "$expiration|$action|{$this->nonce_user_id}", 'jetpack_frame_nonce' );
160
		remove_filter( 'salt', array( $this, 'filter_salt' ) );
161
162
		if ( ! hash_equals( $hash, $expected_hash ) ) {
163
			return false;
164
		}
165
166
		/*
167
		 * Failures may return `WP_Error` (showing an error in the iframe) after the
168
		 * signature verification passes.
169
		 */
170
171
		if ( time() > $expiration ) {
172
			return new WP_Error( 'nonce_invalid_expired', 'Expired nonce.', array( 'status' => 401 ) );
173
		}
174
175
		if ( get_current_user_id() !== $this->nonce_user_id ) {
176
			return new WP_Error( 'nonce_invalid_user_mismatch', 'User ID mismatch.', array( 'status' => 401 ) );
177
		}
178
179
		return true;
180
	}
181
182
	/**
183
	 * Filters the WordPress salt.
184
	 *
185
	 * @param string $salt Salt for the given scheme.
186
	 * @param string $scheme Authentication scheme.
187
	 * @return string
188
	 */
189
	public function filter_salt( $salt, $scheme ) {
190
		if ( 'jetpack_frame_nonce' === $scheme ) {
191
			$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...
192
193
			if ( $token ) {
194
				$salt = $token->secret;
195
			}
196
		}
197
198
		return $salt;
199
	}
200
201
	/**
202
	 * Enqueue the scripts for the WordPress.com block editor integration.
203
	 */
204
	public function enqueue_scripts() {
205
		$debug   = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG;
206
		$version = gmdate( 'Ymd' );
207
208
		$src_common = $debug
209
			? '//widgets.wp.com/wpcom-block-editor/common.js?minify=false'
210
			: '//widgets.wp.com/wpcom-block-editor/common.min.js';
211
212
		wp_enqueue_script(
213
			'wpcom-block-editor-common',
214
			$src_common,
215
			array( 'lodash', 'wp-compose', 'wp-data', 'wp-editor', 'wp-rich-text' ),
216
			$version,
217
			true
218
		);
219
		wp_localize_script(
220
			'wpcom-block-editor-common',
221
			'wpcomGutenberg',
222
			array(
223
				'switchToClassic' => array(
224
					'isVisible' => $this->is_iframed_block_editor(),
225
					'label'     => __( 'Switch to Classic Editor', 'jetpack' ),
226
					'url'       => Jetpack_Calypsoify::getInstance()->get_switch_to_classic_editor_url(),
227
				),
228
				'richTextToolbar' => array(
229
					'justify'   => __( 'Justify', 'jetpack' ),
230
					'underline' => __( 'Underline', 'jetpack' ),
231
				),
232
			)
233
		);
234
235
		if ( $this->is_iframed_block_editor() ) {
236
			$src_calypso_iframe_bridge = $debug
237
				? '//widgets.wp.com/wpcom-block-editor/calypso-iframe-bridge-server.js?minify=false'
238
				: '//widgets.wp.com/wpcom-block-editor/calypso-iframe-bridge-server.min.js';
239
240
			wp_enqueue_script(
241
				'wpcom-block-editor-calypso-iframe-bridge',
242
				$src_calypso_iframe_bridge,
243
				array( 'calypsoify_wpadminmods_js', 'jquery', 'lodash', 'react', 'wp-blocks', 'wp-data', 'wp-hooks', 'wp-tinymce', 'wp-url' ),
244
				$version,
245
				true
246
			);
247
		}
248
	}
249
250
	/**
251
	 * Register the Tiny MCE plugins for the WordPress.com block editor integration.
252
	 *
253
	 * @param array $plugin_array An array of external Tiny MCE plugins.
254
	 * @return array External TinyMCE plugins.
255
	 */
256
	public function add_tinymce_plugins( $plugin_array ) {
257
		if ( $this->is_iframed_block_editor() ) {
258
			$debug               = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG;
259
			$src_calypso_tinymce = $debug
260
				? '//widgets.wp.com/wpcom-block-editor/calypso-tinymce.js?minify=false'
261
				: '//widgets.wp.com/wpcom-block-editor/calypso-tinymce.min.js';
262
263
			$plugin_array['gutenberg-wpcom-iframe-media-modal'] = add_query_arg(
264
				'v',
265
				gmdate( 'YW' ),
266
				$src_calypso_tinymce
267
			);
268
		}
269
270
		return $plugin_array;
271
	}
272
}
273
274
Jetpack_WPCOM_Block_Editor::init();
275