Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
10.57% covered (danger)
10.57%
28 / 265
13.04% covered (danger)
13.04%
3 / 23
CRAP
0.00% covered (danger)
0.00%
0 / 1
Jetpack_WPCOM_Block_Editor
10.61% covered (danger)
10.61%
28 / 264
13.04% covered (danger)
13.04%
3 / 23
4885.44
0.00% covered (danger)
0.00%
0 / 1
 init
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 init_actions
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
 is_iframed_block_editor
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
12
 disable_send_frame_options_header
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
12
 add_iframed_body_class
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 check_iframe_cookie_setting
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
42
 allow_block_editor_login
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
56
 add_login_message
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 add_login_html
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 do_redirect
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 framing_allowed
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 verify_frame_nonce
89.47% covered (warning)
89.47%
17 / 19
0.00% covered (danger)
0.00%
0 / 1
8.07
 filter_salt
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 enqueue_block_editor_assets
0.00% covered (danger)
0.00%
0 / 83
0.00% covered (danger)
0.00%
0 / 1
132
 enqueue_block_assets
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 has_justified_block
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 add_tinymce_plugins
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
 enable_cross_site_auth_cookies
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 maybe_send_cookies
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 get_samesite_attr_for_auth_cookies
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 set_samesite_auth_cookies
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
20
 set_samesite_logged_in_cookies
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2/**
3 * WordPress.com Block Editor
4 * Allow new block editor posts to be composed on WordPress.com.
5 *
6 * @package automattic/jetpack-mu-wpcom
7 */
8
9namespace Automattic\Jetpack\Jetpack_Mu_Wpcom\WPCOM_Block_Editor;
10
11use Automattic\Jetpack\Connection\Manager as Connection_Manager;
12use Automattic\Jetpack\Connection\Tokens;
13use Automattic\Jetpack\Modules;
14use Automattic\Jetpack\Status\Host;
15use WP_Error;
16use WP_Post;
17
18/**
19 * WordPress.com Block editor
20 */
21class Jetpack_WPCOM_Block_Editor {
22    /**
23     * ID of the user who signed the nonce.
24     *
25     * @var int
26     */
27    private $nonce_user_id;
28
29    /**
30     * An array to store auth cookies until we can determine if they should be sent
31     *
32     * @var array
33     */
34    private $set_cookie_args;
35
36    /**
37     * Singleton
38     */
39    public static function init() {
40        static $instance = false;
41
42        if ( ! $instance ) {
43            $instance = new Jetpack_WPCOM_Block_Editor();
44        }
45
46        return $instance;
47    }
48
49    /**
50     * Jetpack_WPCOM_Block_Editor constructor.
51     */
52    private function __construct() {
53        $this->set_cookie_args = array();
54        add_action( 'init', array( $this, 'init_actions' ) );
55    }
56
57    /**
58     * Add in all hooks.
59     */
60    public function init_actions() {
61        // Bail early if Jetpack's block editor extensions are disabled on the site.
62        /* This filter is documented in projects/plugins/jetpack/class.jetpack-gutenberg.php */
63        if ( ! apply_filters( 'jetpack_gutenberg', true ) ) {
64            return;
65        }
66
67        if ( ! ( new Host() )->is_wpcom_simple() && $this->is_iframed_block_editor() ) {
68            add_action( 'admin_init', array( $this, 'disable_send_frame_options_header' ), 9 );
69            add_filter( 'admin_body_class', array( $this, 'add_iframed_body_class' ) );
70        }
71
72        add_action( 'edit_form_top', 'Automattic\Jetpack\Jetpack_Mu_Wpcom\WPCOM_Block_Editor\EditorType\remember_classic_editor' );
73        add_action( 'login_init', array( $this, 'allow_block_editor_login' ), 1 );
74        add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_editor_assets' ), 9 );
75        add_action( 'enqueue_block_assets', array( $this, 'enqueue_block_assets' ) );
76        add_filter( 'mce_external_plugins', array( $this, 'add_tinymce_plugins' ) );
77        add_filter( 'block_editor_settings_all', 'Automattic\Jetpack\Jetpack_Mu_Wpcom\WPCOM_Block_Editor\EditorType\remember_block_editor', 10, 2 );
78
79        $this->enable_cross_site_auth_cookies();
80    }
81
82    /**
83     * Checks if we are embedding the block editor in an iframe in WordPress.com.
84     *
85     * @return bool Whether the current request is from the iframed block editor.
86     */
87    public function is_iframed_block_editor() {
88        global $pagenow;
89
90        // phpcs:ignore WordPress.Security.NonceVerification
91        return ( 'post.php' === $pagenow || 'post-new.php' === $pagenow ) && ! empty( $_GET['frame-nonce'] );
92    }
93
94    /**
95     * Prevents frame options header from firing if this is a allowed iframe request.
96     */
97    public function disable_send_frame_options_header() {
98        // phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
99        if ( isset( $_GET['frame-nonce'] ) && $this->framing_allowed( $_GET['frame-nonce'] ) ) {
100            remove_action( 'admin_init', 'send_frame_options_header' );
101        }
102    }
103
104    /**
105     * Adds custom admin body class if this is a allowed iframe request.
106     *
107     * @param string $classes Admin body classes.
108     * @return string
109     */
110    public function add_iframed_body_class( $classes ) {
111        // phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
112        if ( isset( $_GET['frame-nonce'] ) && $this->framing_allowed( $_GET['frame-nonce'] ) ) {
113            $classes .= ' is-iframed ';
114        }
115
116        return $classes;
117    }
118
119    /**
120     * Checks to see if cookie can be set in current context. If 3rd party cookie blocking
121     * is enabled the editor can't load in iFrame, so emiting X-Frame-Options: DENY will
122     * force the editor to break out of the iFrame.
123     */
124    private function check_iframe_cookie_setting() {
125        if ( ! isset( $_SERVER['QUERY_STRING'] ) || ! strpos( filter_var( wp_unslash( $_SERVER['QUERY_STRING'] ) ), 'calypsoify%3D1%26block-editor' ) || isset( $_COOKIE['wordpress_test_cookie'] ) ) {
126            return;
127        }
128
129        if ( isset( $_SERVER['REQUEST_URI'] ) && empty( $_GET['calypsoify_cookie_check'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
130            header( 'Location: ' . esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) . '&calypsoify_cookie_check=true' ) );
131            exit( 0 );
132        }
133
134        header( 'X-Frame-Options: DENY' );
135        exit( 0 );
136    }
137
138    /**
139     * Allows to iframe the login page if a user is logged out
140     * while trying to access the block editor from wordpress.com.
141     */
142    public function allow_block_editor_login() {
143        // phpcs:ignore WordPress.Security.NonceVerification
144        if ( ( new Host() )->is_wpcom_simple() || empty( $_REQUEST['redirect_to'] ) ) {
145            return;
146        }
147        // phpcs:ignore WordPress.Security.NonceVerification
148        $redirect_to = esc_url_raw( wp_unslash( $_REQUEST['redirect_to'] ) );
149
150        $this->check_iframe_cookie_setting();
151
152        $query = wp_parse_url( urldecode( $redirect_to ), PHP_URL_QUERY );
153        $args  = wp_parse_args( $query );
154
155        // Check nonce and make sure this is a Gutenframe request.
156        if ( ! empty( $args['frame-nonce'] ) && $this->framing_allowed( $args['frame-nonce'] ) ) {
157
158            // If SSO is active, we'll let WordPress.com handle authentication...
159            if ( ( new Modules() )->is_active( 'sso' ) ) {
160                // ...but only if it's not an Atomic site. They already do that.
161                if ( ! ( new Host() )->is_woa_site() ) {
162                    add_filter( 'jetpack_sso_bypass_login_forward_wpcom', '__return_true' );
163                }
164            } else {
165                $_REQUEST['interim-login'] = true;
166                add_action( 'wp_login', array( $this, 'do_redirect' ) );
167                add_action( 'login_form', array( $this, 'add_login_html' ) );
168                add_filter( 'wp_login_errors', array( $this, 'add_login_message' ) );
169                remove_action( 'login_init', 'send_frame_options_header' );
170                wp_add_inline_style( 'login', '.interim-login #login{padding-top:8%}' );
171            }
172        }
173    }
174
175    /**
176     * Adds a login message.
177     *
178     * Intended to soften the expectation mismatch of ending up with a login screen rather than the editor.
179     *
180     * @param WP_Error $errors WP Error object.
181     * @return \WP_Error
182     */
183    public function add_login_message( $errors ) {
184        $errors->remove( 'expired' );
185        $errors->add( 'info', __( 'Before we continue, please log in to your Jetpack site.', 'jetpack-mu-wpcom' ), 'message' );
186
187        return $errors;
188    }
189
190    /**
191     * Maintains the `redirect_to` parameter in login form links.
192     * Adds visual feedback of login in progress.
193     */
194    public function add_login_html() {
195        ?>
196        <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 ?>" />
197        <script type="application/javascript">
198            document.getElementById( 'loginform' ).addEventListener( 'submit' , function() {
199                document.getElementById( 'wp-submit' ).setAttribute( 'disabled', 'disabled' );
200                document.getElementById( 'wp-submit' ).value = <?php echo wp_json_encode( __( 'Logging In...', 'jetpack-mu-wpcom' ), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?>;
201            } );
202        </script>
203        <?php
204    }
205
206    /**
207     * Does the redirect to the block editor.
208     *
209     * @return never
210     */
211    public function do_redirect() {
212        wp_safe_redirect( $GLOBALS['redirect_to'] );
213        exit( 0 );
214    }
215
216    /**
217     * Checks whether this is an allowed iframe request.
218     *
219     * @param string $nonce Nonce to verify.
220     * @return bool
221     */
222    public function framing_allowed( $nonce ) {
223        $blog_id = Connection_Manager::get_site_id();
224        if ( is_wp_error( $blog_id ) ) {
225            return false;
226        }
227
228        $verified = $this->verify_frame_nonce( $nonce, 'frame-' . $blog_id );
229
230        if ( is_wp_error( $verified ) ) {
231            wp_die( $verified ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
232        }
233
234        if ( $verified && ! defined( 'IFRAME_REQUEST' ) ) {
235            define( 'IFRAME_REQUEST', true );
236        }
237
238        return (bool) $verified;
239    }
240
241    /**
242     * Verify that correct nonce was used with time limit.
243     *
244     * The user is given an amount of time to use the token, so therefore, since the
245     * UID and $action remain the same, the independent variable is the time.
246     *
247     * @param string $nonce Nonce that was used in the form to verify.
248     * @param string $action Should give context to what is taking place and be the same when nonce was created.
249     * @return boolean|WP_Error Whether the nonce is valid.
250     */
251    public function verify_frame_nonce( $nonce, $action ) {
252        if ( empty( $nonce ) ) {
253            return false;
254        }
255
256        list( $expiration, $user_id, $hash ) = explode( ':', $nonce, 3 );
257
258        $this->nonce_user_id = (int) $user_id;
259        if ( ! $this->nonce_user_id ) {
260            return false;
261        }
262
263        $token = ( new Tokens() )->get_access_token( $this->nonce_user_id );
264        if ( ! $token ) {
265            return false;
266        }
267
268        /*
269         * Failures must return `false` (blocking the iframe) prior to the
270         * signature verification.
271         */
272
273        add_filter( 'salt', array( $this, 'filter_salt' ), 10, 2 );
274        $expected_hash = wp_hash( "$expiration|$action|{$this->nonce_user_id}", 'jetpack_frame_nonce' );
275        remove_filter( 'salt', array( $this, 'filter_salt' ) );
276
277        if ( ! hash_equals( $hash, $expected_hash ) ) {
278            return false;
279        }
280
281        /*
282         * Failures may return `WP_Error` (showing an error in the iframe) after the
283         * signature verification passes.
284         */
285
286        if ( time() > $expiration ) {
287            return new WP_Error( 'nonce_invalid_expired', 'Expired nonce.', array( 'status' => 401 ) );
288        }
289
290        // Check if it matches the current user, unless they're trying to log in.
291        if ( get_current_user_id() !== $this->nonce_user_id && ! doing_action( 'login_init' ) ) {
292            return new WP_Error( 'nonce_invalid_user_mismatch', 'User ID mismatch.', array( 'status' => 401 ) );
293        }
294
295        return true;
296    }
297
298    /**
299     * Filters the WordPress salt.
300     *
301     * @param string $salt Salt for the given scheme.
302     * @param string $scheme Authentication scheme.
303     * @return string
304     */
305    public function filter_salt( $salt, $scheme ) {
306        if ( 'jetpack_frame_nonce' === $scheme ) {
307            $token = ( new Tokens() )->get_access_token( $this->nonce_user_id );
308
309            if ( $token ) {
310                $salt = $token->secret;
311            }
312        }
313
314        return $salt;
315    }
316
317    /**
318     * Enqueues the WordPress.com block editor integration assets for the editor.
319     */
320    public function enqueue_block_editor_assets() {
321        global $pagenow;
322
323        // Bail if we're not in the post editor, but on the widget settings screen.
324        if ( is_customize_preview() || 'widgets.php' === $pagenow ) {
325            return;
326        }
327
328        $debug   = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG;
329        $version = gmdate( 'Ymd' );
330
331        wp_enqueue_script(
332            'wpcom-block-editor-default-editor-script',
333            $debug
334                ? '//widgets.wp.com/wpcom-block-editor/default.editor.js?minify=false'
335                : '//widgets.wp.com/wpcom-block-editor/default.editor.min.js',
336            array(
337                'jquery',
338                'lodash',
339                'wp-annotations',
340                'wp-compose',
341                'wp-data',
342                'wp-editor',
343                'wp-element',
344                'wp-rich-text',
345            ),
346            $version,
347            true
348        );
349
350        if ( ! ( new Host() )->is_wpcom_simple() ) { // Same object is already used in gutenberg-wpcom plugin.
351            wp_localize_script(
352                'wpcom-block-editor-default-editor-script',
353                'wpcomGutenberg',
354                array(
355                    'richTextToolbar' => array(
356                        'justify'   => __( 'Justify', 'jetpack-mu-wpcom' ),
357                        'underline' => __( 'Underline', 'jetpack-mu-wpcom' ),
358                    ),
359                )
360            );
361        }
362
363        wp_enqueue_script(
364            'wpcom-block-editor-wpcom-editor-script',
365            $debug
366                ? '//widgets.wp.com/wpcom-block-editor/wpcom.editor.js?minify=false'
367                : '//widgets.wp.com/wpcom-block-editor/wpcom.editor.min.js',
368            array(
369                'lodash',
370                'wp-blocks',
371                'wp-data',
372                'wp-dom-ready',
373                'wp-plugins',
374            ),
375            $version,
376            true
377        );
378        wp_enqueue_style(
379            'wpcom-block-editor-wpcom-editor-styles',
380            $debug
381                ? '//widgets.wp.com/wpcom-block-editor/wpcom.editor.css?minify=false'
382                : '//widgets.wp.com/wpcom-block-editor/wpcom.editor.min.css',
383            array(),
384            $version
385        );
386
387        if ( $this->is_iframed_block_editor() ) {
388            wp_enqueue_script(
389                'wpcom-block-editor-calypso-editor-script',
390                $debug
391                    ? '//widgets.wp.com/wpcom-block-editor/calypso.editor.js?minify=false'
392                    : '//widgets.wp.com/wpcom-block-editor/calypso.editor.min.js',
393                array(
394                    'jquery',
395                    'lodash',
396                    'react',
397                    'wp-blocks',
398                    'wp-data',
399                    'wp-hooks',
400                    'wp-tinymce',
401                    'wp-url',
402                ),
403                $version,
404                true
405            );
406
407            wp_enqueue_style(
408                'wpcom-block-editor-calypso-editor-styles',
409                $debug
410                    ? '//widgets.wp.com/wpcom-block-editor/calypso.editor.css?minify=false'
411                    : '//widgets.wp.com/wpcom-block-editor/calypso.editor.min.css',
412                array(),
413                $version
414            );
415        }
416    }
417
418    /**
419     * Enqueues the WordPress.com block editor integration assets for both editor and front-end.
420     */
421    public function enqueue_block_assets() {
422        // These styles are manually copied from //widgets.wp.com/wpcom-block-editor/default.view.css in order to
423        // improve the performance by avoiding an extra network request to download the CSS file on every page.
424        wp_add_inline_style( 'wp-block-library', '.has-text-align-justify{text-align:justify;}' );
425    }
426
427    /**
428     * Determines if the current $post contains a justified paragraph block.
429     *
430     * @return boolean true if justified paragraph is found, false otherwise.
431     */
432    public function has_justified_block() {
433        global $post;
434        if ( ! $post instanceof WP_Post ) {
435            return false;
436        }
437
438        if ( ! has_blocks( $post ) ) {
439            return false;
440        }
441
442        return str_contains( $post->post_content, '<!-- wp:paragraph {"align":"justify"' );
443    }
444
445    /**
446     * Register the Tiny MCE plugins for the WordPress.com block editor integration.
447     *
448     * @param array $plugin_array An array of external Tiny MCE plugins.
449     * @return array External TinyMCE plugins.
450     */
451    public function add_tinymce_plugins( $plugin_array ) {
452        if ( $this->is_iframed_block_editor() ) {
453            $debug = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG;
454
455            $plugin_array['gutenberg-wpcom-iframe-media-modal'] = add_query_arg(
456                'v',
457                gmdate( 'YW' ),
458                $debug
459                    ? '//widgets.wp.com/wpcom-block-editor/calypso.tinymce.js?minify=false'
460                    : '//widgets.wp.com/wpcom-block-editor/calypso.tinymce.min.js'
461            );
462        }
463
464        return $plugin_array;
465    }
466
467    /**
468     * Ensures the authentication cookies are designated for cross-site access.
469     */
470    private function enable_cross_site_auth_cookies() {
471        /**
472         * Allow plugins to disable the cross-site auth cookies.
473         *
474         * @since jetpack-8.1.1
475         *
476         * @param false bool Whether auth cookies should be disabled for cross-site access. False by default.
477         */
478        if ( apply_filters( 'jetpack_disable_cross_site_auth_cookies', false ) ) {
479            return;
480        }
481
482        add_action( 'set_auth_cookie', array( $this, 'set_samesite_auth_cookies' ), 10, 5 );
483        add_action( 'set_logged_in_cookie', array( $this, 'set_samesite_logged_in_cookies' ), 10, 4 );
484        add_filter( 'send_auth_cookies', array( $this, 'maybe_send_cookies' ), 9999 );
485    }
486
487    /**
488     * Checks if we've stored any cookies to send and then sends them
489     * if the send_auth_cookies value is true.
490     *
491     * @param bool $send_cookies The filtered value that determines whether to send auth cookies.
492     */
493    public function maybe_send_cookies( $send_cookies ) {
494
495        if ( ! empty( $this->set_cookie_args ) && $send_cookies ) {
496            array_walk(
497                $this->set_cookie_args,
498                function ( $cookie ) {
499                    call_user_func_array( 'setcookie', $cookie );
500                }
501            );
502            $this->set_cookie_args = array();
503            return false;
504        }
505
506        return $send_cookies;
507    }
508
509    /**
510     * Gets the SameSite attribute to use in auth cookies.
511     *
512     * @param  bool $secure Whether the connection is secure.
513     * @return string SameSite attribute to use on auth cookies.
514     */
515    public function get_samesite_attr_for_auth_cookies( $secure ) {
516        $samesite = $secure ? 'None' : 'Lax';
517        /**
518         * Filters the SameSite attribute to use in auth cookies.
519         *
520         * @param string $samesite SameSite attribute to use in auth cookies.
521         *
522         * @since jetpack-8.1.1
523         */
524        $samesite = apply_filters( 'jetpack_auth_cookie_samesite', $samesite );
525
526        return $samesite;
527    }
528
529    /**
530     * Generates cross-site auth cookies so they can be accessed by WordPress.com.
531     *
532     * @param string $auth_cookie Authentication cookie value.
533     * @param int    $expire      The time the login grace period expires as a UNIX timestamp.
534     *                            Default is 12 hours past the cookie's expiration time.
535     * @param int    $expiration  The time when the authentication cookie expires as a UNIX timestamp.
536     *                            Default is 14 days from now.
537     * @param int    $user_id     User ID.
538     * @param string $scheme      Authentication scheme. Values include 'auth' or 'secure_auth'.
539     */
540    public function set_samesite_auth_cookies( $auth_cookie, $expire, $expiration, $user_id, $scheme ) {
541        if ( $scheme && is_scalar( $scheme ) && str_starts_with( (string) $scheme, 'secure_' ) ) {
542            $secure           = true;
543            $auth_cookie_name = SECURE_AUTH_COOKIE;
544        } else {
545            $secure           = false;
546            $auth_cookie_name = AUTH_COOKIE;
547        }
548        $samesite = $this->get_samesite_attr_for_auth_cookies( $secure );
549
550        $this->set_cookie_args[] = array(
551            $auth_cookie_name,
552            $auth_cookie,
553            array(
554                'expires'  => $expire,
555                'path'     => PLUGINS_COOKIE_PATH,
556                'domain'   => COOKIE_DOMAIN,
557                'secure'   => $secure,
558                'httponly' => true,
559                'samesite' => $samesite,
560            ),
561        );
562
563        $this->set_cookie_args[] = array(
564            $auth_cookie_name,
565            $auth_cookie,
566            array(
567                'expires'  => $expire,
568                'path'     => ADMIN_COOKIE_PATH,
569                'domain'   => COOKIE_DOMAIN,
570                'secure'   => $secure,
571                'httponly' => true,
572                'samesite' => $samesite,
573            ),
574        );
575    }
576
577    /**
578     * Generates cross-site logged in cookies so they can be accessed by WordPress.com.
579     *
580     * @param string $logged_in_cookie The logged-in cookie value.
581     * @param int    $expire           The time the login grace period expires as a UNIX timestamp.
582     *                                 Default is 12 hours past the cookie's expiration time.
583     * @param int    $expiration       The time when the logged-in cookie expires as a UNIX timestamp.
584     *                                 Default is 14 days from now.
585     * @param int    $user_id          User ID.
586     */
587    public function set_samesite_logged_in_cookies( $logged_in_cookie, $expire, $expiration, $user_id ) {
588        $secure = is_ssl();
589
590        // Front-end cookie is secure when the auth cookie is secure and the site's home URL is forced HTTPS.
591        $secure_logged_in_cookie = $secure && 'https' === wp_parse_url( get_option( 'home' ), PHP_URL_SCHEME );
592
593        /** This filter is documented in core/src/wp-includes/pluggable.php */
594        $secure = apply_filters( 'secure_auth_cookie', $secure, $user_id );
595
596        /** This filter is documented in core/src/wp-includes/pluggable.php */
597        $secure_logged_in_cookie = apply_filters( 'secure_logged_in_cookie', $secure_logged_in_cookie, $user_id, $secure );
598
599        $samesite = $this->get_samesite_attr_for_auth_cookies( $secure_logged_in_cookie );
600
601        $this->set_cookie_args[] = array(
602            LOGGED_IN_COOKIE,
603            $logged_in_cookie,
604            array(
605                'expires'  => $expire,
606                'path'     => COOKIEPATH,
607                'domain'   => COOKIE_DOMAIN,
608                'secure'   => $secure_logged_in_cookie,
609                'httponly' => true,
610                'samesite' => $samesite,
611            ),
612        );
613
614        if ( COOKIEPATH !== SITECOOKIEPATH ) {
615            $this->set_cookie_args[] = array(
616                LOGGED_IN_COOKIE,
617                $logged_in_cookie,
618                array(
619                    'expires'  => $expire,
620                    'path'     => SITECOOKIEPATH,
621                    'domain'   => COOKIE_DOMAIN,
622                    'secure'   => $secure_logged_in_cookie,
623                    'httponly' => true,
624                    'samesite' => $samesite,
625                ),
626            );
627        }
628    }
629}
630
631Jetpack_WPCOM_Block_Editor::init();