Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
86.44% covered (warning)
86.44%
102 / 118
33.33% covered (danger)
33.33%
1 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
Connection_Notice
86.44% covered (warning)
86.44%
102 / 118
33.33% covered (danger)
33.33%
1 / 3
18.81
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 initialize_notices
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
42
 delete_user_update_connection_owner_notice
96.12% covered (success)
96.12%
99 / 103
0.00% covered (danger)
0.00%
0 / 1
10
1<?php
2/**
3 * Admin connection notices.
4 *
5 * @package automattic/jetpack-admin-ui
6 */
7
8namespace Automattic\Jetpack\Connection;
9
10use Automattic\Jetpack\Redirect;
11use Automattic\Jetpack\Tracking;
12
13/**
14 * Admin connection notices.
15 *
16 * @phan-constructor-used-for-side-effects
17 */
18class Connection_Notice {
19
20    /**
21     * Whether the class has been initialized.
22     *
23     * @var bool
24     */
25    private static $is_initialized = false;
26
27    /**
28     * The constructor.
29     */
30    public function __construct() {
31        if ( ! static::$is_initialized ) {
32            add_action( 'current_screen', array( $this, 'initialize_notices' ) );
33            static::$is_initialized = true;
34        }
35    }
36
37    /**
38     * Initialize the notices if needed.
39     *
40     * @param \WP_Screen $screen WP Core's screen object.
41     *
42     * @return void
43     */
44    public function initialize_notices( $screen ) {
45        if ( in_array(
46            $screen->id,
47            array(
48                'jetpack_page_akismet-key-config',
49                'admin_page_jetpack_modules',
50            ),
51            true
52        ) ) {
53            return;
54        }
55
56        /*
57         * phpcs:disable WordPress.Security.NonceVerification.Recommended
58         *
59         * This function is firing within wp-admin and checks (below) if it is in the midst of a deletion on the users
60         * page. Nonce will be already checked by WordPress, so we do not need to check ourselves.
61         */
62
63        if ( isset( $screen->base ) && 'users' === $screen->base
64            && isset( $_REQUEST['action'] ) && 'delete' === $_REQUEST['action']
65        ) {
66            add_action( 'admin_notices', array( $this, 'delete_user_update_connection_owner_notice' ) );
67        }
68    }
69
70    /**
71     * This is an entire admin notice dedicated to messaging and handling of the case where a user is trying to delete
72     * the connection owner.
73     */
74    public function delete_user_update_connection_owner_notice() {
75        // Get connection owner or bail.
76        $connection_manager  = new Manager();
77        $connection_owner_id = $connection_manager->get_connection_owner_id();
78        if ( ! $connection_owner_id ) {
79            return;
80        }
81        $connection_owner_userdata = get_userdata( $connection_owner_id );
82
83        // Bail if we're not trying to delete connection owner.
84        $user_ids_to_delete = array();
85        if ( isset( $_REQUEST['users'] ) ) {
86            $user_ids_to_delete = array_map( 'sanitize_text_field', wp_unslash( $_REQUEST['users'] ) );
87        } elseif ( isset( $_REQUEST['user'] ) ) {
88            $user_ids_to_delete[] = sanitize_text_field( wp_unslash( $_REQUEST['user'] ) );
89        }
90
91        // phpcs:enable
92        $user_ids_to_delete        = array_map( 'absint', $user_ids_to_delete );
93        $deleting_connection_owner = in_array( $connection_owner_id, (array) $user_ids_to_delete, true );
94        if ( ! $deleting_connection_owner ) {
95            return;
96        }
97
98        // Bail if they're trying to delete themselves to avoid confusion.
99        if ( get_current_user_id() === $connection_owner_id ) {
100            return;
101        }
102
103        $tracking = new Tracking();
104
105        // Track it!
106        if ( method_exists( $tracking, 'record_user_event' ) ) {
107            $tracking->record_user_event( 'delete_connection_owner_notice_view' );
108        }
109
110        $connected_admins = $connection_manager->get_connected_users( 'jetpack_disconnect' );
111        $user             = is_a( $connection_owner_userdata, 'WP_User' ) ? esc_html( $connection_owner_userdata->data->user_login ) : '';
112
113        echo "<div class='notice notice-warning' id='jetpack-notice-switch-connection-owner'>";
114        echo '<h2>' . esc_html__( 'Important notice about your Jetpack connection:', 'jetpack-connection' ) . '</h2>';
115        echo '<p>' . sprintf(
116            /* translators: WordPress User, if available. */
117            esc_html__( 'Warning! You are about to delete the Jetpack connection owner (%s) for this site, which may cause some of your Jetpack features to stop working.', 'jetpack-connection' ),
118            esc_html( $user )
119        ) . '</p>';
120
121        if ( ! empty( $connected_admins ) && count( $connected_admins ) > 1 ) {
122            echo '<form id="jp-switch-connection-owner" action="" method="post">';
123            echo "<label for='owner'>" . esc_html__( 'You can choose to transfer connection ownership to one of these already-connected admins:', 'jetpack-connection' ) . ' </label>';
124
125            $connected_admin_ids = array_map(
126                function ( $connected_admin ) {
127                    return $connected_admin->ID;
128                },
129                $connected_admins
130            );
131
132            wp_dropdown_users(
133                array(
134                    'name'    => 'owner',
135                    'include' => array_diff( $connected_admin_ids, array( $connection_owner_id ) ),
136                    'show'    => 'display_name_with_login',
137                )
138            );
139
140            echo '<p>';
141            submit_button( esc_html__( 'Set new connection owner', 'jetpack-connection' ), 'primary', 'jp-switch-connection-owner-submit', false );
142            echo '</p>';
143
144            echo "<div id='jp-switch-user-results'></div>";
145            echo '</form>';
146            ?>
147            <script type="text/javascript">
148                ( function() {
149                    const switchOwnerButton = document.getElementById('jp-switch-connection-owner');
150                    if ( ! switchOwnerButton ) {
151                        return;
152                    }
153
154                    switchOwnerButton.addEventListener( 'submit', function ( e ) {
155                        e.preventDefault();
156
157                        const submitBtn = document.getElementById('jp-switch-connection-owner-submit');
158                        submitBtn.disabled = true;
159
160                        const results = document.getElementById('jp-switch-user-results');
161                        results.innerHTML = '';
162                        results.classList.remove( 'error-message' );
163
164                        const handleAPIError = ( message ) => {
165                            submitBtn.disabled = false;
166
167                            results.classList.add( 'error-message' );
168                            results.innerHTML = message || "<?php esc_html_e( 'Something went wrong. Please try again.', 'jetpack-connection' ); ?>";
169                        }
170
171                        fetch(
172                            <?php echo wp_json_encode( esc_url_raw( get_rest_url() . 'jetpack/v4/connection/owner' ), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?>,
173                            {
174                                method: 'POST',
175                                headers: {
176                                    'X-WP-Nonce': <?php echo wp_json_encode( wp_create_nonce( 'wp_rest' ), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?>,
177                                },
178                                body: new URLSearchParams( new FormData( this ) ),
179                            }
180                        )
181                            .then( response => response.json() )
182                            .then( data => {
183                                if ( data.hasOwnProperty( 'code' ) && data.code === 'success' ) {
184                                    // Owner successfully changed.
185                                    results.innerHTML = <?php echo wp_json_encode( esc_html__( 'Success!', 'jetpack-connection' ), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?>;
186                                    setTimeout(function () {
187                                        document.getElementById( 'jetpack-notice-switch-connection-owner' ).style.display = 'none';
188                                    }, 1000);
189
190                                    return;
191                                }
192
193                                handleAPIError( data?.message );
194                            } )
195                            .catch( () => handleAPIError() );
196                    });
197                } )();
198            </script>
199            <?php
200        } else {
201            echo '<p>' . esc_html__( 'Every Jetpack site needs at least one connected admin for the features to work properly. Please connect to your WordPress.com account via the button below. Once you connect, you may refresh this page to see an option to change the connection owner.', 'jetpack-connection' ) . '</p>';
202            $connect_url = $connection_manager->get_authorization_url();
203            $connect_url = add_query_arg( 'from', 'delete_connection_owner_notice', $connect_url );
204            echo "<a href='" . esc_url( $connect_url ) . "' target='_blank' rel='noopener noreferrer' class='button-primary'>" . esc_html__( 'Connect to WordPress.com', 'jetpack-connection' ) . '</a>';
205        }
206
207        echo '<p>';
208        printf(
209            wp_kses(
210            /* translators: URL to Jetpack support doc regarding the primary user. */
211                __( "<a href='%s' target='_blank' rel='noopener noreferrer'>Learn more</a> about the connection owner and what will break if you do not have one.", 'jetpack-connection' ),
212                array(
213                    'a' => array(
214                        'href'   => true,
215                        'target' => true,
216                        'rel'    => true,
217                    ),
218                )
219            ),
220            esc_url( Redirect::get_url( 'jetpack-support-primary-user' ) )
221        );
222        echo '</p>';
223        echo '<p>';
224        printf(
225            wp_kses(
226            /* translators: URL to contact Jetpack support. */
227                __( 'As always, feel free to <a href="%s" target="_blank" rel="noopener noreferrer">contact our support team</a> if you have any questions.', 'jetpack-connection' ),
228                array(
229                    'a' => array(
230                        'href'   => true,
231                        'target' => true,
232                        'rel'    => true,
233                    ),
234                )
235            ),
236            esc_url( Redirect::get_url( 'jetpack-contact-support' ) )
237        );
238        echo '</p>';
239        echo '</div>';
240    }
241}