Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
33.09% covered (danger)
33.09%
91 / 275
17.39% covered (danger)
17.39%
4 / 23
CRAP
0.00% covered (danger)
0.00%
0 / 1
Jetpack_WPCOM_Block_Editor
33.21% covered (danger)
33.21%
91 / 274
17.39% covered (danger)
17.39%
4 / 23
2135.38
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
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
3
 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
86.36% covered (warning)
86.36%
19 / 22
0.00% covered (danger)
0.00%
0 / 1
9.21
 filter_salt
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 enqueue_block_editor_assets
66.67% covered (warning)
66.67%
60 / 90
0.00% covered (danger)
0.00%
0 / 1
15.48
 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        $parts = explode( ':', $nonce, 3 );
257        if ( count( $parts ) !== 3 ) {
258            return false;
259        }
260        list( $expiration, $user_id, $hash ) = $parts;
261
262        $this->nonce_user_id = (int) $user_id;
263        if ( ! $this->nonce_user_id ) {
264            return false;
265        }
266
267        $token = ( new Tokens() )->get_access_token( $this->nonce_user_id );
268        if ( ! $token ) {
269            return false;
270        }
271
272        /*
273         * Failures must return `false` (blocking the iframe) prior to the
274         * signature verification.
275         */
276
277        add_filter( 'salt', array( $this, 'filter_salt' ), 10, 2 );
278        $expected_hash = wp_hash( "$expiration|$action|{$this->nonce_user_id}", 'jetpack_frame_nonce' );
279        remove_filter( 'salt', array( $this, 'filter_salt' ) );
280
281        if ( ! hash_equals( $hash, $expected_hash ) ) {
282            return false;
283        }
284
285        /*
286         * Failures may return `WP_Error` (showing an error in the iframe) after the
287         * signature verification passes.
288         */
289
290        if ( time() > $expiration ) {
291            return new WP_Error( 'nonce_invalid_expired', 'Expired nonce.', array( 'status' => 401 ) );
292        }
293
294        // Check if it matches the current user, unless they're trying to log in.
295        if ( get_current_user_id() !== $this->nonce_user_id && ! doing_action( 'login_init' ) ) {
296            return new WP_Error( 'nonce_invalid_user_mismatch', 'User ID mismatch.', array( 'status' => 401 ) );
297        }
298
299        return true;
300    }
301
302    /**
303     * Filters the WordPress salt.
304     *
305     * @param string $salt Salt for the given scheme.
306     * @param string $scheme Authentication scheme.
307     * @return string
308     */
309    public function filter_salt( $salt, $scheme ) {
310        if ( 'jetpack_frame_nonce' === $scheme ) {
311            $token = ( new Tokens() )->get_access_token( $this->nonce_user_id );
312
313            if ( $token ) {
314                $salt = $token->secret;
315            }
316        }
317
318        return $salt;
319    }
320
321    /**
322     * Enqueues the WordPress.com block editor integration assets for the editor.
323     */
324    public function enqueue_block_editor_assets() {
325        global $pagenow;
326
327        // Bail if we're not in the post editor, but on the widget settings screen.
328        if ( is_customize_preview() || 'widgets.php' === $pagenow ) {
329            return;
330        }
331
332        $debug   = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG;
333        $version = gmdate( 'Ymd' );
334
335        // Keep these dependencies in sync with the generated asset metadata from
336        // the Calypso wpcom-block-editor app.
337        wp_enqueue_script(
338            'wpcom-block-editor-default-editor-script',
339            $debug
340                ? '//widgets.wp.com/wpcom-block-editor/default.editor.js?minify=false'
341                : '//widgets.wp.com/wpcom-block-editor/default.editor.min.js',
342            array(
343                'react',
344                'wp-block-editor',
345                'wp-blocks',
346                'wp-compose',
347                'wp-data',
348                'wp-rich-text',
349            ),
350            $version,
351            true
352        );
353
354        if ( ! ( new Host() )->is_wpcom_simple() ) { // Same object is already used in gutenberg-wpcom plugin.
355            wp_localize_script(
356                'wpcom-block-editor-default-editor-script',
357                'wpcomGutenberg',
358                array(
359                    'richTextToolbar' => array(
360                        'justify'   => __( 'Justify', 'jetpack-mu-wpcom' ),
361                        'underline' => __( 'Underline', 'jetpack-mu-wpcom' ),
362                    ),
363                )
364            );
365        }
366
367        wp_enqueue_script(
368            'wpcom-block-editor-wpcom-editor-script',
369            $debug
370                ? '//widgets.wp.com/wpcom-block-editor/wpcom.editor.js?minify=false'
371                : '//widgets.wp.com/wpcom-block-editor/wpcom.editor.min.js',
372            array(
373                'lodash',
374                'react',
375                'wp-block-editor',
376                'wp-blocks',
377                'wp-components',
378                'wp-compose',
379                'wp-data',
380                'wp-dom-ready',
381                'wp-element',
382                'wp-hooks',
383                'wp-i18n',
384                'wp-plugins',
385                'wp-primitives',
386                'wp-url',
387            ),
388            $version,
389            true
390        );
391        wp_enqueue_style(
392            'wpcom-block-editor-wpcom-editor-styles',
393            $debug
394                ? '//widgets.wp.com/wpcom-block-editor/wpcom.editor.css?minify=false'
395                : '//widgets.wp.com/wpcom-block-editor/wpcom.editor.min.css',
396            array(),
397            $version
398        );
399
400        if ( $this->is_iframed_block_editor() ) {
401            wp_enqueue_script(
402                'wpcom-block-editor-calypso-editor-script',
403                $debug
404                    ? '//widgets.wp.com/wpcom-block-editor/calypso.editor.js?minify=false'
405                    : '//widgets.wp.com/wpcom-block-editor/calypso.editor.min.js',
406                array(
407                    'jquery',
408                    'lodash',
409                    'react',
410                    'wp-blocks',
411                    'wp-data',
412                    'wp-hooks',
413                    'wp-tinymce',
414                    'wp-url',
415                ),
416                $version,
417                true
418            );
419
420            wp_enqueue_style(
421                'wpcom-block-editor-calypso-editor-styles',
422                $debug
423                    ? '//widgets.wp.com/wpcom-block-editor/calypso.editor.css?minify=false'
424                    : '//widgets.wp.com/wpcom-block-editor/calypso.editor.min.css',
425                array(),
426                $version
427            );
428        }
429    }
430
431    /**
432     * Enqueues the WordPress.com block editor integration assets for both editor and front-end.
433     */
434    public function enqueue_block_assets() {
435        // These styles are manually copied from //widgets.wp.com/wpcom-block-editor/default.view.css in order to
436        // improve the performance by avoiding an extra network request to download the CSS file on every page.
437        wp_add_inline_style( 'wp-block-library', '.has-text-align-justify{text-align:justify;}' );
438    }
439
440    /**
441     * Determines if the current $post contains a justified paragraph block.
442     *
443     * @return boolean true if justified paragraph is found, false otherwise.
444     */
445    public function has_justified_block() {
446        global $post;
447        if ( ! $post instanceof WP_Post ) {
448            return false;
449        }
450
451        if ( ! has_blocks( $post ) ) {
452            return false;
453        }
454
455        return str_contains( $post->post_content, '<!-- wp:paragraph {"align":"justify"' );
456    }
457
458    /**
459     * Register the Tiny MCE plugins for the WordPress.com block editor integration.
460     *
461     * @param array $plugin_array An array of external Tiny MCE plugins.
462     * @return array External TinyMCE plugins.
463     */
464    public function add_tinymce_plugins( $plugin_array ) {
465        if ( $this->is_iframed_block_editor() ) {
466            $debug = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG;
467
468            $plugin_array['gutenberg-wpcom-iframe-media-modal'] = add_query_arg(
469                'v',
470                gmdate( 'YW' ),
471                $debug
472                    ? '//widgets.wp.com/wpcom-block-editor/calypso.tinymce.js?minify=false'
473                    : '//widgets.wp.com/wpcom-block-editor/calypso.tinymce.min.js'
474            );
475        }
476
477        return $plugin_array;
478    }
479
480    /**
481     * Ensures the authentication cookies are designated for cross-site access.
482     */
483    private function enable_cross_site_auth_cookies() {
484        /**
485         * Allow plugins to disable the cross-site auth cookies.
486         *
487         * @since jetpack-8.1.1
488         *
489         * @param false bool Whether auth cookies should be disabled for cross-site access. False by default.
490         */
491        if ( apply_filters( 'jetpack_disable_cross_site_auth_cookies', false ) ) {
492            return;
493        }
494
495        add_action( 'set_auth_cookie', array( $this, 'set_samesite_auth_cookies' ), 10, 5 );
496        add_action( 'set_logged_in_cookie', array( $this, 'set_samesite_logged_in_cookies' ), 10, 4 );
497        add_filter( 'send_auth_cookies', array( $this, 'maybe_send_cookies' ), 9999 );
498    }
499
500    /**
501     * Checks if we've stored any cookies to send and then sends them
502     * if the send_auth_cookies value is true.
503     *
504     * @param bool $send_cookies The filtered value that determines whether to send auth cookies.
505     */
506    public function maybe_send_cookies( $send_cookies ) {
507
508        if ( ! empty( $this->set_cookie_args ) && $send_cookies ) {
509            array_walk(
510                $this->set_cookie_args,
511                function ( $cookie ) {
512                    call_user_func_array( 'setcookie', $cookie );
513                }
514            );
515            $this->set_cookie_args = array();
516            return false;
517        }
518
519        return $send_cookies;
520    }
521
522    /**
523     * Gets the SameSite attribute to use in auth cookies.
524     *
525     * @param  bool $secure Whether the connection is secure.
526     * @return string SameSite attribute to use on auth cookies.
527     */
528    public function get_samesite_attr_for_auth_cookies( $secure ) {
529        $samesite = $secure ? 'None' : 'Lax';
530        /**
531         * Filters the SameSite attribute to use in auth cookies.
532         *
533         * @param string $samesite SameSite attribute to use in auth cookies.
534         *
535         * @since jetpack-8.1.1
536         */
537        $samesite = apply_filters( 'jetpack_auth_cookie_samesite', $samesite );
538
539        return $samesite;
540    }
541
542    /**
543     * Generates cross-site auth cookies so they can be accessed by WordPress.com.
544     *
545     * @param string $auth_cookie Authentication cookie value.
546     * @param int    $expire      The time the login grace period expires as a UNIX timestamp.
547     *                            Default is 12 hours past the cookie's expiration time.
548     * @param int    $expiration  The time when the authentication cookie expires as a UNIX timestamp.
549     *                            Default is 14 days from now.
550     * @param int    $user_id     User ID.
551     * @param string $scheme      Authentication scheme. Values include 'auth' or 'secure_auth'.
552     */
553    public function set_samesite_auth_cookies( $auth_cookie, $expire, $expiration, $user_id, $scheme ) {
554        if ( $scheme && is_scalar( $scheme ) && str_starts_with( (string) $scheme, 'secure_' ) ) {
555            $secure           = true;
556            $auth_cookie_name = SECURE_AUTH_COOKIE;
557        } else {
558            $secure           = false;
559            $auth_cookie_name = AUTH_COOKIE;
560        }
561        $samesite = $this->get_samesite_attr_for_auth_cookies( $secure );
562
563        $this->set_cookie_args[] = array(
564            $auth_cookie_name,
565            $auth_cookie,
566            array(
567                'expires'  => $expire,
568                'path'     => PLUGINS_COOKIE_PATH,
569                'domain'   => COOKIE_DOMAIN,
570                'secure'   => $secure,
571                'httponly' => true,
572                'samesite' => $samesite,
573            ),
574        );
575
576        $this->set_cookie_args[] = array(
577            $auth_cookie_name,
578            $auth_cookie,
579            array(
580                'expires'  => $expire,
581                'path'     => ADMIN_COOKIE_PATH,
582                'domain'   => COOKIE_DOMAIN,
583                'secure'   => $secure,
584                'httponly' => true,
585                'samesite' => $samesite,
586            ),
587        );
588    }
589
590    /**
591     * Generates cross-site logged in cookies so they can be accessed by WordPress.com.
592     *
593     * @param string $logged_in_cookie The logged-in cookie value.
594     * @param int    $expire           The time the login grace period expires as a UNIX timestamp.
595     *                                 Default is 12 hours past the cookie's expiration time.
596     * @param int    $expiration       The time when the logged-in cookie expires as a UNIX timestamp.
597     *                                 Default is 14 days from now.
598     * @param int    $user_id          User ID.
599     */
600    public function set_samesite_logged_in_cookies( $logged_in_cookie, $expire, $expiration, $user_id ) {
601        $secure = is_ssl();
602
603        // Front-end cookie is secure when the auth cookie is secure and the site's home URL is forced HTTPS.
604        $secure_logged_in_cookie = $secure && 'https' === wp_parse_url( get_option( 'home' ), PHP_URL_SCHEME );
605
606        /** This filter is documented in core/src/wp-includes/pluggable.php */
607        $secure = apply_filters( 'secure_auth_cookie', $secure, $user_id );
608
609        /** This filter is documented in core/src/wp-includes/pluggable.php */
610        $secure_logged_in_cookie = apply_filters( 'secure_logged_in_cookie', $secure_logged_in_cookie, $user_id, $secure );
611
612        $samesite = $this->get_samesite_attr_for_auth_cookies( $secure_logged_in_cookie );
613
614        $this->set_cookie_args[] = array(
615            LOGGED_IN_COOKIE,
616            $logged_in_cookie,
617            array(
618                'expires'  => $expire,
619                'path'     => COOKIEPATH,
620                'domain'   => COOKIE_DOMAIN,
621                'secure'   => $secure_logged_in_cookie,
622                'httponly' => true,
623                'samesite' => $samesite,
624            ),
625        );
626
627        if ( COOKIEPATH !== SITECOOKIEPATH ) {
628            $this->set_cookie_args[] = array(
629                LOGGED_IN_COOKIE,
630                $logged_in_cookie,
631                array(
632                    'expires'  => $expire,
633                    'path'     => SITECOOKIEPATH,
634                    'domain'   => COOKIE_DOMAIN,
635                    'secure'   => $secure_logged_in_cookie,
636                    'httponly' => true,
637                    'samesite' => $samesite,
638                ),
639            );
640        }
641    }
642}
643
644Jetpack_WPCOM_Block_Editor::init();