PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 13.5.2
Jetpack – WP Security, Backup, Speed, & Growth v13.5.2
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / modules / wpcom-block-editor / class-jetpack-wpcom-block-editor.php
jetpack / modules / wpcom-block-editor Last commit date
class-jetpack-wpcom-block-editor.php 2 years ago functions.editor-type.php 2 years ago
class-jetpack-wpcom-block-editor.php
631 lines
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 automattic/jetpack
8 */
9
10 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
11 use Automattic\Jetpack\Connection\Tokens;
12 use Automattic\Jetpack\Modules;
13 use Automattic\Jetpack\Status\Host;
14
15 /**
16 * WordPress.com Block editor for Jetpack
17 */
18 class Jetpack_WPCOM_Block_Editor {
19 /**
20 * ID of the user who signed the nonce.
21 *
22 * @var int
23 */
24 private $nonce_user_id;
25
26 /**
27 * An array to store auth cookies until we can determine if they should be sent
28 *
29 * @var array
30 */
31 private $set_cookie_args;
32
33 /**
34 * Singleton
35 */
36 public static function init() {
37 static $instance = false;
38
39 if ( ! $instance ) {
40 $instance = new Jetpack_WPCOM_Block_Editor();
41 }
42
43 return $instance;
44 }
45
46 /**
47 * Jetpack_WPCOM_Block_Editor constructor.
48 */
49 private function __construct() {
50 $this->set_cookie_args = array();
51 add_action( 'init', array( $this, 'init_actions' ) );
52 }
53
54 /**
55 * Add in all hooks.
56 */
57 public function init_actions() {
58 // Bail early if Jetpack's block editor extensions are disabled on the site.
59 /* This filter is documented in class.jetpack-gutenberg.php */
60 if ( ! apply_filters( 'jetpack_gutenberg', true ) ) {
61 return;
62 }
63
64 if ( $this->is_iframed_block_editor() ) {
65 add_action( 'admin_init', array( $this, 'disable_send_frame_options_header' ), 9 );
66 add_filter( 'admin_body_class', array( $this, 'add_iframed_body_class' ) );
67 }
68
69 require_once __DIR__ . '/functions.editor-type.php';
70 add_action( 'edit_form_top', 'Jetpack\EditorType\remember_classic_editor' );
71 add_action( 'login_init', array( $this, 'allow_block_editor_login' ), 1 );
72 add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_editor_assets' ), 9 );
73 add_action( 'enqueue_block_assets', array( $this, 'enqueue_block_assets' ) );
74 add_filter( 'mce_external_plugins', array( $this, 'add_tinymce_plugins' ) );
75 add_filter( 'block_editor_settings_all', 'Jetpack\EditorType\remember_block_editor', 10, 2 );
76
77 $this->enable_cross_site_auth_cookies();
78 }
79
80 /**
81 * Checks if we are embedding the block editor in an iframe in WordPress.com.
82 *
83 * @return bool Whether the current request is from the iframed block editor.
84 */
85 public function is_iframed_block_editor() {
86 global $pagenow;
87
88 // phpcs:ignore WordPress.Security.NonceVerification
89 return ( 'post.php' === $pagenow || 'post-new.php' === $pagenow ) && ! empty( $_GET['frame-nonce'] );
90 }
91
92 /**
93 * Prevents frame options header from firing if this is a allowed iframe request.
94 */
95 public function disable_send_frame_options_header() {
96 // phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
97 if ( isset( $_GET['frame-nonce'] ) && $this->framing_allowed( $_GET['frame-nonce'] ) ) {
98 remove_action( 'admin_init', 'send_frame_options_header' );
99 }
100 }
101
102 /**
103 * Adds custom admin body class if this is a allowed iframe request.
104 *
105 * @param string $classes Admin body classes.
106 * @return string
107 */
108 public function add_iframed_body_class( $classes ) {
109 // phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
110 if ( isset( $_GET['frame-nonce'] ) && $this->framing_allowed( $_GET['frame-nonce'] ) ) {
111 $classes .= ' is-iframed ';
112 }
113
114 return $classes;
115 }
116
117 /**
118 * Checks to see if cookie can be set in current context. If 3rd party cookie blocking
119 * is enabled the editor can't load in iFrame, so emiting X-Frame-Options: DENY will
120 * force the editor to break out of the iFrame.
121 */
122 private function check_iframe_cookie_setting() {
123 if ( ! isset( $_SERVER['QUERY_STRING'] ) || ! strpos( filter_var( wp_unslash( $_SERVER['QUERY_STRING'] ) ), 'calypsoify%3D1%26block-editor' ) || isset( $_COOKIE['wordpress_test_cookie'] ) ) {
124 return;
125 }
126
127 if ( isset( $_SERVER['REQUEST_URI'] ) && empty( $_GET['calypsoify_cookie_check'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
128 header( 'Location: ' . esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) . '&calypsoify_cookie_check=true' ) );
129 exit;
130 }
131
132 header( 'X-Frame-Options: DENY' );
133 exit;
134 }
135
136 /**
137 * Allows to iframe the login page if a user is logged out
138 * while trying to access the block editor from wordpress.com.
139 */
140 public function allow_block_editor_login() {
141 // phpcs:ignore WordPress.Security.NonceVerification
142 if ( empty( $_REQUEST['redirect_to'] ) ) {
143 return;
144 }
145 // phpcs:ignore WordPress.Security.NonceVerification
146 $redirect_to = esc_url_raw( wp_unslash( $_REQUEST['redirect_to'] ) );
147
148 $this->check_iframe_cookie_setting();
149
150 $query = wp_parse_url( urldecode( $redirect_to ), PHP_URL_QUERY );
151 $args = wp_parse_args( $query );
152
153 // Check nonce and make sure this is a Gutenframe request.
154 if ( ! empty( $args['frame-nonce'] ) && $this->framing_allowed( $args['frame-nonce'] ) ) {
155
156 // If SSO is active, we'll let WordPress.com handle authentication...
157 if ( ( new Modules() )->is_active( 'sso' ) ) {
158 // ...but only if it's not an Atomic site. They already do that.
159 if ( ! ( new Host() )->is_woa_site() ) {
160 add_filter( 'jetpack_sso_bypass_login_forward_wpcom', '__return_true' );
161 }
162 } else {
163 $_REQUEST['interim-login'] = true;
164 add_action( 'wp_login', array( $this, 'do_redirect' ) );
165 add_action( 'login_form', array( $this, 'add_login_html' ) );
166 add_filter( 'wp_login_errors', array( $this, 'add_login_message' ) );
167 remove_action( 'login_init', 'send_frame_options_header' );
168 wp_add_inline_style( 'login', '.interim-login #login{padding-top:8%}' );
169 }
170 }
171 }
172
173 /**
174 * Adds a login message.
175 *
176 * Intended to soften the expectation mismatch of ending up with a login screen rather than the editor.
177 *
178 * @param WP_Error $errors WP Error object.
179 * @return \WP_Error
180 */
181 public function add_login_message( $errors ) {
182 $errors->remove( 'expired' );
183 $errors->add( 'info', __( 'Before we continue, please log in to your Jetpack site.', 'jetpack' ), 'message' );
184
185 return $errors;
186 }
187
188 /**
189 * Maintains the `redirect_to` parameter in login form links.
190 * Adds visual feedback of login in progress.
191 */
192 public function add_login_html() {
193 ?>
194 <input type="hidden" name="redirect_to" value="<?php echo isset( $_REQUEST['redirect_to'] ) ? esc_url( wp_unslash( $_REQUEST['redirect_to'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized ?>" />
195 <script type="application/javascript">
196 document.getElementById( 'loginform' ).addEventListener( 'submit' , function() {
197 document.getElementById( 'wp-submit' ).setAttribute( 'disabled', 'disabled' );
198 document.getElementById( 'wp-submit' ).value = '<?php echo esc_js( __( 'Logging In...', 'jetpack' ) ); ?>';
199 } );
200 </script>
201 <?php
202 }
203
204 /**
205 * Does the redirect to the block editor.
206 *
207 * @return never
208 */
209 public function do_redirect() {
210 wp_safe_redirect( $GLOBALS['redirect_to'] );
211 exit;
212 }
213
214 /**
215 * Checks whether this is an allowed iframe request.
216 *
217 * @param string $nonce Nonce to verify.
218 * @return bool
219 */
220 public function framing_allowed( $nonce ) {
221 $blog_id = Connection_Manager::get_site_id();
222 if ( is_wp_error( $blog_id ) ) {
223 return false;
224 }
225
226 $verified = $this->verify_frame_nonce( $nonce, 'frame-' . $blog_id );
227
228 if ( is_wp_error( $verified ) ) {
229 wp_die( $verified ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
230 }
231
232 if ( $verified && ! defined( 'IFRAME_REQUEST' ) ) {
233 define( 'IFRAME_REQUEST', true );
234 }
235
236 return (bool) $verified;
237 }
238
239 /**
240 * Verify that correct nonce was used with time limit.
241 *
242 * The user is given an amount of time to use the token, so therefore, since the
243 * UID and $action remain the same, the independent variable is the time.
244 *
245 * @param string $nonce Nonce that was used in the form to verify.
246 * @param string $action Should give context to what is taking place and be the same when nonce was created.
247 * @return boolean|WP_Error Whether the nonce is valid.
248 */
249 public function verify_frame_nonce( $nonce, $action ) {
250 if ( empty( $nonce ) ) {
251 return false;
252 }
253
254 list( $expiration, $user_id, $hash ) = explode( ':', $nonce, 3 );
255
256 $this->nonce_user_id = (int) $user_id;
257 if ( ! $this->nonce_user_id ) {
258 return false;
259 }
260
261 $token = ( new Tokens() )->get_access_token( $this->nonce_user_id );
262 if ( ! $token ) {
263 return false;
264 }
265
266 /*
267 * Failures must return `false` (blocking the iframe) prior to the
268 * signature verification.
269 */
270
271 add_filter( 'salt', array( $this, 'filter_salt' ), 10, 2 );
272 $expected_hash = wp_hash( "$expiration|$action|{$this->nonce_user_id}", 'jetpack_frame_nonce' );
273 remove_filter( 'salt', array( $this, 'filter_salt' ) );
274
275 if ( ! hash_equals( $hash, $expected_hash ) ) {
276 return false;
277 }
278
279 /*
280 * Failures may return `WP_Error` (showing an error in the iframe) after the
281 * signature verification passes.
282 */
283
284 if ( time() > $expiration ) {
285 return new WP_Error( 'nonce_invalid_expired', 'Expired nonce.', array( 'status' => 401 ) );
286 }
287
288 // Check if it matches the current user, unless they're trying to log in.
289 if ( get_current_user_id() !== $this->nonce_user_id && ! doing_action( 'login_init' ) ) {
290 return new WP_Error( 'nonce_invalid_user_mismatch', 'User ID mismatch.', array( 'status' => 401 ) );
291 }
292
293 return true;
294 }
295
296 /**
297 * Filters the WordPress salt.
298 *
299 * @param string $salt Salt for the given scheme.
300 * @param string $scheme Authentication scheme.
301 * @return string
302 */
303 public function filter_salt( $salt, $scheme ) {
304 if ( 'jetpack_frame_nonce' === $scheme ) {
305 $token = ( new Tokens() )->get_access_token( $this->nonce_user_id );
306
307 if ( $token ) {
308 $salt = $token->secret;
309 }
310 }
311
312 return $salt;
313 }
314
315 /**
316 * Enqueues the WordPress.com block editor integration assets for the editor.
317 */
318 public function enqueue_block_editor_assets() {
319 global $pagenow;
320
321 // Bail if we're not in the post editor, but on the widget settings screen.
322 if ( is_customize_preview() || 'widgets.php' === $pagenow ) {
323 return;
324 }
325
326 $debug = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG;
327 $version = gmdate( 'Ymd' );
328
329 wp_enqueue_script(
330 'wpcom-block-editor-default-editor-script',
331 $debug
332 ? '//widgets.wp.com/wpcom-block-editor/default.editor.js?minify=false'
333 : '//widgets.wp.com/wpcom-block-editor/default.editor.min.js',
334 array(
335 'jquery',
336 'lodash',
337 'wp-annotations',
338 'wp-compose',
339 'wp-data',
340 'wp-editor',
341 'wp-element',
342 'wp-rich-text',
343 ),
344 $version,
345 true
346 );
347
348 wp_localize_script(
349 'wpcom-block-editor-default-editor-script',
350 'wpcomGutenberg',
351 array(
352 'richTextToolbar' => array(
353 'justify' => __( 'Justify', 'jetpack' ),
354 'underline' => __( 'Underline', 'jetpack' ),
355 ),
356 )
357 );
358
359 if ( ( new Host() )->is_woa_site() ) {
360 wp_enqueue_script(
361 'wpcom-block-editor-wpcom-editor-script',
362 $debug
363 ? '//widgets.wp.com/wpcom-block-editor/wpcom.editor.js?minify=false'
364 : '//widgets.wp.com/wpcom-block-editor/wpcom.editor.min.js',
365 array(
366 'lodash',
367 'wp-blocks',
368 'wp-data',
369 'wp-dom-ready',
370 'wp-plugins',
371 ),
372 $version,
373 true
374 );
375 wp_enqueue_style(
376 'wpcom-block-editor-wpcom-editor-styles',
377 $debug
378 ? '//widgets.wp.com/wpcom-block-editor/wpcom.editor.css?minify=false'
379 : '//widgets.wp.com/wpcom-block-editor/wpcom.editor.min.css',
380 array(),
381 $version
382 );
383 }
384
385 if ( $this->is_iframed_block_editor() ) {
386 wp_enqueue_script(
387 'wpcom-block-editor-calypso-editor-script',
388 $debug
389 ? '//widgets.wp.com/wpcom-block-editor/calypso.editor.js?minify=false'
390 : '//widgets.wp.com/wpcom-block-editor/calypso.editor.min.js',
391 array(
392 'calypsoify_wpadminmods_js',
393 'jquery',
394 'lodash',
395 'react',
396 'wp-blocks',
397 'wp-data',
398 'wp-hooks',
399 'wp-tinymce',
400 'wp-url',
401 ),
402 $version,
403 true
404 );
405
406 wp_enqueue_style(
407 'wpcom-block-editor-calypso-editor-styles',
408 $debug
409 ? '//widgets.wp.com/wpcom-block-editor/calypso.editor.css?minify=false'
410 : '//widgets.wp.com/wpcom-block-editor/calypso.editor.min.css',
411 array(),
412 $version
413 );
414 }
415 }
416
417 /**
418 * Enqueues the WordPress.com block editor integration assets for both editor and front-end.
419 */
420 public function enqueue_block_assets() {
421 // These styles are manually copied from //widgets.wp.com/wpcom-block-editor/default.view.css in order to
422 // improve the performance by avoiding an extra network request to download the CSS file on every page.
423 wp_add_inline_style( 'wp-block-library', '.has-text-align-justify{text-align:justify;}' );
424 }
425
426 /**
427 * Determines if the current $post contains a justified paragraph block.
428 *
429 * @return boolean true if justified paragraph is found, false otherwise.
430 */
431 public function has_justified_block() {
432 global $post;
433 if ( ! $post instanceof WP_Post ) {
434 return false;
435 }
436
437 if ( ! has_blocks( $post ) ) {
438 return false;
439 }
440
441 return str_contains( $post->post_content, '<!-- wp:paragraph {"align":"justify"' );
442 }
443
444 /**
445 * Register the Tiny MCE plugins for the WordPress.com block editor integration.
446 *
447 * @param array $plugin_array An array of external Tiny MCE plugins.
448 * @return array External TinyMCE plugins.
449 */
450 public function add_tinymce_plugins( $plugin_array ) {
451 if ( $this->is_iframed_block_editor() ) {
452 $debug = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG;
453
454 $plugin_array['gutenberg-wpcom-iframe-media-modal'] = add_query_arg(
455 'v',
456 gmdate( 'YW' ),
457 $debug
458 ? '//widgets.wp.com/wpcom-block-editor/calypso.tinymce.js?minify=false'
459 : '//widgets.wp.com/wpcom-block-editor/calypso.tinymce.min.js'
460 );
461 }
462
463 return $plugin_array;
464 }
465
466 /**
467 * Ensures the authentication cookies are designated for cross-site access.
468 */
469 private function enable_cross_site_auth_cookies() {
470 /**
471 * Allow plugins to disable the cross-site auth cookies.
472 *
473 * @since 8.1.1
474 *
475 * @param false bool Whether auth cookies should be disabled for cross-site access. False by default.
476 */
477 if ( apply_filters( 'jetpack_disable_cross_site_auth_cookies', false ) ) {
478 return;
479 }
480
481 add_action( 'set_auth_cookie', array( $this, 'set_samesite_auth_cookies' ), 10, 5 );
482 add_action( 'set_logged_in_cookie', array( $this, 'set_samesite_logged_in_cookies' ), 10, 4 );
483 add_filter( 'send_auth_cookies', array( $this, 'maybe_send_cookies' ), 9999 );
484 }
485
486 /**
487 * Checks if we've stored any cookies to send and then sends them
488 * if the send_auth_cookies value is true.
489 *
490 * @param bool $send_cookies The filtered value that determines whether to send auth cookies.
491 */
492 public function maybe_send_cookies( $send_cookies ) {
493
494 if ( ! empty( $this->set_cookie_args ) && $send_cookies ) {
495 array_map(
496 function ( $cookie ) {
497 call_user_func_array( 'jetpack_shim_setcookie', $cookie );
498 },
499 $this->set_cookie_args
500 );
501 $this->set_cookie_args = array();
502 return false;
503 }
504
505 return $send_cookies;
506 }
507
508 /**
509 * Gets the SameSite attribute to use in auth cookies.
510 *
511 * @param bool $secure Whether the connection is secure.
512 * @return string SameSite attribute to use on auth cookies.
513 */
514 public function get_samesite_attr_for_auth_cookies( $secure ) {
515 $samesite = $secure ? 'None' : 'Lax';
516 /**
517 * Filters the SameSite attribute to use in auth cookies.
518 *
519 * @param string $samesite SameSite attribute to use in auth cookies.
520 *
521 * @since 8.1.1
522 */
523 $samesite = apply_filters( 'jetpack_auth_cookie_samesite', $samesite );
524
525 return $samesite;
526 }
527
528 /**
529 * Generates cross-site auth cookies so they can be accessed by WordPress.com.
530 *
531 * @param string $auth_cookie Authentication cookie value.
532 * @param int $expire The time the login grace period expires as a UNIX timestamp.
533 * Default is 12 hours past the cookie's expiration time.
534 * @param int $expiration The time when the authentication cookie expires as a UNIX timestamp.
535 * Default is 14 days from now.
536 * @param int $user_id User ID.
537 * @param string $scheme Authentication scheme. Values include 'auth' or 'secure_auth'.
538 */
539 public function set_samesite_auth_cookies( $auth_cookie, $expire, $expiration, $user_id, $scheme ) {
540 if ( wp_startswith( $scheme, 'secure_' ) ) {
541 $secure = true;
542 $auth_cookie_name = SECURE_AUTH_COOKIE;
543 } else {
544 $secure = false;
545 $auth_cookie_name = AUTH_COOKIE;
546 }
547 $samesite = $this->get_samesite_attr_for_auth_cookies( $secure );
548
549 $this->set_cookie_args[] = array(
550 $auth_cookie_name,
551 $auth_cookie,
552 array(
553 'expires' => $expire,
554 'path' => PLUGINS_COOKIE_PATH,
555 'domain' => COOKIE_DOMAIN,
556 'secure' => $secure,
557 'httponly' => true,
558 'samesite' => $samesite,
559 ),
560 );
561
562 $this->set_cookie_args[] = array(
563 $auth_cookie_name,
564 $auth_cookie,
565 array(
566 'expires' => $expire,
567 'path' => ADMIN_COOKIE_PATH,
568 'domain' => COOKIE_DOMAIN,
569 'secure' => $secure,
570 'httponly' => true,
571 'samesite' => $samesite,
572 ),
573 );
574 }
575
576 /**
577 * Generates cross-site logged in cookies so they can be accessed by WordPress.com.
578 *
579 * @param string $logged_in_cookie The logged-in cookie value.
580 * @param int $expire The time the login grace period expires as a UNIX timestamp.
581 * Default is 12 hours past the cookie's expiration time.
582 * @param int $expiration The time when the logged-in cookie expires as a UNIX timestamp.
583 * Default is 14 days from now.
584 * @param int $user_id User ID.
585 */
586 public function set_samesite_logged_in_cookies( $logged_in_cookie, $expire, $expiration, $user_id ) {
587 $secure = is_ssl();
588
589 // Front-end cookie is secure when the auth cookie is secure and the site's home URL is forced HTTPS.
590 $secure_logged_in_cookie = $secure && 'https' === wp_parse_url( get_option( 'home' ), PHP_URL_SCHEME );
591
592 /** This filter is documented in core/src/wp-includes/pluggable.php */
593 $secure = apply_filters( 'secure_auth_cookie', $secure, $user_id );
594
595 /** This filter is documented in core/src/wp-includes/pluggable.php */
596 $secure_logged_in_cookie = apply_filters( 'secure_logged_in_cookie', $secure_logged_in_cookie, $user_id, $secure );
597
598 $samesite = $this->get_samesite_attr_for_auth_cookies( $secure_logged_in_cookie );
599
600 $this->set_cookie_args[] = array(
601 LOGGED_IN_COOKIE,
602 $logged_in_cookie,
603 array(
604 'expires' => $expire,
605 'path' => COOKIEPATH,
606 'domain' => COOKIE_DOMAIN,
607 'secure' => $secure_logged_in_cookie,
608 'httponly' => true,
609 'samesite' => $samesite,
610 ),
611 );
612
613 if ( COOKIEPATH !== SITECOOKIEPATH ) {
614 $this->set_cookie_args[] = array(
615 LOGGED_IN_COOKIE,
616 $logged_in_cookie,
617 array(
618 'expires' => $expire,
619 'path' => SITECOOKIEPATH,
620 'domain' => COOKIE_DOMAIN,
621 'secure' => $secure_logged_in_cookie,
622 'httponly' => true,
623 'samesite' => $samesite,
624 ),
625 );
626 }
627 }
628 }
629
630 Jetpack_WPCOM_Block_Editor::init();
631