Make WordPress Core

Ticket #44952: user.php

File user.php, 49.2 KB (added by eArtboard, 5 years ago)
Line 
1<?php
2/**
3 * WordPress user administration API.
4 *
5 * @package WordPress
6 * @subpackage Administration
7 */
8
9/**
10 * Creates a new user from the "Users" form using $_POST information.
11 *
12 * @since 2.0.0
13 *
14 * @return int|WP_Error WP_Error or User ID.
15 */
16function add_user() {
17        return edit_user();
18}
19
20/**
21 * Edit user settings based on contents of $_POST
22 *
23 * Used on user-edit.php and profile.php to manage and process user options, passwords etc.
24 *
25 * @since 2.0.0
26 *
27 * @param int $user_id Optional. User ID.
28 * @return int|WP_Error user id of the updated user
29 */
30function edit_user( $user_id = 0 ) {
31        $wp_roles = wp_roles();
32        $user = new stdClass;
33        if ( $user_id ) {
34                $update = true;
35                $user->ID = (int) $user_id;
36                $userdata = get_userdata( $user_id );
37                $user->user_login = wp_slash( $userdata->user_login );
38        } else {
39                $update = false;
40        }
41
42        if ( !$update && isset( $_POST['user_login'] ) )
43                $user->user_login = sanitize_user($_POST['user_login'], true);
44
45        $pass1 = $pass2 = '';
46        if ( isset( $_POST['pass1'] ) )
47                $pass1 = $_POST['pass1'];
48        if ( isset( $_POST['pass2'] ) )
49                $pass2 = $_POST['pass2'];
50
51        if ( isset( $_POST['role'] ) && current_user_can( 'edit_users' ) ) {
52                $new_role = sanitize_text_field( $_POST['role'] );
53                $potential_role = isset($wp_roles->role_objects[$new_role]) ? $wp_roles->role_objects[$new_role] : false;
54                // Don't let anyone with 'edit_users' (admins) edit their own role to something without it.
55                // Multisite super admins can freely edit their blog roles -- they possess all caps.
56                if ( ( is_multisite() && current_user_can( 'manage_sites' ) ) || $user_id != get_current_user_id() || ($potential_role && $potential_role->has_cap( 'edit_users' ) ) )
57                        $user->role = $new_role;
58
59                // If the new role isn't editable by the logged-in user die with error
60                $editable_roles = get_editable_roles();
61                if ( ! empty( $new_role ) && empty( $editable_roles[$new_role] ) )
62                        wp_die( __( 'Sorry, you are not allowed to give users that role.' ), 403 );
63        }
64
65        if ( isset( $_POST['email'] ))
66                $user->user_email = sanitize_text_field( wp_unslash( $_POST['email'] ) );
67        if ( isset( $_POST['url'] ) ) {
68                if ( empty ( $_POST['url'] ) || $_POST['url'] == 'http://' ) {
69                        $user->user_url = '';
70                } else {
71                        $user->user_url = esc_url_raw( $_POST['url'] );
72                        $protocols = implode( '|', array_map( 'preg_quote', wp_allowed_protocols() ) );
73                        $user->user_url = preg_match('/^(' . $protocols . '):/is', $user->user_url) ? $user->user_url : 'http://'.$user->user_url;
74                }
75        }
76        if ( isset( $_POST['first_name'] ) )
77                $user->first_name = sanitize_text_field( $_POST['first_name'] );
78        if ( isset( $_POST['last_name'] ) )
79                $user->last_name = sanitize_text_field( $_POST['last_name'] );
80        if ( isset( $_POST['nickname'] ) )
81                $user->nickname = sanitize_text_field( $_POST['nickname'] );
82        if ( isset( $_POST['display_name'] ) )
83                $user->display_name = sanitize_text_field( $_POST['display_name'] );
84
85        if ( isset( $_POST['description'] ) )
86                $user->description = trim( $_POST['description'] );
87
88        foreach ( wp_get_user_contact_methods( $user ) as $method => $name ) {
89                if ( isset( $_POST[$method] ))
90                        $user->$method = sanitize_text_field( $_POST[$method] );
91        }
92
93        if ( $update ) {
94                $user->rich_editing = isset( $_POST['rich_editing'] ) && 'false' === $_POST['rich_editing'] ? 'false' : 'true';
95                $user->syntax_highlighting = isset( $_POST['syntax_highlighting'] ) && 'false' === $_POST['syntax_highlighting'] ? 'false' : 'true';
96                $user->admin_color = isset( $_POST['admin_color'] ) ? sanitize_text_field( $_POST['admin_color'] ) : 'fresh';
97                $user->show_admin_bar_front = isset( $_POST['admin_bar_front'] ) ? 'true' : 'false';
98                $user->locale = '';
99
100                if ( isset( $_POST['locale'] ) ) {
101                        $locale = sanitize_text_field( $_POST['locale'] );
102                        if ( 'site-default' === $locale ) {
103                                $locale = '';
104                        } elseif ( '' === $locale ) {
105                                $locale = 'en_US';
106                        } elseif ( ! in_array( $locale, get_available_languages(), true ) ) {
107                                $locale = '';
108                        }
109
110                        $user->locale = $locale;
111                }
112        }
113
114        $user->comment_shortcuts = isset( $_POST['comment_shortcuts'] ) && 'true' == $_POST['comment_shortcuts'] ? 'true' : '';
115
116        $user->use_ssl = 0;
117        if ( !empty($_POST['use_ssl']) )
118                $user->use_ssl = 1;
119
120        $errors = new WP_Error();
121
122        /* checking that username has been typed */
123        if ( $user->user_login == '' )
124                $errors->add( 'user_login', __( '<strong>ERROR</strong>: Please enter a username.' ) );
125
126        /* checking that nickname has been typed */
127        if ( $update && empty( $user->nickname ) ) {
128                $errors->add( 'nickname', __( '<strong>ERROR</strong>: Please enter a nickname.' ) );
129        }
130
131        /**
132         * Fires before the password and confirm password fields are checked for congruity.
133         *
134         * @since 1.5.1
135         *
136         * @param string $user_login The username.
137         * @param string $pass1     The password (passed by reference).
138         * @param string $pass2     The confirmed password (passed by reference).
139         */
140        do_action_ref_array( 'check_passwords', array( $user->user_login, &$pass1, &$pass2 ) );
141
142        // Check for blank password when adding a user.
143        if ( ! $update && empty( $pass1 ) ) {
144                $errors->add( 'pass', __( '<strong>ERROR</strong>: Please enter a password.' ), array( 'form-field' => 'pass1' ) );
145        }
146
147        // Check for "\" in password.
148        if ( false !== strpos( wp_unslash( $pass1 ), "\\" ) ) {
149                $errors->add( 'pass', __( '<strong>ERROR</strong>: Passwords may not contain the character "\\".' ), array( 'form-field' => 'pass1' ) );
150        }
151
152        // Checking the password has been typed twice the same.
153        if ( ( $update || ! empty( $pass1 ) ) && $pass1 != $pass2 ) {
154                $errors->add( 'pass', __( '<strong>ERROR</strong>: Please enter the same password in both password fields.' ), array( 'form-field' => 'pass1' ) );
155        }
156
157        if ( !empty( $pass1 ) )
158                $user->user_pass = $pass1;
159
160        if ( !$update && isset( $_POST['user_login'] ) && !validate_username( $_POST['user_login'] ) )
161                $errors->add( 'user_login', __( '<strong>ERROR</strong>: This username is invalid because it uses illegal characters. Please enter a valid username.' ));
162
163        if ( !$update && username_exists( $user->user_login ) )
164                $errors->add( 'user_login', __( '<strong>ERROR</strong>: This username is already registered. Please choose another one.' ));
165
166        /** This filter is documented in wp-includes/user.php */
167        $illegal_logins = (array) apply_filters( 'illegal_user_logins', array() );
168
169        if ( in_array( strtolower( $user->user_login ), array_map( 'strtolower', $illegal_logins ) ) ) {
170                $errors->add( 'invalid_username', __( '<strong>ERROR</strong>: Sorry, that username is not allowed.' ) );
171        }
172
173        /* checking email address */
174        if ( empty( $user->user_email ) ) {
175                $errors->add( 'empty_email', __( '<strong>ERROR</strong>: Please enter an email address.' ), array( 'form-field' => 'email' ) );
176        } elseif ( !is_email( $user->user_email ) ) {
177                $errors->add( 'invalid_email', __( '<strong>ERROR</strong>: The email address isn&#8217;t correct.' ), array( 'form-field' => 'email' ) );
178        } elseif ( ( $owner_id = email_exists($user->user_email) ) && ( !$update || ( $owner_id != $user->ID ) ) ) {
179                $errors->add( 'email_exists', __('<strong>ERROR</strong>: This email is already registered, please choose another one.'), array( 'form-field' => 'email' ) );
180        }
181
182        /**
183         * Fires before user profile update errors are returned.
184         *
185         * @since 2.8.0
186         *
187         * @param WP_Error $errors WP_Error object (passed by reference).
188         * @param bool     $update  Whether this is a user update.
189         * @param stdClass $user   User object (passed by reference).
190         */
191        do_action_ref_array( 'user_profile_update_errors', array( &$errors, $update, &$user ) );
192
193        if ( $errors->get_error_codes() )
194                return $errors;
195
196        if ( $update ) {
197                $user_id = wp_update_user( $user );
198        } else {
199                $user_id = wp_insert_user( $user );
200                $notify  = isset( $_POST['send_user_notification'] ) ? 'both' : 'admin';
201
202                /**
203                  * Fires after a new user has been created.
204                  *
205                  * @since 4.4.0
206                  *
207                  * @param int    $user_id ID of the newly created user.
208                  * @param string $notify  Type of notification that should happen. See wp_send_new_user_notifications()
209                  *                        for more information on possible values.
210                  */
211                do_action( 'edit_user_created_user', $user_id, $notify );
212        }
213        return $user_id;
214}
215
216/**
217 * Fetch a filtered list of user roles that the current user is
218 * allowed to edit.
219 *
220 * Simple function who's main purpose is to allow filtering of the
221 * list of roles in the $wp_roles object so that plugins can remove
222 * inappropriate ones depending on the situation or user making edits.
223 * Specifically because without filtering anyone with the edit_users
224 * capability can edit others to be administrators, even if they are
225 * only editors or authors. This filter allows admins to delegate
226 * user management.
227 *
228 * @since 2.8.0
229 *
230 * @return array
231 */
232function get_editable_roles() {
233        $all_roles = wp_roles()->roles;
234
235        /**
236         * Filters the list of editable roles.
237         *
238         * @since 2.8.0
239         *
240         * @param array $all_roles List of roles.
241         */
242        $editable_roles = apply_filters( 'editable_roles', $all_roles );
243
244        return $editable_roles;
245}
246
247/**
248 * Retrieve user data and filter it.
249 *
250 * @since 2.0.5
251 *
252 * @param int $user_id User ID.
253 * @return WP_User|bool WP_User object on success, false on failure.
254 */
255function get_user_to_edit( $user_id ) {
256        $user = get_userdata( $user_id );
257
258        if ( $user )
259                $user->filter = 'edit';
260
261        return $user;
262}
263
264/**
265 * Retrieve the user's drafts.
266 *
267 * @since 2.0.0
268 *
269 * @global wpdb $wpdb WordPress database abstraction object.
270 *
271 * @param int $user_id User ID.
272 * @return array
273 */
274function get_users_drafts( $user_id ) {
275        global $wpdb;
276        $query = $wpdb->prepare("SELECT ID, post_title FROM $wpdb->posts WHERE post_type = 'post' AND post_status = 'draft' AND post_author = %d ORDER BY post_modified DESC", $user_id);
277
278        /**
279         * Filters the user's drafts query string.
280         *
281         * @since 2.0.0
282         *
283         * @param string $query The user's drafts query string.
284         */
285        $query = apply_filters( 'get_users_drafts', $query );
286        return $wpdb->get_results( $query );
287}
288
289/**
290 * Remove user and optionally reassign posts and links to another user.
291 *
292 * If the $reassign parameter is not assigned to a User ID, then all posts will
293 * be deleted of that user. The action {@see 'delete_user'} that is passed the User ID
294 * being deleted will be run after the posts are either reassigned or deleted.
295 * The user meta will also be deleted that are for that User ID.
296 *
297 * @since 2.0.0
298 *
299 * @global wpdb $wpdb WordPress database abstraction object.
300 *
301 * @param int $id User ID.
302 * @param int $reassign Optional. Reassign posts and links to new User ID.
303 * @return bool True when finished.
304 */
305function wp_delete_user( $id, $reassign = null ) {
306        global $wpdb;
307
308        if ( ! is_numeric( $id ) ) {
309                return false;
310        }
311
312        $id = (int) $id;
313        $user = new WP_User( $id );
314
315        if ( !$user->exists() )
316                return false;
317
318        // Normalize $reassign to null or a user ID. 'novalue' was an older default.
319        if ( 'novalue' === $reassign ) {
320                $reassign = null;
321        } elseif ( null !== $reassign ) {
322                $reassign = (int) $reassign;
323        }
324
325        /**
326         * Fires immediately before a user is deleted from the database.
327         *
328         * @since 2.0.0
329         *
330         * @param int      $id       ID of the user to delete.
331         * @param int|null $reassign ID of the user to reassign posts and links to.
332         *                           Default null, for no reassignment.
333         */
334        do_action( 'delete_user', $id, $reassign );
335
336        if ( null === $reassign ) {
337                $post_types_to_delete = array();
338                foreach ( get_post_types( array(), 'objects' ) as $post_type ) {
339                        if ( $post_type->delete_with_user ) {
340                                $post_types_to_delete[] = $post_type->name;
341                        } elseif ( null === $post_type->delete_with_user && post_type_supports( $post_type->name, 'author' ) ) {
342                                $post_types_to_delete[] = $post_type->name;
343                        }
344                }
345
346                /**
347                 * Filters the list of post types to delete with a user.
348                 *
349                 * @since 3.4.0
350                 *
351                 * @param array $post_types_to_delete Post types to delete.
352                 * @param int   $id                   User ID.
353                 */
354                $post_types_to_delete = apply_filters( 'post_types_to_delete_with_user', $post_types_to_delete, $id );
355                $post_types_to_delete = implode( "', '", $post_types_to_delete );
356                $post_ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_author = %d AND post_type IN ('$post_types_to_delete')", $id ) );
357                if ( $post_ids ) {
358                        foreach ( $post_ids as $post_id )
359                                wp_delete_post( $post_id );
360                }
361
362                // Clean links
363                $link_ids = $wpdb->get_col( $wpdb->prepare("SELECT link_id FROM $wpdb->links WHERE link_owner = %d", $id) );
364
365                if ( $link_ids ) {
366                        foreach ( $link_ids as $link_id )
367                                wp_delete_link($link_id);
368                }
369        } else {
370                $post_ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_author = %d", $id ) );
371                $wpdb->update( $wpdb->posts, array('post_author' => $reassign), array('post_author' => $id) );
372                if ( ! empty( $post_ids ) ) {
373                        foreach ( $post_ids as $post_id )
374                                clean_post_cache( $post_id );
375                }
376                $link_ids = $wpdb->get_col( $wpdb->prepare("SELECT link_id FROM $wpdb->links WHERE link_owner = %d", $id) );
377                $wpdb->update( $wpdb->links, array('link_owner' => $reassign), array('link_owner' => $id) );
378                if ( ! empty( $link_ids ) ) {
379                        foreach ( $link_ids as $link_id )
380                                clean_bookmark_cache( $link_id );
381                }
382        }
383
384        // FINALLY, delete user
385        if ( is_multisite() ) {
386                remove_user_from_blog( $id, get_current_blog_id() );
387        } else {
388                $meta = $wpdb->get_col( $wpdb->prepare( "SELECT umeta_id FROM $wpdb->usermeta WHERE user_id = %d", $id ) );
389                foreach ( $meta as $mid )
390                        delete_metadata_by_mid( 'user', $mid );
391
392                $wpdb->delete( $wpdb->users, array( 'ID' => $id ) );
393        }
394
395        clean_user_cache( $user );
396
397        /**
398         * Fires immediately after a user is deleted from the database.
399         *
400         * @since 2.9.0
401         *
402         * @param int      $id       ID of the deleted user.
403         * @param int|null $reassign ID of the user to reassign posts and links to.
404         *                           Default null, for no reassignment.
405         */
406        do_action( 'deleted_user', $id, $reassign );
407
408        return true;
409}
410
411/**
412 * Remove all capabilities from user.
413 *
414 * @since 2.1.0
415 *
416 * @param int $id User ID.
417 */
418function wp_revoke_user($id) {
419        $id = (int) $id;
420
421        $user = new WP_User($id);
422        $user->remove_all_caps();
423}
424
425/**
426 * @since 2.8.0
427 *
428 * @global int $user_ID
429 *
430 * @param false $errors Deprecated.
431 */
432function default_password_nag_handler($errors = false) {
433        global $user_ID;
434        // Short-circuit it.
435        if ( ! get_user_option('default_password_nag') )
436                return;
437
438        // get_user_setting = JS saved UI setting. else no-js-fallback code.
439        if ( 'hide' == get_user_setting('default_password_nag') || isset($_GET['default_password_nag']) && '0' == $_GET['default_password_nag'] ) {
440                delete_user_setting('default_password_nag');
441                update_user_option($user_ID, 'default_password_nag', false, true);
442        }
443}
444
445/**
446 * @since 2.8.0
447 *
448 * @param int    $user_ID
449 * @param object $old_data
450 */
451function default_password_nag_edit_user($user_ID, $old_data) {
452        // Short-circuit it.
453        if ( ! get_user_option('default_password_nag', $user_ID) )
454                return;
455
456        $new_data = get_userdata($user_ID);
457
458        // Remove the nag if the password has been changed.
459        if ( $new_data->user_pass != $old_data->user_pass ) {
460                delete_user_setting('default_password_nag');
461                update_user_option($user_ID, 'default_password_nag', false, true);
462        }
463}
464
465/**
466 * @since 2.8.0
467 *
468 * @global string $pagenow
469 */
470function default_password_nag() {
471        global $pagenow;
472        // Short-circuit it.
473        if ( 'profile.php' == $pagenow || ! get_user_option('default_password_nag') )
474                return;
475
476        echo '<div class="error default-password-nag">';
477        echo '<p>';
478        echo '<strong>' . __('Notice:') . '</strong> ';
479        _e('You&rsquo;re using the auto-generated password for your account. Would you like to change it?');
480        echo '</p><p>';
481        printf( '<a href="%s">' . __('Yes, take me to my profile page') . '</a> | ', get_edit_profile_url() . '#password' );
482        printf( '<a href="%s" id="default-password-nag-no">' . __('No thanks, do not remind me again') . '</a>', '?default_password_nag=0' );
483        echo '</p></div>';
484}
485
486/**
487 * @since 3.5.0
488 * @access private
489 */
490function delete_users_add_js() { ?>
491<script>
492jQuery(document).ready( function($) {
493        var submit = $('#submit').prop('disabled', true);
494        $('input[name="delete_option"]').one('change', function() {
495                submit.prop('disabled', false);
496        });
497        $('#reassign_user').focus( function() {
498                $('#delete_option1').prop('checked', true).trigger('change');
499        });
500});
501</script>
502<?php
503}
504
505/**
506 * Optional SSL preference that can be turned on by hooking to the 'personal_options' action.
507 *
508 * See the {@see 'personal_options'} action.
509 *
510 * @since 2.7.0
511 *
512 * @param object $user User data object
513 */
514function use_ssl_preference($user) {
515?>
516        <tr class="user-use-ssl-wrap">
517                <th scope="row"><?php _e('Use https')?></th>
518                <td><label for="use_ssl"><input name="use_ssl" type="checkbox" id="use_ssl" value="1" <?php checked('1', $user->use_ssl); ?> /> <?php _e('Always use https when visiting the admin'); ?></label></td>
519        </tr>
520<?php
521}
522
523/**
524 *
525 * @param string $text
526 * @return string
527 */
528function admin_created_user_email( $text ) {
529        $roles = get_editable_roles();
530        $role = $roles[ $_REQUEST['role'] ];
531        /* translators: 1: Site name, 2: site URL, 3: role */
532        return sprintf( __( 'Hi,
533You\'ve been invited to join \'%1$s\' at
534%2$s with the role of %3$s.
535If you do not want to join this site please ignore
536this email. This invitation will expire in a few days.
537
538Please click the following link to activate your user account:
539%%s' ), wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), home_url(), wp_specialchars_decode( translate_user_role( $role['name'] ) ) );
540}
541
542/**
543 * Resend an existing request and return the result.
544 *
545 * @since 4.9.6
546 * @access private
547 *
548 * @param int $request_id Request ID.
549 * @return bool|WP_Error Returns true/false based on the success of sending the email, or a WP_Error object.
550 */
551function _wp_privacy_resend_request( $request_id ) {
552        $request_id = absint( $request_id );
553        $request    = get_post( $request_id );
554
555        if ( ! $request || 'user_request' !== $request->post_type ) {
556                return new WP_Error( 'privacy_request_error', __( 'Invalid request.' ) );
557        }
558
559        $result = wp_send_user_request( $request_id );
560
561        if ( is_wp_error( $result ) ) {
562                return $result;
563        } elseif ( ! $result ) {
564                return new WP_Error( 'privacy_request_error', __( 'Unable to initiate confirmation request.' ) );
565        }
566
567        return true;
568}
569
570/**
571 * Marks a request as completed by the admin and logs the current timestamp.
572 *
573 * @since 4.9.6
574 * @access private
575 *
576 * @param  int          $request_id Request ID.
577 * @return int|WP_Error $request    Request ID on success or WP_Error.
578 */
579function _wp_privacy_completed_request( $request_id ) {
580        $request_id   = absint( $request_id );
581        $request_data = wp_get_user_request_data( $request_id );
582
583        if ( ! $request_data ) {
584                return new WP_Error( 'privacy_request_error', __( 'Invalid request.' ) );
585        }
586
587        update_post_meta( $request_id, '_wp_user_request_completed_timestamp', time() );
588
589        $request = wp_update_post( array(
590                'ID'          => $request_id,
591                'post_status' => 'request-completed',
592        ) );
593
594        return $request;
595}
596
597/**
598 * Handle list table actions.
599 *
600 * @since 4.9.6
601 * @access private
602 */
603function _wp_personal_data_handle_actions() {
604        if ( isset( $_POST['privacy_action_email_retry'] ) ) {
605                check_admin_referer( 'bulk-privacy_requests' );
606
607                $request_id = absint( current( array_keys( (array) wp_unslash( $_POST['privacy_action_email_retry'] ) ) ) );
608                $result     = _wp_privacy_resend_request( $request_id );
609
610                if ( is_wp_error( $result ) ) {
611                        add_settings_error(
612                                'privacy_action_email_retry',
613                                'privacy_action_email_retry',
614                                $result->get_error_message(),
615                                'error'
616                        );
617                } else {
618                        add_settings_error(
619                                'privacy_action_email_retry',
620                                'privacy_action_email_retry',
621                                __( 'Confirmation request sent again successfully.' ),
622                                'updated'
623                        );
624                }
625        } elseif ( isset( $_POST['action'] ) ) {
626                $action = isset( $_POST['action'] ) ? sanitize_key( wp_unslash( $_POST['action'] ) ) : '';
627
628                switch ( $action ) {
629                        case 'add_export_personal_data_request':
630                        case 'add_remove_personal_data_request':
631                                check_admin_referer( 'personal-data-request' );
632
633                                if ( ! isset( $_POST['type_of_action'], $_POST['username_or_email_for_privacy_request'] ) ) {
634                                        add_settings_error(
635                                                'action_type',
636                                                'action_type',
637                                                __( 'Invalid action.' ),
638                                                'error'
639                                        );
640                                }
641                                $action_type               = sanitize_text_field( wp_unslash( $_POST['type_of_action'] ) );
642                                $username_or_email_address = sanitize_text_field( wp_unslash( $_POST['username_or_email_for_privacy_request'] ) );
643                                $email_address             = '';
644
645                                if ( ! in_array( $action_type, _wp_privacy_action_request_types(), true ) ) {
646                                        add_settings_error(
647                                                'action_type',
648                                                'action_type',
649                                                __( 'Invalid action.' ),
650                                                'error'
651                                        );
652                                }
653
654                                if ( ! is_email( $username_or_email_address ) ) {
655                                        $user = get_user_by( 'login', $username_or_email_address );
656                                        if ( ! $user instanceof WP_User ) {
657                                                add_settings_error(
658                                                        'username_or_email_for_privacy_request',
659                                                        'username_or_email_for_privacy_request',
660                                                        __( 'Unable to add this request. A valid email address or username must be supplied.' ),
661                                                        'error'
662                                                );
663                                        } else {
664                                                $email_address = $user->user_email;
665                                        }
666                                } else {
667                                        $email_address = $username_or_email_address;
668                                }
669
670                                if ( empty( $email_address ) ) {
671                                        break;
672                                }
673
674                                $request_id = wp_create_user_request( $email_address, $action_type );
675
676                                if ( is_wp_error( $request_id ) ) {
677                                        add_settings_error(
678                                                'username_or_email_for_privacy_request',
679                                                'username_or_email_for_privacy_request',
680                                                $request_id->get_error_message(),
681                                                'error'
682                                        );
683                                        break;
684                                } elseif ( ! $request_id ) {
685                                        add_settings_error(
686                                                'username_or_email_for_privacy_request',
687                                                'username_or_email_for_privacy_request',
688                                                __( 'Unable to initiate confirmation request.' ),
689                                                'error'
690                                        );
691                                        break;
692                                }
693
694                                wp_send_user_request( $request_id );
695
696                                add_settings_error(
697                                        'username_or_email_for_privacy_request',
698                                        'username_or_email_for_privacy_request',
699                                        __( 'Confirmation request initiated successfully.' ),
700                                        'updated'
701                                );
702                                break;
703                }
704        }
705}
706
707/**
708 * Cleans up failed and expired requests before displaying the list table.
709 *
710 * @since 4.9.6
711 * @access private
712 */
713function _wp_personal_data_cleanup_requests() {
714        /** This filter is documented in wp-includes/user.php */
715        $expires        = (int) apply_filters( 'user_request_key_expiration', DAY_IN_SECONDS );
716
717        $requests_query = new WP_Query( array(
718                'post_type'      => 'user_request',
719                'posts_per_page' => -1,
720                'post_status'    => 'request-pending',
721                'fields'         => 'ids',
722                'date_query'     => array(
723                        array(
724                                'column' => 'post_modified_gmt',
725                                'before' => $expires . ' seconds ago',
726                        ),
727                ),
728        ) );
729
730        $request_ids = $requests_query->posts;
731
732        foreach ( $request_ids as $request_id ) {
733                wp_update_post( array(
734                        'ID'            => $request_id,
735                        'post_status'   => 'request-failed',
736                        'post_password' => '',
737                ) );
738        }
739}
740
741/**
742 * Personal data export.
743 *
744 * @since 4.9.6
745 * @access private
746 */
747function _wp_personal_data_export_page() {
748        if ( ! current_user_can( 'export_others_personal_data' ) ) {
749                wp_die( __( 'Sorry, you are not allowed to export personal data on this site.' ) );
750        }
751
752        _wp_personal_data_handle_actions();
753        _wp_personal_data_cleanup_requests();
754
755        // "Borrow" xfn.js for now so we don't have to create new files.
756        wp_enqueue_script( 'xfn' );
757
758        $requests_table = new WP_Privacy_Data_Export_Requests_Table( array(
759                'plural'   => 'privacy_requests',
760                'singular' => 'privacy_request',
761        ) );
762        $requests_table->process_bulk_action();
763        $requests_table->prepare_items();
764        ?>
765        <div class="wrap nosubsub">
766                <h1><?php esc_html_e( 'Export Personal Data' ); ?></h1>
767                <hr class="wp-header-end" />
768
769                <?php settings_errors(); ?>
770
771                <form method="post" class="wp-privacy-request-form">
772                        <h2><?php esc_html_e( 'Add Data Export Request' ); ?></h2>
773                        <p><?php esc_html_e( 'An email will be sent to the user at this email address asking them to verify the request.' ); ?></p>
774
775                        <div class="wp-privacy-request-form-field">
776                                <label for="username_or_email_for_privacy_request"><?php esc_html_e( 'Username or email address' ); ?></label>
777                                <input type="text" required class="regular-text" id="username_or_email_for_privacy_request" name="username_or_email_for_privacy_request" />
778                                <?php submit_button( __( 'Send Request' ), 'secondary', 'submit', false ); ?>
779                        </div>
780                        <?php wp_nonce_field( 'personal-data-request' ); ?>
781                        <input type="hidden" name="action" value="add_export_personal_data_request" />
782                        <input type="hidden" name="type_of_action" value="export_personal_data" />
783                </form>
784                <hr />
785
786                <?php $requests_table->views(); ?>
787
788                <form class="search-form wp-clearfix">
789                        <?php $requests_table->search_box( __( 'Search Requests' ), 'requests' ); ?>
790                        <input type="hidden" name="page" value="export_personal_data" />
791                        <input type="hidden" name="filter-status" value="<?php echo isset( $_REQUEST['filter-status'] ) ? esc_attr( sanitize_text_field( $_REQUEST['filter-status'] ) ) : ''; ?>" />
792                        <input type="hidden" name="orderby" value="<?php echo isset( $_REQUEST['orderby'] ) ? esc_attr( sanitize_text_field( $_REQUEST['orderby'] ) ) : ''; ?>" />
793                        <input type="hidden" name="order" value="<?php echo isset( $_REQUEST['order'] ) ? esc_attr( sanitize_text_field( $_REQUEST['order'] ) ) : ''; ?>" />
794                </form>
795
796                <form method="post">
797                        <?php
798                        $requests_table->display();
799                        $requests_table->embed_scripts();
800                        ?>
801                </form>
802        </div>
803        <?php
804}
805
806/**
807 * Personal data anonymization.
808 *
809 * @since 4.9.6
810 * @access private
811 */
812function _wp_personal_data_removal_page() {
813        /*
814         * Require both caps in order to make it explicitly clear that delegating
815         * erasure from network admins to single-site admins will give them the
816         * ability to affect global users, rather than being limited to the site
817         * that they administer.
818         */
819        if ( ! current_user_can( 'erase_others_personal_data' ) || ! current_user_can( 'delete_users' ) ) {
820                wp_die( __( 'Sorry, you are not allowed to erase data on this site.' ) );
821        }
822
823        _wp_personal_data_handle_actions();
824        _wp_personal_data_cleanup_requests();
825
826        // "Borrow" xfn.js for now so we don't have to create new files.
827        wp_enqueue_script( 'xfn' );
828
829        $requests_table = new WP_Privacy_Data_Removal_Requests_Table( array(
830                'plural'   => 'privacy_requests',
831                'singular' => 'privacy_request',
832        ) );
833
834        $requests_table->process_bulk_action();
835        $requests_table->prepare_items();
836
837        ?>
838        <div class="wrap nosubsub">
839                <h1><?php esc_html_e( 'Erase Personal Data' ); ?></h1>
840                <hr class="wp-header-end" />
841
842                <?php settings_errors(); ?>
843
844                <form method="post" class="wp-privacy-request-form">
845                        <h2><?php esc_html_e( 'Add Data Erasure Request' ); ?></h2>
846                        <p><?php esc_html_e( 'An email will be sent to the user at this email address asking them to verify the request.' ); ?></p>
847
848                        <div class="wp-privacy-request-form-field">
849                                <label for="username_or_email_for_privacy_request"><?php esc_html_e( 'Username or email address' ); ?></label>
850                                <input type="text" required class="regular-text" id="username_or_email_for_privacy_request" name="username_or_email_for_privacy_request" />
851                                <?php submit_button( __( 'Send Request' ), 'secondary', 'submit', false ); ?>
852                        </div>
853                        <?php wp_nonce_field( 'personal-data-request' ); ?>
854                        <input type="hidden" name="action" value="add_remove_personal_data_request" />
855                        <input type="hidden" name="type_of_action" value="remove_personal_data" />
856                </form>
857                <hr />
858
859                <?php $requests_table->views(); ?>
860
861                <form class="search-form wp-clearfix">
862                        <?php $requests_table->search_box( __( 'Search Requests' ), 'requests' ); ?>
863                        <input type="hidden" name="page" value="remove_personal_data" />
864                        <input type="hidden" name="filter-status" value="<?php echo isset( $_REQUEST['filter-status'] ) ? esc_attr( sanitize_text_field( $_REQUEST['filter-status'] ) ) : ''; ?>" />
865                        <input type="hidden" name="orderby" value="<?php echo isset( $_REQUEST['orderby'] ) ? esc_attr( sanitize_text_field( $_REQUEST['orderby'] ) ) : ''; ?>" />
866                        <input type="hidden" name="order" value="<?php echo isset( $_REQUEST['order'] ) ? esc_attr( sanitize_text_field( $_REQUEST['order'] ) ) : ''; ?>" />
867                </form>
868
869                <form method="post">
870                        <?php
871                        $requests_table->display();
872                        $requests_table->embed_scripts();
873                        ?>
874                </form>
875        </div>
876        <?php
877}
878
879/**
880 * Mark erasure requests as completed after processing is finished.
881 *
882 * This intercepts the Ajax responses to personal data eraser page requests, and
883 * monitors the status of a request. Once all of the processing has finished, the
884 * request is marked as completed.
885 *
886 * @since 4.9.6
887 *
888 * @see wp_privacy_personal_data_erasure_page
889 *
890 * @param array  $response      The response from the personal data eraser for
891 *                              the given page.
892 * @param int    $eraser_index  The index of the personal data eraser. Begins
893 *                              at 1.
894 * @param string $email_address The email address of the user whose personal
895 *                              data this is.
896 * @param int    $page          The page of personal data for this eraser.
897 *                              Begins at 1.
898 * @param int    $request_id    The request ID for this personal data erasure.
899 * @return array The filtered response.
900 */
901function wp_privacy_process_personal_data_erasure_page( $response, $eraser_index, $email_address, $page, $request_id ) {
902        /*
903         * If the eraser response is malformed, don't attempt to consume it; let it
904         * pass through, so that the default Ajax processing will generate a warning
905         * to the user.
906         */
907        if ( ! is_array( $response ) ) {
908                return $response;
909        }
910
911        if ( ! array_key_exists( 'done', $response ) ) {
912                return $response;
913        }
914
915        if ( ! array_key_exists( 'items_removed', $response ) ) {
916                return $response;
917        }
918
919        if ( ! array_key_exists( 'items_retained', $response ) ) {
920                return $response;
921        }
922
923        if ( ! array_key_exists( 'messages', $response ) ) {
924                return $response;
925        }
926
927        $request = wp_get_user_request_data( $request_id );
928
929        if ( ! $request || 'remove_personal_data' !== $request->action_name ) {
930                wp_send_json_error( __( 'Invalid request ID when processing eraser data.' ) );
931        }
932
933        /** This filter is documented in wp-admin/includes/ajax-actions.php */
934        $erasers        = apply_filters( 'wp_privacy_personal_data_erasers', array() );
935        $is_last_eraser = count( $erasers ) === $eraser_index;
936        $eraser_done    = $response['done'];
937
938        if ( ! $is_last_eraser || ! $eraser_done ) {
939                return $response;
940        }
941
942        _wp_privacy_completed_request( $request_id );
943
944        /**
945         * Fires immediately after a personal data erasure request has been marked completed.
946         *
947         * @since 4.9.6
948         *
949         * @param int $request_id The privacy request post ID associated with this request.
950         */
951        do_action( 'wp_privacy_personal_data_erased', $request_id );
952
953        return $response;
954}
955
956/**
957 * Add requests pages.
958 *
959 * @since 4.9.6
960 * @access private
961 */
962function _wp_privacy_hook_requests_page() {
963        add_submenu_page( 'tools.php', __( 'Export Personal Data' ), __( 'Export Personal Data' ), 'export_others_personal_data', 'export_personal_data', '_wp_personal_data_export_page' );
964        add_submenu_page( 'tools.php', __( 'Erase Personal Data' ), __( 'Erase Personal Data' ), 'erase_others_personal_data', 'remove_personal_data', '_wp_personal_data_removal_page' );
965}
966
967/**
968 * Add options for the privacy requests screens.
969 *
970 * @since 4.9.8
971 * @access private
972 */
973function _wp_privacy_requests_screen_options() {
974        $args = array(
975                'option'  => str_replace( 'tools_page_', '', get_current_screen()->id ) . '_requests_per_page',
976        );
977        add_screen_option( 'per_page', $args );
978}
979
980// TODO: move the following classes in new files.
981if ( ! class_exists( 'WP_List_Table' ) ) {
982        require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
983}
984
985/**
986 * WP_Privacy_Requests_Table class.
987 *
988 * @since 4.9.6
989 */
990abstract class WP_Privacy_Requests_Table extends WP_List_Table {
991
992        /**
993         * Action name for the requests this table will work with. Classes
994         * which inherit from WP_Privacy_Requests_Table should define this.
995         *
996         * Example: 'export_personal_data'.
997         *
998         * @since 4.9.6
999         *
1000         * @var string $request_type Name of action.
1001         */
1002        protected $request_type = 'INVALID';
1003
1004        /**
1005         * Post type to be used.
1006         *
1007         * @since 4.9.6
1008         *
1009         * @var string $post_type The post type.
1010         */
1011        protected $post_type = 'INVALID';
1012
1013        /**
1014         * Get columns to show in the list table.
1015         *
1016         * @since 4.9.6
1017         *
1018         * @return array Array of columns.
1019         */
1020        public function get_columns() {
1021                $columns = array(
1022                        'cb'                => '<input type="checkbox" />',
1023                        'email'             => __( 'Requester' ),
1024                        'status'            => __( 'Status' ),
1025                        'created_timestamp' => __( 'Requested' ),
1026                        'next_steps'        => __( 'Next Steps' ),
1027                );
1028                return $columns;
1029        }
1030
1031        /**
1032         * Get a list of sortable columns.
1033         *
1034         * @since 4.9.6
1035         *
1036         * @return array Default sortable columns.
1037         */
1038        protected function get_sortable_columns() {
1039                return array();
1040        }
1041
1042        /**
1043         * Default primary column.
1044         *
1045         * @since 4.9.6
1046         *
1047         * @return string Default primary column name.
1048         */
1049        protected function get_default_primary_column_name() {
1050                return 'email';
1051        }
1052
1053        /**
1054         * Count number of requests for each status.
1055         *
1056         * @since 4.9.6
1057         *
1058         * @return object Number of posts for each status.
1059         */
1060        protected function get_request_counts() {
1061                global $wpdb;
1062
1063                $cache_key = $this->post_type . '-' . $this->request_type;
1064                $counts    = wp_cache_get( $cache_key, 'counts' );
1065
1066                if ( false !== $counts ) {
1067                        return $counts;
1068                }
1069
1070                $query = "
1071                        SELECT post_status, COUNT( * ) AS num_posts
1072                        FROM {$wpdb->posts}
1073                        WHERE post_type = %s
1074                        AND post_name = %s
1075                        GROUP BY post_status";
1076
1077                $results = (array) $wpdb->get_results( $wpdb->prepare( $query, $this->post_type, $this->request_type ), ARRAY_A );
1078                $counts  = array_fill_keys( get_post_stati(), 0 );
1079
1080                foreach ( $results as $row ) {
1081                        $counts[ $row['post_status'] ] = $row['num_posts'];
1082                }
1083
1084                $counts = (object) $counts;
1085                wp_cache_set( $cache_key, $counts, 'counts' );
1086
1087                return $counts;
1088        }
1089
1090        /**
1091         * Get an associative array ( id => link ) with the list of views available on this table.
1092         *
1093         * @since 4.9.6
1094         *
1095         * @return array Associative array of views in the format of $view_name => $view_markup.
1096         */
1097        protected function get_views() {
1098                $current_status = isset( $_REQUEST['filter-status'] ) ? sanitize_text_field( $_REQUEST['filter-status'] ) : '';
1099                $statuses       = _wp_privacy_statuses();
1100                $views          = array();
1101                $admin_url      = admin_url( 'tools.php?page=' . $this->request_type );
1102                $counts         = $this->get_request_counts();
1103                $total_requests = absint( array_sum( (array) $counts ) );
1104
1105                $current_link_attributes = empty( $current_status ) ? ' class="current" aria-current="page"' : '';
1106                $views['all']            = '<a href="' . esc_url( $admin_url ) . "\" $current_link_attributes>" . sprintf( _nx( 'All <span class="count">(%s)</span>', 'All <span class="count">(%d)</span>', $total_requests, 'requests' ), number_format_i18n( $total_requests ) ) . '</a>';
1107
1108                foreach ( $statuses as $status => $label ) {
1109                        $current_link_attributes = $status === $current_status ? ' class="current" aria-current="page"' : '';
1110                        $total_status_requests   = absint( $counts->$status );
1111                        $views[ $status ]        = '<a href="' . esc_url( add_query_arg( 'filter-status', $status, $admin_url ) ) . "\" $current_link_attributes>" . sprintf( _nx( '%s <span class="count">(%d)</span>', '%s <span class="count">(%d)</span>', $total_status_requests, 'requests' ), esc_html( $label ), number_format_i18n( $total_status_requests ) ) . '</a>';
1112                }
1113
1114                return $views;
1115        }
1116
1117        /**
1118         * Get bulk actions.
1119         *
1120         * @since 4.9.6
1121         *
1122         * @return array List of bulk actions.
1123         */
1124        protected function get_bulk_actions() {
1125                return array(
1126                        'delete' => __( 'Remove' ),
1127                        'resend' => __( 'Resend email' ),
1128                );
1129        }
1130
1131        /**
1132         * Process bulk actions.
1133         *
1134         * @since 4.9.6
1135         */
1136        public function process_bulk_action() {
1137                $action      = $this->current_action();
1138                $request_ids = isset( $_REQUEST['request_id'] ) ? wp_parse_id_list( wp_unslash( $_REQUEST['request_id'] ) ) : array();
1139
1140                $count       = 0;
1141
1142                if ( $request_ids ) {
1143                        check_admin_referer( 'bulk-privacy_requests' );
1144                }
1145
1146                switch ( $action ) {
1147                        case 'delete':
1148                                foreach ( $request_ids as $request_id ) {
1149                                        if ( wp_delete_post( $request_id, true ) ) {
1150                                                $count ++;
1151                                        }
1152                                }
1153
1154                                add_settings_error(
1155                                        'bulk_action',
1156                                        'bulk_action',
1157                                        /* translators: %d: number of requests */
1158                                        sprintf( _n( 'Deleted %d request', 'Deleted %d requests', $count ), $count ),
1159                                        'updated'
1160                                );
1161                                break;
1162                        case 'resend':
1163                                foreach ( $request_ids as $request_id ) {
1164                                        $resend = _wp_privacy_resend_request( $request_id );
1165
1166                                        if ( $resend && ! is_wp_error( $resend ) ) {
1167                                                $count++;
1168                                        }
1169                                }
1170
1171                                add_settings_error(
1172                                        'bulk_action',
1173                                        'bulk_action',
1174                                        /* translators: %d: number of requests */
1175                                        sprintf( _n( 'Re-sent %d request', 'Re-sent %d requests', $count ), $count ),
1176                                        'updated'
1177                                );
1178                                break;
1179                }
1180        }
1181
1182        /**
1183         * Prepare items to output.
1184         *
1185         * @since 4.9.6
1186         */
1187        public function prepare_items() {
1188                global $wpdb;
1189
1190                $primary               = $this->get_primary_column_name();
1191                $this->_column_headers = array(
1192                        $this->get_columns(),
1193                        array(),
1194                        $this->get_sortable_columns(),
1195                        $primary,
1196                );
1197
1198                $this->items    = array();
1199                $posts_per_page = $this->get_items_per_page( $this->request_type . '_requests_per_page' );
1200                $args           = array(
1201                        'post_type'      => $this->post_type,
1202                        'post_name__in'  => array( $this->request_type ),
1203                        'posts_per_page' => $posts_per_page,
1204                        'offset'         => isset( $_REQUEST['paged'] ) ? max( 0, absint( $_REQUEST['paged'] ) - 1 ) * $posts_per_page : 0,
1205                        'post_status'    => 'any',
1206                        's'              => isset( $_REQUEST['s'] ) ? sanitize_text_field( $_REQUEST['s'] ) : '',
1207                );
1208
1209                if ( ! empty( $_REQUEST['filter-status'] ) ) {
1210                        $filter_status       = isset( $_REQUEST['filter-status'] ) ? sanitize_text_field( $_REQUEST['filter-status'] ) : '';
1211                        $args['post_status'] = $filter_status;
1212                }
1213
1214                $requests_query = new WP_Query( $args );
1215                $requests       = $requests_query->posts;
1216
1217                foreach ( $requests as $request ) {
1218                        $this->items[] = wp_get_user_request_data( $request->ID );
1219                }
1220
1221                $this->items = array_filter( $this->items );
1222
1223                $this->set_pagination_args(
1224                        array(
1225                                'total_items' => $requests_query->found_posts,
1226                                'per_page'    => $posts_per_page,
1227                        )
1228                );
1229        }
1230
1231        /**
1232         * Checkbox column.
1233         *
1234         * @since 4.9.6
1235         *
1236         * @param WP_User_Request $item Item being shown.
1237         * @return string Checkbox column markup.
1238         */
1239        public function column_cb( $item ) {
1240                return sprintf( '<input type="checkbox" name="request_id[]" value="%1$s" /><span class="spinner"></span>', esc_attr( $item->ID ) );
1241        }
1242
1243        /**
1244         * Status column.
1245         *
1246         * @since 4.9.6
1247         *
1248         * @param WP_User_Request $item Item being shown.
1249         * @return string Status column markup.
1250         */
1251        public function column_status( $item ) {
1252                $status        = get_post_status( $item->ID );
1253                $status_object = get_post_status_object( $status );
1254
1255                if ( ! $status_object || empty( $status_object->label ) ) {
1256                        return '-';
1257                }
1258
1259                $timestamp = false;
1260
1261                switch ( $status ) {
1262                        case 'request-confirmed':
1263                                $timestamp = $item->confirmed_timestamp;
1264                                break;
1265                        case 'request-completed':
1266                                $timestamp = $item->completed_timestamp;
1267                                break;
1268                }
1269
1270                echo '<span class="status-label status-' . esc_attr( $status ) . '">';
1271                echo esc_html( $status_object->label );
1272
1273                if ( $timestamp ) {
1274                        echo ' (' . $this->get_timestamp_as_date( $timestamp ) . ')';
1275                }
1276
1277                echo '</span>';
1278        }
1279
1280        /**
1281         * Convert timestamp for display.
1282         *
1283         * @since 4.9.6
1284         *
1285         * @param int $timestamp Event timestamp.
1286         * @return string Human readable date.
1287         */
1288        protected function get_timestamp_as_date( $timestamp ) {
1289                if ( empty( $timestamp ) ) {
1290                        return '';
1291                }
1292
1293                $time_diff = current_time( 'timestamp', true ) - $timestamp;
1294
1295                if ( $time_diff >= 0 && $time_diff < DAY_IN_SECONDS ) {
1296                        /* translators: human readable timestamp */
1297                        return sprintf( __( '%s ago' ), human_time_diff( $timestamp ) );
1298                }
1299
1300                return date_i18n( get_option( 'date_format' ), $timestamp );
1301        }
1302
1303        /**
1304         * Default column handler.
1305         *
1306         * @since 4.9.6
1307         *
1308         * @param WP_User_Request $item        Item being shown.
1309         * @param string          $column_name Name of column being shown.
1310         * @return string Default column output.
1311         */
1312        public function column_default( $item, $column_name ) {
1313                $cell_value = $item->$column_name;
1314
1315                if ( in_array( $column_name, array( 'created_timestamp' ), true ) ) {
1316                        return $this->get_timestamp_as_date( $cell_value );
1317                }
1318
1319                return $cell_value;
1320        }
1321
1322        /**
1323         * Actions column. Overridden by children.
1324         *
1325         * @since 4.9.6
1326         *
1327         * @param WP_User_Request $item Item being shown.
1328         * @return string Email column markup.
1329         */
1330        public function column_email( $item ) {
1331                return sprintf( '<a href="%1$s">%2$s</a> %3$s', esc_url( 'mailto:' . $item->email ), $item->email, $this->row_actions( array() ) );
1332        }
1333
1334        /**
1335         * Next steps column. Overridden by children.
1336         *
1337         * @since 4.9.6
1338         *
1339         * @param WP_User_Request $item Item being shown.
1340         */
1341        public function column_next_steps( $item ) {}
1342
1343        /**
1344         * Generates content for a single row of the table,
1345         *
1346         * @since 4.9.6
1347         *
1348         * @param WP_User_Request $item The current item.
1349         */
1350        public function single_row( $item ) {
1351                $status = $item->status;
1352
1353                echo '<tr id="request-' . esc_attr( $item->ID ) . '" class="status-' . esc_attr( $status ) . '">';
1354                $this->single_row_columns( $item );
1355                echo '</tr>';
1356        }
1357
1358        /**
1359         * Embed scripts used to perform actions. Overridden by children.
1360         *
1361         * @since 4.9.6
1362         */
1363        public function embed_scripts() {}
1364}
1365
1366/**
1367 * WP_Privacy_Data_Export_Requests_Table class.
1368 *
1369 * @since 4.9.6
1370 */
1371class WP_Privacy_Data_Export_Requests_Table extends WP_Privacy_Requests_Table {
1372        /**
1373         * Action name for the requests this table will work with.
1374         *
1375         * @since 4.9.6
1376         *
1377         * @var string $request_type Name of action.
1378         */
1379        protected $request_type = 'export_personal_data';
1380
1381        /**
1382         * Post type for the requests.
1383         *
1384         * @since 4.9.6
1385         *
1386         * @var string $post_type The post type.
1387         */
1388        protected $post_type = 'user_request';
1389
1390        /**
1391         * Actions column.
1392         *
1393         * @since 4.9.6
1394         *
1395         * @param WP_User_Request $item Item being shown.
1396         * @return string Email column markup.
1397         */
1398        public function column_email( $item ) {
1399                /** This filter is documented in wp-admin/includes/ajax-actions.php */
1400                $exporters       = apply_filters( 'wp_privacy_personal_data_exporters', array() );
1401                $exporters_count = count( $exporters );
1402                $request_id      = $item->ID;
1403                $nonce           = wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id );
1404
1405                $download_data_markup = '<div class="export-personal-data" ' .
1406                        'data-exporters-count="' . esc_attr( $exporters_count ) . '" ' .
1407                        'data-request-id="' . esc_attr( $request_id ) . '" ' .
1408                        'data-nonce="' . esc_attr( $nonce ) .
1409                        '">';
1410
1411                $download_data_markup .= '<span class="export-personal-data-idle"><button type="button" class="button-link export-personal-data-handle">' . __( 'Download Personal Data' ) . '</button></span>' .
1412                        '<span style="display:none" class="export-personal-data-processing" >' . __( 'Downloading Data...' ) . '</span>' .
1413                        '<span style="display:none" class="export-personal-data-success"><button type="button" class="button-link export-personal-data-handle">' . __( 'Download Personal Data Again' ) . '</button></span>' .
1414                        '<span style="display:none" class="export-personal-data-failed">' . __( 'Download has failed.' ) . ' <button type="button" class="button-link">' . __( 'Retry' ) . '</button></span>';
1415
1416                $download_data_markup .= '</div>';
1417
1418                $row_actions = array(
1419                        'download-data' => $download_data_markup,
1420                );
1421
1422                return sprintf( '<a href="%1$s">%2$s</a> %3$s', esc_url( 'mailto:' . $item->email ), $item->email, $this->row_actions( $row_actions ) );
1423        }
1424
1425        /**
1426         * Displays the next steps column.
1427         *
1428         * @since 4.9.6
1429         *
1430         * @param WP_User_Request $item Item being shown.
1431         */
1432        public function column_next_steps( $item ) {
1433                $status = $item->status;
1434
1435                switch ( $status ) {
1436                        case 'request-pending':
1437                                esc_html_e( 'Waiting for confirmation' );
1438                                break;
1439                        case 'request-confirmed':
1440                                /** This filter is documented in wp-admin/includes/ajax-actions.php */
1441                                $exporters       = apply_filters( 'wp_privacy_personal_data_exporters', array() );
1442                                $exporters_count = count( $exporters );
1443                                $request_id      = $item->ID;
1444                                $nonce           = wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id );
1445
1446                                echo '<div class="export-personal-data" ' .
1447                                        'data-send-as-email="1" ' .
1448                                        'data-exporters-count="' . esc_attr( $exporters_count ) . '" ' .
1449                                        'data-request-id="' . esc_attr( $request_id ) . '" ' .
1450                                        'data-nonce="' . esc_attr( $nonce ) .
1451                                        '">';
1452
1453                                ?>
1454                                <span class="export-personal-data-idle"><button type="button" class="button export-personal-data-handle"><?php _e( 'Email Data' ); ?></button></span>
1455                                <span style="display:none" class="export-personal-data-processing button updating-message" ><?php _e( 'Sending Email...' ); ?></span>
1456                                <span style="display:none" class="export-personal-data-success success-message" ><?php _e( 'Email sent.' ); ?></span>
1457                                <span style="display:none" class="export-personal-data-failed"><?php _e( 'Email could not be sent.' ); ?> <button type="button" class="button export-personal-data-handle"><?php _e( 'Retry' ); ?></button></span>
1458                                <?php
1459
1460                                echo '</div>';
1461                                break;
1462                        case 'request-failed':
1463                                submit_button( __( 'Retry' ), 'secondary', 'privacy_action_email_retry[' . $item->ID . ']', false );
1464                                break;
1465                        case 'request-completed':
1466                                echo '<a href="' . esc_url( wp_nonce_url( add_query_arg( array(
1467                                        'action'     => 'delete',
1468                                        'request_id' => array( $item->ID ),
1469                                ), admin_url( 'tools.php?page=export_personal_data' ) ), 'bulk-privacy_requests' ) ) . '" class="button">' . esc_html__( 'Remove request' ) . '</a>';
1470                                break;
1471                }
1472        }
1473}
1474
1475/**
1476 * WP_Privacy_Data_Removal_Requests_Table class.
1477 *
1478 * @since 4.9.6
1479 */
1480class WP_Privacy_Data_Removal_Requests_Table extends WP_Privacy_Requests_Table {
1481        /**
1482         * Action name for the requests this table will work with.
1483         *
1484         * @since 4.9.6
1485         *
1486         * @var string $request_type Name of action.
1487         */
1488        protected $request_type = 'remove_personal_data';
1489
1490        /**
1491         * Post type for the requests.
1492         *
1493         * @since 4.9.6
1494         *
1495         * @var string $post_type The post type.
1496         */
1497        protected $post_type = 'user_request';
1498
1499        /**
1500         * Actions column.
1501         *
1502         * @since 4.9.6
1503         *
1504         * @param WP_User_Request $item Item being shown.
1505         * @return string Email column markup.
1506         */
1507        public function column_email( $item ) {
1508                $row_actions = array();
1509
1510                // Allow the administrator to "force remove" the personal data even if confirmation has not yet been received.
1511                $status = $item->status;
1512                if ( 'request-confirmed' !== $status ) {
1513                        /** This filter is documented in wp-admin/includes/ajax-actions.php */
1514                        $erasers       = apply_filters( 'wp_privacy_personal_data_erasers', array() );
1515                        $erasers_count = count( $erasers );
1516                        $request_id    = $item->ID;
1517                        $nonce         = wp_create_nonce( 'wp-privacy-erase-personal-data-' . $request_id );
1518
1519                        $remove_data_markup = '<div class="remove-personal-data force-remove-personal-data" ' .
1520                                'data-erasers-count="' . esc_attr( $erasers_count ) . '" ' .
1521                                'data-request-id="' . esc_attr( $request_id ) . '" ' .
1522                                'data-nonce="' . esc_attr( $nonce ) .
1523                                '">';
1524
1525                        $remove_data_markup .= '<span class="remove-personal-data-idle"><button type="button" class="button-link remove-personal-data-handle">' . __( 'Force Erase Personal Data' ) . '</button></span>' .
1526                                '<span style="display:none" class="remove-personal-data-processing" >' . __( 'Erasing Data...' ) . '</span>' .
1527                                '<span style="display:none" class="remove-personal-data-failed">' . __( 'Force Erase has failed.' ) . ' <button type="button" class="button-link remove-personal-data-handle">' . __( 'Retry' ) . '</button></span>';
1528
1529                        $remove_data_markup .= '</div>';
1530
1531                        $row_actions = array(
1532                                'remove-data' => $remove_data_markup,
1533                        );
1534                }
1535
1536                return sprintf( '<a href="%1$s">%2$s</a> %3$s', esc_url( 'mailto:' . $item->email ), $item->email, $this->row_actions( $row_actions ) );
1537        }
1538
1539        /**
1540         * Next steps column.
1541         *
1542         * @since 4.9.6
1543         *
1544         * @param WP_User_Request $item Item being shown.
1545         */
1546        public function column_next_steps( $item ) {
1547                $status = $item->status;
1548
1549                switch ( $status ) {
1550                        case 'request-pending':
1551                                esc_html_e( 'Waiting for confirmation' );
1552                                break;
1553                        case 'request-confirmed':
1554                                /** This filter is documented in wp-admin/includes/ajax-actions.php */
1555                                $erasers       = apply_filters( 'wp_privacy_personal_data_erasers', array() );
1556                                $erasers_count = count( $erasers );
1557                                $request_id    = $item->ID;
1558                                $nonce         = wp_create_nonce( 'wp-privacy-erase-personal-data-' . $request_id );
1559
1560                                echo '<div class="remove-personal-data" ' .
1561                                        'data-force-erase="1" ' .
1562                                        'data-erasers-count="' . esc_attr( $erasers_count ) . '" ' .
1563                                        'data-request-id="' . esc_attr( $request_id ) . '" ' .
1564                                        'data-nonce="' . esc_attr( $nonce ) .
1565                                        '">';
1566
1567                                ?>
1568                                <span class="remove-personal-data-idle"><button type="button" class="button remove-personal-data-handle"><?php _e( 'Erase Personal Data' ); ?></button></span>
1569                                <span style="display:none" class="remove-personal-data-processing button updating-message" ><?php _e( 'Erasing Data...' ); ?></span>
1570                                <span style="display:none" class="remove-personal-data-failed"><?php _e( 'Erasing Data has failed.' ); ?> <button type="button" class="button remove-personal-data-handle"><?php _e( 'Retry' ); ?></button></span>
1571                                <?php
1572
1573                                echo '</div>';
1574
1575                                break;
1576                        case 'request-failed':
1577                                submit_button( __( 'Retry' ), 'secondary', 'privacy_action_email_retry[' . $item->ID . ']', false );
1578                                break;
1579                        case 'request-completed':
1580                                echo '<a href="' . esc_url( wp_nonce_url( add_query_arg( array(
1581                                        'action'     => 'delete',
1582                                        'request_id' => array( $item->ID ),
1583                                ), admin_url( 'tools.php?page=remove_personal_data' ) ), 'bulk-privacy_requests' ) ) . '" class="button">' . esc_html__( 'Remove request' ) . '</a>';
1584                                break;
1585                }
1586        }
1587
1588}