Ticket #43443: 43443.5.patch
File 43443.5.patch, 38.4 KB (added by , 6 years ago) |
---|
-
src/wp-admin/includes/admin-filters.php
46 46 add_action( 'admin_head', '_ipad_meta' ); 47 47 48 48 // Privacy tools 49 add_action( 'account_action_failed', '_wp_privacy_account_request_failed' );50 49 add_action( 'admin_menu', '_wp_privacy_hook_requests_page' ); 51 50 52 51 // Prerendering. -
src/wp-admin/includes/ajax-actions.php
4464 4464 4465 4465 // Find the request CPT 4466 4466 $request = get_post( $request_id ); 4467 if ( ' user_remove_request' !== $request->post_type ) {4467 if ( 'remove_personal_data' !== $request->post_title ) { 4468 4468 wp_send_json_error( __( 'Error: Invalid request ID.' ) ); 4469 4469 } 4470 4470 4471 $email_address = get_post_meta( $request_id, '_ user_email', true );4471 $email_address = get_post_meta( $request_id, '_wp_user_request_user_email', true ); 4472 4472 4473 4473 if ( ! is_email( $email_address ) ) { 4474 4474 wp_send_json_error( __( 'Error: Invalid email address in request.' ) ); -
src/wp-admin/includes/user.php
581 581 } 582 582 583 583 /** 584 * Get action description from the name.585 *586 * @since 4.9.6587 * @access private588 *589 * @return string590 */591 function _wp_privacy_action_description( $request_type ) {592 switch ( $request_type ) {593 case 'user_export_request':594 return __( 'Export Personal Data' );595 case 'user_remove_request':596 return __( 'Remove Personal Data' );597 }598 }599 600 /**601 * Log a request and send to the user.602 *603 * @since 4.9.6604 * @access private605 *606 * @param string $email_address Email address sending the request to.607 * @param string $action Action being requested.608 * @param string $description Description of request.609 * @return bool|WP_Error depending on success.610 */611 function _wp_privacy_create_request( $email_address, $action, $description ) {612 $user_id = 0;613 $user = get_user_by( 'email', $email_address );614 615 if ( $user ) {616 $user_id = $user->ID;617 }618 619 $privacy_request_id = wp_insert_post( array(620 'post_author' => $user_id,621 'post_status' => 'request-pending',622 'post_type' => $action,623 'post_date' => current_time( 'mysql', false ),624 'post_date_gmt' => current_time( 'mysql', true ),625 ), true );626 627 if ( is_wp_error( $privacy_request_id ) ) {628 return $privacy_request_id;629 }630 631 update_post_meta( $privacy_request_id, '_user_email', $email_address );632 update_post_meta( $privacy_request_id, '_action_name', $action );633 update_post_meta( $privacy_request_id, '_confirmed_timestamp', false );634 635 return wp_send_account_verification_key( $email_address, $action, $description, array(636 'privacy_request_id' => $privacy_request_id,637 ) );638 }639 640 /**641 584 * Resend an existing request and return the result. 642 585 * 643 586 * @since 4.9.6 644 587 * @access private 645 588 * 646 * @param int $ privacy_request_id Request ID.589 * @param int $request_id Request ID. 647 590 * @return bool|WP_Error 648 591 */ 649 function _wp_privacy_resend_request( $ privacy_request_id ) {650 $ privacy_request_id = absint( $privacy_request_id );651 $ privacy_request = get_post( $privacy_request_id );592 function _wp_privacy_resend_request( $request_id ) { 593 $request_id = absint( $request_id ); 594 $request = get_post( $request_id ); 652 595 653 if ( ! $ privacy_request || ! in_array( $privacy_request->post_type, _wp_privacy_action_request_types(), true )) {596 if ( ! $request || 'user_request' !== $request->post_type ) { 654 597 return new WP_Error( 'privacy_request_error', __( 'Invalid request.' ) ); 655 598 } 656 599 657 $email_address = get_post_meta( $privacy_request_id, '_user_email', true ); 658 $action = get_post_meta( $privacy_request_id, '_action_name', true ); 659 $description = _wp_privacy_action_description( $action ); 660 $result = wp_send_account_verification_key( $email_address, $action, $description, array( 661 'privacy_request_id' => $privacy_request_id, 662 ) ); 600 $result = wp_send_user_request( $request_id ); 663 601 664 602 if ( is_wp_error( $result ) ) { 665 603 return $result; … … 667 605 return new WP_Error( 'privacy_request_error', __( 'Unable to initiate confirmation request.' ) ); 668 606 } 669 607 670 wp_update_post( array(671 'ID' => $privacy_request_id,672 'post_status' => 'request-pending',673 'post_date' => current_time( 'mysql', false ),674 'post_date_gmt' => current_time( 'mysql', true ),675 ) );676 677 608 return true; 678 609 } 679 610 … … 683 614 * @since 4.9.6 684 615 * @access private 685 616 * 686 * @param int $ privacy_request_id Request ID.617 * @param int $request_id Request ID. 687 618 * @return bool|WP_Error 688 619 */ 689 function _wp_privacy_completed_request( $ privacy_request_id ) {690 $ privacy_request_id = absint( $privacy_request_id );691 $ privacy_request = get_post( $privacy_request_id );620 function _wp_privacy_completed_request( $request_id ) { 621 $request_id = absint( $request_id ); 622 $request_data = wp_get_user_request_data( $request_id ); 692 623 693 if ( ! $ privacy_request || ! in_array( $privacy_request->post_type, _wp_privacy_action_request_types(), true )) {694 return new WP_Error( 'privacy_request_error', __( 'Invalid request.' ) );624 if ( ! $request_data ) { 625 return; 695 626 } 696 627 628 update_post_meta( $privacy_request_id, '_wp_user_request_confirmed_timestamp', time() ); 697 629 wp_update_post( array( 698 'ID' => $ privacy_request_id,699 'post_status' => 'request-co mpleted',630 'ID' => $request_data['request_id'], 631 'post_status' => 'request-confirmed', 700 632 ) ); 701 702 update_post_meta( $privacy_request_id, '_completed_timestamp', time() );703 633 } 704 634 705 635 /** … … 803 733 $email_address = $username_or_email_address; 804 734 } 805 735 806 if ( ! empty( $email_address ) ) { 807 $result = _wp_privacy_create_request( $email_address, $action_type, _wp_privacy_action_description( $action_type ) ); 736 if ( empty( $email_address ) ) { 737 break; 738 } 808 739 809 if ( is_wp_error( $result ) ) { 810 add_settings_error( 811 'username_or_email_to_export', 812 'username_or_email_to_export', 813 $result->get_error_message(), 814 'error' 815 ); 816 } elseif ( ! $result ) { 817 add_settings_error( 818 'username_or_email_to_export', 819 'username_or_email_to_export', 820 __( 'Unable to initiate confirmation request.' ), 821 'error' 822 ); 823 } else { 824 add_settings_error( 825 'username_or_email_to_export', 826 'username_or_email_to_export', 827 __( 'Confirmation request initiated successfully.' ), 828 'updated' 829 ); 830 } 740 $request_id = wp_create_user_request( $email_address, $action_type ); 741 742 if ( is_wp_error( $request_id ) ) { 743 add_settings_error( 744 'username_or_email_to_export', 745 'username_or_email_to_export', 746 $request_id->get_error_message(), 747 'error' 748 ); 749 break; 750 } elseif ( ! $request_id ) { 751 add_settings_error( 752 'username_or_email_to_export', 753 'username_or_email_to_export', 754 __( 'Unable to initiate confirmation request.' ), 755 'error' 756 ); 757 break; 831 758 } 759 760 wp_send_user_request( $request_id ); 761 762 add_settings_error( 763 'username_or_email_to_export', 764 'username_or_email_to_export', 765 __( 'Confirmation request initiated successfully.' ), 766 'updated' 767 ); 832 768 break; 833 769 } 834 770 } … … 871 807 </div> 872 808 <?php wp_nonce_field( 'personal-data-request' ); ?> 873 809 <input type="hidden" name="action" value="add_export_personal_data_request" /> 874 <input type="hidden" name="type_of_action" value=" user_export_request" />810 <input type="hidden" name="type_of_action" value="export_personal_data" /> 875 811 </form> 876 812 <hr /> 877 813 … … 937 873 </div> 938 874 <?php wp_nonce_field( 'personal-data-request' ); ?> 939 875 <input type="hidden" name="action" value="add_remove_personal_data_request" /> 940 <input type="hidden" name="type_of_action" value=" user_remove_request" />876 <input type="hidden" name="type_of_action" value="remove_personal_data" /> 941 877 </form> 942 878 <hr /> 943 879 … … 1011 947 */ 1012 948 public function get_columns() { 1013 949 $columns = array( 1014 'cb' => '<input type="checkbox" />',1015 'email' => __( 'Requester' ),1016 'status' => __( 'Status' ),1017 'requested '=> __( 'Requested' ),1018 'next_steps' => __( 'Next Steps' ),950 'cb' => '<input type="checkbox" />', 951 'email' => __( 'Requester' ), 952 'status' => __( 'Status' ), 953 'requested_timestamp' => __( 'Requested' ), 954 'next_steps' => __( 'Next Steps' ), 1019 955 ); 1020 956 return $columns; 1021 957 } … … 1043 979 } 1044 980 1045 981 /** 982 * Count number of requests for each status. 983 * 984 * @since 4.9.6 985 * 986 * @return object Number of posts for each status. 987 */ 988 protected function get_request_counts() { 989 global $wpdb; 990 991 $cache_key = $this->post_type . '-' . $this->request_type; 992 $counts = wp_cache_get( $cache_key, 'counts' ); 993 994 if ( false !== $counts ) { 995 return $counts; 996 } 997 998 $query = " 999 SELECT post_status, COUNT( * ) AS num_posts 1000 FROM {$wpdb->posts} 1001 WHERE post_type = %s 1002 AND post_title = %s 1003 GROUP BY post_status"; 1004 1005 $results = (array) $wpdb->get_results( $wpdb->prepare( $query, $this->post_type, $this->request_type ), ARRAY_A ); 1006 $counts = array_fill_keys( get_post_stati(), 0 ); 1007 1008 foreach ( $results as $row ) { 1009 $counts[ $row['post_status'] ] = $row['num_posts']; 1010 } 1011 1012 $counts = (object) $counts; 1013 wp_cache_set( $cache_key, $counts, 'counts' ); 1014 1015 return $counts; 1016 } 1017 1018 /** 1046 1019 * Get an associative array ( id => link ) with the list 1047 1020 * of views available on this table. 1048 1021 * … … 1055 1028 $statuses = _wp_privacy_statuses(); 1056 1029 $views = array(); 1057 1030 $admin_url = admin_url( 'tools.php?page=' . $this->request_type ); 1058 $counts = wp_count_posts( $this->post_type);1031 $counts = $this->get_request_counts(); 1059 1032 1060 1033 $current_link_attributes = empty( $current_status ) ? ' class="current" aria-current="page"' : ''; 1061 1034 $views['all'] = '<a href="' . esc_url( $admin_url ) . "\" $current_link_attributes>" . esc_html__( 'All' ) . ' <span class="count">(' . absint( array_sum( (array) $counts ) ) . ')</span></a>'; … … 1151 1124 $posts_per_page = 20; 1152 1125 $args = array( 1153 1126 'post_type' => $this->post_type, 1127 'title' => $this->request_type, 1154 1128 'posts_per_page' => $posts_per_page, 1155 1129 'offset' => isset( $_REQUEST['paged'] ) ? max( 0, absint( $_REQUEST['paged'] ) - 1 ) * $posts_per_page: 0, 1156 1130 'post_status' => 'any', … … 1166 1140 $name_query, 1167 1141 'relation' => 'AND', 1168 1142 array( 1169 'key' => '_ user_email',1143 'key' => '_wp_user_request_user_email', 1170 1144 'value' => isset( $_REQUEST['s'] ) ? sanitize_text_field( $_REQUEST['s'] ): '', 1171 'compare' => 'LIKE' 1145 'compare' => 'LIKE', 1172 1146 ), 1173 1147 ); 1174 1148 } 1175 1149 1176 $ privacy_requests_query = new WP_Query( $args );1177 $ privacy_requests = $privacy_requests_query->posts;1150 $requests_query = new WP_Query( $args ); 1151 $requests = $requests_query->posts; 1178 1152 1179 foreach ( $privacy_requests as $privacy_request ) { 1180 $this->items[] = array( 1181 'request_id' => $privacy_request->ID, 1182 'user_id' => $privacy_request->post_author, 1183 'email' => get_post_meta( $privacy_request->ID, '_user_email', true ), 1184 'action' => get_post_meta( $privacy_request->ID, '_action_name', true ), 1185 'requested' => strtotime( $privacy_request->post_date_gmt ), 1186 'confirmed' => get_post_meta( $privacy_request->ID, '_confirmed_timestamp', true ), 1187 'completed' => get_post_meta( $privacy_request->ID, '_completed_timestamp', true ), 1188 ); 1153 foreach ( $requests as $request ) { 1154 $this->items[] = wp_get_user_request_data( $request->ID ); 1189 1155 } 1190 1156 1191 1157 $this->set_pagination_args( 1192 1158 array( 1193 'total_items' => $ privacy_requests_query->found_posts,1159 'total_items' => $requests_query->found_posts, 1194 1160 'per_page' => $posts_per_page, 1195 1161 ) 1196 1162 ); … … 1228 1194 1229 1195 switch ( $status ) { 1230 1196 case 'request-confirmed': 1231 $timestamp = $item['confirmed '];1197 $timestamp = $item['confirmed_timestamp']; 1232 1198 break; 1233 1199 case 'request-completed': 1234 $timestamp = $item['completed '];1200 $timestamp = $item['completed_timestamp']; 1235 1201 break; 1236 1202 } 1237 1203 … … 1279 1245 public function column_default( $item, $column_name ) { 1280 1246 $cell_value = $item[ $column_name ]; 1281 1247 1282 if ( in_array( $column_name, array( 'requested ' ), true ) ) {1248 if ( in_array( $column_name, array( 'requested_timestamp' ), true ) ) { 1283 1249 return $this->get_timestamp_as_date( $cell_value ); 1284 1250 } 1285 1251 … … 1352 1318 * 1353 1319 * @var string $post_type The post type. 1354 1320 */ 1355 protected $post_type = 'user_ export_request';1321 protected $post_type = 'user_request'; 1356 1322 1357 1323 /** 1358 1324 * Actions column. … … 1437 1403 * 1438 1404 * @var string $post_type The post type. 1439 1405 */ 1440 protected $post_type = 'user_re move_request';1406 protected $post_type = 'user_request'; 1441 1407 1442 1408 /** 1443 1409 * Actions column. -
src/wp-includes/default-filters.php
328 328 add_action( 'do_pings', 'do_all_pings', 10, 1 ); 329 329 add_action( 'do_robots', 'do_robots' ); 330 330 add_action( 'set_comment_cookies', 'wp_set_comment_cookies', 10, 3 ); 331 add_filter( 'wp_privacy_personal_data_exporters', 'wp_register_comment_personal_data_exporter', 10 );332 add_filter( 'wp_privacy_personal_data_erasers', 'wp_register_comment_personal_data_eraser', 10 );333 331 add_action( 'sanitize_comment_cookies', 'sanitize_comment_cookies' ); 334 332 add_action( 'admin_print_scripts', 'print_emoji_detection_script' ); 335 333 add_action( 'admin_print_scripts', 'print_head_scripts', 20 ); … … 349 347 add_action( 'admin_init', 'send_frame_options_header', 10, 0 ); 350 348 add_action( 'welcome_panel', 'wp_welcome_panel' ); 351 349 350 // Privacy 351 add_action( 'user_request_action_confirmed', '_wp_privacy_account_request_confirmed' ); 352 add_filter( 'user_request_action_confirmed_message', '_wp_privacy_account_request_confirmed_message', 10, 2 ); 353 add_filter( 'wp_privacy_personal_data_exporters', 'wp_register_comment_personal_data_exporter' ); 354 add_filter( 'wp_privacy_personal_data_erasers', 'wp_register_comment_personal_data_eraser' ); 355 352 356 // Cron tasks 353 357 add_action( 'wp_scheduled_delete', 'wp_scheduled_delete' ); 354 358 add_action( 'wp_scheduled_auto_draft_delete', 'wp_delete_auto_drafts' ); -
src/wp-includes/post.php
227 227 ); 228 228 229 229 register_post_type( 230 'user_ export_request', array(230 'user_request', array( 231 231 'labels' => array( 232 'name' => __( ' Export Personal DataRequests' ),233 'singular_name' => __( ' Export Personal DataRequest' ),232 'name' => __( 'User Requests' ), 233 'singular_name' => __( 'User Request' ), 234 234 ), 235 235 'public' => false, 236 236 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */ … … 239 239 'query_var' => false, 240 240 'can_export' => false, 241 241 'delete_with_user' => false, 242 'supports' => array(), 242 243 ) 243 244 ); 244 245 245 register_post_type(246 'user_remove_request', array(247 'labels' => array(248 'name' => __( 'Remove Personal Data Requests' ),249 'singular_name' => __( 'Remove Personal Data Request' ),250 ),251 'public' => false,252 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */253 'hierarchical' => false,254 'rewrite' => false,255 'query_var' => false,256 'can_export' => false,257 'delete_with_user' => false,258 )259 );260 261 246 register_post_status( 262 247 'publish', array( 263 248 'label' => _x( 'Published', 'post status' ), -
src/wp-includes/user.php
2813 2813 /** 2814 2814 * Get all user privacy request types. 2815 2815 * 2816 * @since 5.0.02816 * @since 4.9.6 2817 2817 * @access private 2818 2818 * 2819 2819 * @return array … … 2820 2820 */ 2821 2821 function _wp_privacy_action_request_types() { 2822 2822 return array( 2823 ' user_export_request',2824 ' user_remove_request',2823 'export_personal_data', 2824 'remove_personal_data', 2825 2825 ); 2826 2826 } 2827 2827 … … 2828 2828 /** 2829 2829 * Update log when privacy request is confirmed. 2830 2830 * 2831 * @since 5.0.02831 * @since 4.9.6 2832 2832 * @access private 2833 2833 * 2834 * @param array $result Result of the request from the user.2834 * @param int $request_id ID of the request. 2835 2835 */ 2836 function _wp_privacy_account_request_confirmed( $result ) { 2837 if ( isset( $result['action'], $result['request_data'], $result['request_data']['privacy_request_id'] ) && in_array( $result['action'], _wp_privacy_action_request_types(), true ) ) { 2838 $privacy_request_id = absint( $result['request_data']['privacy_request_id'] ); 2839 $privacy_request = get_post( $privacy_request_id ); 2836 function _wp_privacy_account_request_confirmed( $request_id ) { 2837 $request_data = wp_get_user_request_data( $request_id ); 2840 2838 2841 if ( ! $privacy_request || ! in_array( $privacy_request->post_type, _wp_privacy_action_request_types(), true )) {2842 2843 2839 if ( ! $request_data ) { 2840 return; 2841 } 2844 2842 2845 update_post_meta( $privacy_request_id, '_confirmed_timestamp', time() ); 2846 wp_update_post( array( 2847 'ID' => $privacy_request_id, 2848 'post_status' => 'request-confirmed', 2849 ) ); 2843 if ( ! in_array( $request_data['status'], array( 'request-pending', 'request-failed' ), true ) ) { 2844 return; 2850 2845 } 2846 2847 update_post_meta( $request_id, '_wp_user_request_confirmed_timestamp', time() ); 2848 wp_update_post( array( 2849 'ID' => $request_data['request_id'], 2850 'post_status' => 'request-confirmed', 2851 ) ); 2851 2852 } 2852 add_action( 'account_action_confirmed', '_wp_privacy_account_request_confirmed' );2853 2853 2854 2854 /** 2855 * Update log when privacy request fails.2855 * Return request confirmation message HTML. 2856 2856 * 2857 * @since 5.0.02857 * @since 4.9.6 2858 2858 * @access private 2859 2859 * 2860 * @ param array $result Result of the request from the user.2860 * @return string $message The confirmation message. 2861 2861 */ 2862 function _wp_privacy_account_request_failed( $result ) { 2863 if ( isset( $result['action'], $result['request_data'], $result['request_data']['privacy_request_id'] ) && 2864 in_array( $result['action'], _wp_privacy_action_request_types(), true ) ) { 2862 function _wp_privacy_account_request_confirmed_message( $message, $request_id ) { 2863 $request = wp_get_user_request_data( $request_id ); 2865 2864 2866 $privacy_request_id = absint( $result['request_data']['privacy_request_id'] ); 2867 $privacy_request = get_post( $privacy_request_id ); 2865 if ( $request && in_array( $request['action'], _wp_privacy_action_request_types(), true ) ) { 2866 $message = '<p class="message">' . __( 'Action has been confirmed.' ) . '</p>'; 2867 $message .= __( 'The site administrator has been notified and will fulfill your request as soon as possible.' ); 2868 } 2868 2869 2869 if ( ! $privacy_request || ! in_array( $privacy_request->post_type, _wp_privacy_action_request_types(), true ) ) { 2870 return; 2871 } 2872 2873 wp_update_post( array( 2874 'ID' => $privacy_request_id, 2875 'post_status' => 'request-failed', 2876 ) ); 2877 } 2870 return $message; 2878 2871 } 2879 2872 2880 2873 /** 2881 * Send a confirmation request email to confirm anaction.2874 * Create and log a user request to perform a specific action. 2882 2875 * 2883 * @since 5.0.0 2876 * Requests are stored inside a post type named `user_request` since they can apply to both 2877 * users on the site, or guests without a user account. 2884 2878 * 2885 * @param string $email User email address. This can be the address of a registered or non-registered user. Defaults to logged in user email address. 2886 * @param string $action_name Name of the action that is being confirmed. Defaults to 'confirm_email'. 2887 * @param string $action_description User facing description of the action they will be confirming. Defaults to "confirm your email address". 2888 * @param array $request_data Misc data you want to send with the verification request and pass to the actions once the request is confirmed. 2889 * @return WP_Error|bool Will return true/false based on the success of sending the email, or a WP_Error object. 2879 * @since 4.9.6 2880 * 2881 * @param string $email_address User email address. This can be the address of a registered or non-registered user. 2882 * @param string $action_name Name of the action that is being confirmed. Required. 2883 * @param array $request_data Misc data you want to send with the verification request and pass to the actions once the request is confirmed. 2884 * @return int|WP_Error Returns the request ID if successful, or a WP_Error object on failure. 2890 2885 */ 2891 function wp_send_account_verification_key( $email = '', $action_name = '', $action_description = '', $request_data = array() ) { 2892 if ( ! function_exists( 'wp_get_current_user' ) ) { 2893 return new WP_Error( 'invalid', __( 'This function cannot be used before init.' ) ); 2894 } 2886 function wp_create_user_request( $email_address = '', $action_name = '', $request_data = array() ) { 2887 $email_address = sanitize_email( $email_address ); 2888 $action_name = sanitize_key( $action_name ); 2895 2889 2896 $action_name = sanitize_key( $action_name ); 2897 $action_description = wp_kses_post( $action_description ); 2898 2899 if ( empty( $action_name ) ) { 2900 $action_name = 'confirm_email'; 2890 if ( ! is_email( $email_address ) ) { 2891 return new WP_Error( 'invalid_email', __( 'Invalid email address' ) ); 2901 2892 } 2902 2893 2903 if ( empty( $action_description )) {2904 $action_description = __( 'Confirm your email address.');2894 if ( ! $action_name ) { 2895 return new WP_Error( 'invalid_action', __( 'Invalid action name' ) ); 2905 2896 } 2906 2897 2907 if ( empty( $email ) ) { 2908 $user = wp_get_current_user(); 2909 $email = $user->ID ? $user->user_email : ''; 2910 } else { 2911 $user = false; 2912 } 2898 $user = get_user_by( 'email', $email_address ); 2899 $user_id = $user && ! is_wp_error( $user ) ? $user->ID: 0; 2913 2900 2914 $email = sanitize_email( $email ); 2901 // Check for duplicates. 2902 $requests_query = new WP_Query( array( 2903 'post_type' => 'user_request', 2904 'title' => $action_name, 2905 'post_status' => 'any', 2906 'fields' => 'ids', 2907 'meta_query' => array( 2908 array( 2909 'key' => '_wp_user_request_user_email', 2910 'value' => $email_address, 2911 ), 2912 ), 2913 ) ); 2915 2914 2916 if ( ! is_email( $email )) {2917 return new WP_Error( ' invalid_email', __( 'Invalid email address' ) );2915 if ( $requests_query->found_posts ) { 2916 return new WP_Error( 'duplicate_request', __( 'A request for this email address already exists.' ) ); 2918 2917 } 2919 2918 2920 if ( ! $user ) { 2921 $user = get_user_by( 'email', $email ); 2919 $request_id = wp_insert_post( array( 2920 'post_author' => $user_id, 2921 'post_title' => $action_name, 2922 'post_content' => wp_json_encode( $request_data ), 2923 'post_status' => 'request-pending', 2924 'post_type' => 'user_request', 2925 'post_date' => current_time( 'mysql', false ), 2926 'post_date_gmt' => current_time( 'mysql', true ), 2927 ), true ); 2928 2929 if ( is_wp_error( $request_id ) ) { 2930 return $request_id; 2922 2931 } 2923 2932 2924 $confirm_key = wp_get_account_verification_key( $email, $action_name, $request_data ); 2933 update_post_meta( $request_id, '_wp_user_request_user_email', $email_address ); 2934 update_post_meta( $request_id, '_wp_user_request_confirmed_timestamp', false ); 2925 2935 2926 if ( is_wp_error( $confirm_key ) ) { 2927 return $confirm_key; 2936 return $request_id; 2937 } 2938 2939 /** 2940 * Get action description from the name and return a string. 2941 * 2942 * @since 4.9.6 2943 * 2944 * @param string $action_name Action name of the request. 2945 * @return string 2946 */ 2947 function wp_user_request_action_description( $action_name ) { 2948 switch ( $action_name ) { 2949 case 'export_personal_data': 2950 $description = __( 'Export Personal Data' ); 2951 break; 2952 case 'remove_personal_data': 2953 $description = __( 'Remove Personal Data' ); 2954 break; 2955 default: 2956 /* translators: %s: action name */ 2957 $description = sprintf( __( 'Confirm the "%s" action' ), $action_name ); 2958 break; 2928 2959 } 2929 2960 2930 // We could be dealing with a registered user account, or a visitor. 2931 $is_registered_user = $user && ! is_wp_error( $user ); 2961 /** 2962 * Filters the user action description. 2963 * 2964 * @param string $description The default description. 2965 * @param string $action_name The name of the request. 2966 */ 2967 return apply_filters( 'user_request_action_description', $description, $action_name ); 2968 } 2932 2969 2933 if ( $is_registered_user ) { 2934 $uid = $user->ID; 2935 } else { 2936 // Generate a UID for this email address so we don't send the actual email in the query string. Hash is not supported on all systems. 2937 $uid = function_exists( 'hash' ) ? hash( 'sha256', $email ) : sha1( $email ); 2970 /** 2971 * Send a confirmation request email to confirm an action. 2972 * 2973 * If the request is not already pending, it will be updated. 2974 * 2975 * @since 4.9.6 2976 * 2977 * @param string $request_id ID of the request created via wp_create_user_request(). 2978 * @return WP_Error|bool Will return true/false based on the success of sending the email, or a WP_Error object. 2979 */ 2980 function wp_send_user_request( $request_id ) { 2981 $request_id = absint( $request_id ); 2982 $request = get_post( $request_id ); 2983 2984 if ( ! $request || 'user_request' !== $request->post_type ) { 2985 return new WP_Error( 'user_request_error', __( 'Invalid request.' ) ); 2938 2986 } 2939 2987 2988 if ( 'request-pending' !== $request->post_status ) { 2989 wp_update_post( array( 2990 'ID' => $request_id, 2991 'post_status' => 'request-pending', 2992 'post_date' => current_time( 'mysql', false ), 2993 'post_date_gmt' => current_time( 'mysql', true ), 2994 ) ); 2995 } 2996 2997 $email_data = array( 2998 'action_name' => $request->post_title, 2999 'email' => get_post_meta( $request->ID, '_wp_user_request_user_email', true ), 3000 'description' => wp_user_request_action_description( $request->post_title ), 3001 'confirm_url' => add_query_arg( array( 3002 'action' => 'confirmaction', 3003 'request_id' => $request_id, 3004 'confirm_key' => wp_generate_user_request_key( $request_id ), 3005 ), site_url( 'wp-login.php' ) ), 3006 'sitename' => is_multisite() ? get_site_option( 'site_name' ) : get_option( 'blogname' ), 3007 'siteurl' => network_home_url(), 3008 ); 3009 2940 3010 /* translators: Do not translate DESCRIPTION, CONFIRM_URL, EMAIL, SITENAME, SITEURL: those are placeholders. */ 2941 3011 $email_text = __( 2942 3012 'Howdy, … … 2958 3028 ###SITEURL###' 2959 3029 ); 2960 3030 2961 $email_data = array(2962 'action_name' => $action_name,2963 'email' => $email,2964 'description' => $action_description,2965 'confirm_url' => add_query_arg( array(2966 'action' => 'verifyaccount',2967 'confirm_action' => $action_name,2968 'uid' => $uid,2969 'confirm_key' => $confirm_key,2970 ), site_url( 'wp-login.php' ) ),2971 'sitename' => is_multisite() ? get_site_option( 'site_name' ) : get_option( 'blogname' ),2972 'siteurl' => network_home_url(),2973 );2974 2975 3031 /** 2976 3032 * Filters the text of the email sent when an account action is attempted. 2977 3033 * … … 2983 3039 * ###SITENAME### The name of the site. 2984 3040 * ###SITEURL### The URL to the site. 2985 3041 * 2986 * @since 5.0.03042 * @since 4.9.6 2987 3043 * 2988 3044 * @param string $email_text Text in the email. 2989 3045 * @param array $email_data { … … 2997 3053 * @type string $siteurl The site URL sending the mail. 2998 3054 * } 2999 3055 */ 3000 $content = apply_filters( ' account_verification_email_content', $email_text, $email_data );3056 $content = apply_filters( 'user_request_action_email_content', $email_text, $email_data ); 3001 3057 3002 3058 $content = str_replace( '###DESCRIPTION###', $email_data['description'], $content ); 3003 3059 $content = str_replace( '###CONFIRM_URL###', esc_url_raw( $email_data['confirm_url'] ), $content ); … … 3010 3066 } 3011 3067 3012 3068 /** 3013 * Creates, stores, then returns a confirmation key for an account action.3069 * Returns a confirmation key for a user action and stores the hashed version. 3014 3070 * 3015 * @since 5.0.03071 * @since 4.9.6 3016 3072 * 3017 * @param string $email User email address. This can be the address of a registered or non-registered user. 3018 * @param string $action_name Name of the action this key is being generated for. 3019 * @param array $request_data Misc data you want to send with the verification request and pass to the actions once the request is confirmed. 3020 * @return string|WP_Error Confirmation key on success. WP_Error on error. 3073 * @param int $request_id Request ID. 3074 * @return string Confirmation key. 3021 3075 */ 3022 function wp_ge t_account_verification_key( $email, $action_name, $request_data = array()) {3076 function wp_generate_user_request_key( $request_id ) { 3023 3077 global $wp_hasher; 3024 3078 3025 if ( ! is_email( $email ) ) {3026 return new WP_Error( 'invalid_email', __( 'Invalid email address' ) );3027 }3028 3029 if ( empty( $action_name ) ) {3030 return new WP_Error( 'invalid_action', __( 'Invalid action' ) );3031 }3032 3033 $user = get_user_by( 'email', $email );3034 3035 // We could be dealing with a registered user account, or a visitor.3036 $is_registered_user = $user && ! is_wp_error( $user );3037 3038 3079 // Generate something random for a confirmation key. 3039 3080 $key = wp_generate_password( 20, false ); 3040 3081 3041 // Now insert the key, hashed, into the DB.3082 // Return the key, hashed. 3042 3083 if ( empty( $wp_hasher ) ) { 3043 3084 require_once ABSPATH . WPINC . '/class-phpass.php'; 3044 3085 $wp_hasher = new PasswordHash( 8, true ); 3045 3086 } 3046 3087 3047 $hashed_key = $wp_hasher->HashPassword( $key ); 3048 $value = array( 3049 'action' => $action_name, 3050 'time' => time(), 3051 'hash' => $hashed_key, 3052 'email' => $email, 3053 'request_data' => $request_data, 3054 ); 3088 update_post_meta( $request_id, '_wp_user_request_confirm_key', $wp_hasher->HashPassword( $key ) ); 3089 update_post_meta( $request_id, '_wp_user_request_confirm_key_timestamp', time() ); 3055 3090 3056 if ( $is_registered_user ) {3057 $key_saved = (bool) update_user_meta( $user->ID, '_verify_action_' . $action_name, wp_json_encode( $value ) );3058 } else {3059 $uid = function_exists( 'hash' ) ? hash( 'sha256', $email ) : sha1( $email );3060 $key_saved = (bool) update_site_option( '_verify_action_' . $action_name . '_' . $uid, wp_json_encode( $value ) );3061 }3062 3063 if ( false === $key_saved ) {3064 return new WP_Error( 'no_account_verification_key_update', __( 'Could not save confirm account action key to database.' ) );3065 }3066 3067 3091 return $key; 3068 3092 } 3069 3093 3070 3094 /** 3071 * Checks if a key is valid and handles the action based on this.3095 * Valdate a user request by comparing the key with the request's key. 3072 3096 * 3073 * @since 5.0.03097 * @since 4.9.6 3074 3098 * 3075 * @param string $key Key to confirm. 3076 * @param string $uid Email hash or user ID. 3077 * @param string $action_name Name of the action this key is being generated for. 3078 * @return array|WP_Error WP_Error on failure, action name and user email address on success. 3099 * @param string $request_id ID of the request being confirmed. 3100 * @param string $key Provided key to validate. 3101 * @return bool|WP_Error WP_Error on failure, true on success. 3079 3102 */ 3080 function wp_ check_account_verification_key( $key, $uid, $action_name) {3103 function wp_validate_user_request_key( $request_id, $key ) { 3081 3104 global $wp_hasher; 3082 3105 3083 if ( empty( $action_name ) || empty( $key ) || empty( $uid ) ) { 3084 return new WP_Error( 'invalid_key', __( 'Invalid key' ) ); 3106 $request_id = absint( $request_id ); 3107 $request = wp_get_user_request_data( $request_id ); 3108 3109 if ( ! $request ) { 3110 return new WP_Error( 'user_request_error', __( 'Invalid request.' ) ); 3085 3111 } 3086 3112 3087 $user = false; 3113 if ( ! in_array( $request['status'], array( 'request-pending', 'request-failed' ), true ) ) { 3114 return __( 'This link has expired.' ); 3115 } 3088 3116 3089 if ( is_numeric( $uid) ) {3090 $user = get_user_by( 'id', absint( $uid) );3117 if ( empty( $key ) ) { 3118 return new WP_Error( 'invalid_key', __( 'Invalid key' ) ); 3091 3119 } 3092 3120 3093 // We could be dealing with a registered user account, or a visitor.3094 $is_registered_user = ( $user && ! is_wp_error( $user ) );3095 $key_request_time = '';3096 $saved_key = '';3097 $email = '';3098 3099 3121 if ( empty( $wp_hasher ) ) { 3100 3122 require_once ABSPATH . WPINC . '/class-phpass.php'; 3101 3123 $wp_hasher = new PasswordHash( 8, true ); 3102 3124 } 3103 3125 3104 // Get the saved key from the database. 3105 if ( $is_registered_user ) { 3106 $raw_data = get_user_meta( $user->ID, '_verify_action_' . $action_name, true ); 3107 $email = $user->user_email; 3126 $key_request_time = $request['confirm_key_timestamp']; 3127 $saved_key = $request['confirm_key']; 3108 3128 3109 if ( false !== strpos( $raw_data, ':' ) ) {3110 list( $key_request_time, $saved_key ) = explode( ':', $raw_data, 2 );3111 }3112 } else {3113 $raw_data = get_site_option( '_verify_action_' . $action_name . '_' . $uid, '' );3114 3115 if ( false !== strpos( $raw_data, ':' ) ) {3116 list( $key_request_time, $saved_key, $email ) = explode( ':', $raw_data, 3 );3117 }3118 }3119 3120 $data = json_decode( $raw_data, true );3121 $key_request_time = (int) isset( $data['time'] ) ? $data['time'] : 0;3122 $saved_key = isset( $data['hash'] ) ? $data['hash'] : '';3123 $email = sanitize_email( isset( $data['email'] ) ? $data['email'] : '' );3124 $request_data = isset( $data['request_data'] ) ? $data['request_data'] : array();3125 3126 3129 if ( ! $saved_key ) { 3127 3130 return new WP_Error( 'invalid_key', __( 'Invalid key' ) ); 3128 3131 } 3129 3132 3130 if ( ! $key_request_time || ! $email) {3133 if ( ! $key_request_time ) { 3131 3134 return new WP_Error( 'invalid_key', __( 'Invalid action' ) ); 3132 3135 } 3133 3136 … … 3134 3137 /** 3135 3138 * Filters the expiration time of confirm keys. 3136 3139 * 3137 * @since 5.0.03140 * @since 4.9.6 3138 3141 * 3139 3142 * @param int $expiration The expiration time in seconds. 3140 3143 */ 3141 $expiration_duration = apply_filters( 'account_verification_expiration', DAY_IN_SECONDS );3144 $expiration_duration = (int) apply_filters( 'user_request_key_expiration', DAY_IN_SECONDS ); 3142 3145 $expiration_time = $key_request_time + $expiration_duration; 3143 3146 3144 3147 if ( ! $wp_hasher->CheckPassword( $key, $saved_key ) ) { … … 3145 3148 return new WP_Error( 'invalid_key', __( 'Invalid key' ) ); 3146 3149 } 3147 3150 3148 if ( $expiration_time && time() < $expiration_time ) { 3149 $return = array( 3150 'action' => $action_name, 3151 'email' => $email, 3152 'request_data' => $request_data, 3153 ); 3154 } else { 3151 if ( ! $expiration_time || time() > $expiration_time ) { 3155 3152 $return = new WP_Error( 'expired_key', __( 'The confirmation email has expired.' ) ); 3156 3153 } 3157 3154 3158 // Clean up stored keys. 3159 if ( $is_registered_user ) { 3160 delete_user_meta( $user->ID, '_verify_action_' . $action_name ); 3161 } else { 3162 delete_site_option( '_verify_action_' . $action_name . '_' . $uid ); 3155 return true; 3156 } 3157 3158 /** 3159 * Return data about a user request. 3160 * 3161 * @since 4.9.6 3162 * 3163 * @param int $request_id Request ID to get data about. 3164 * @return array|false 3165 */ 3166 function wp_get_user_request_data( $request_id ) { 3167 $request_id = absint( $request_id ); 3168 $request = get_post( $request_id ); 3169 3170 if ( ! $request || 'user_request' !== $request->post_type ) { 3171 return false; 3163 3172 } 3164 3173 3165 return $return; 3174 return array( 3175 'request_id' => $request->ID, 3176 'user_id' => $request->post_author, 3177 'email' => get_post_meta( $request->ID, '_wp_user_request_user_email', true ), 3178 'action' => $request->post_title, 3179 'requested_timestamp' => strtotime( $request->post_date_gmt ), 3180 'confirmed_timestamp' => get_post_meta( $request->ID, '_wp_user_request_confirmed_timestamp', true ), 3181 'completed_timestamp' => get_post_meta( $request->ID, '_wp_user_request_completed_timestamp', true ), 3182 'request_data' => json_decode( $request->post_content, true ), 3183 'status' => $request->post_status, 3184 'confirm_key' => get_post_meta( $request_id, '_wp_user_request_confirm_key', true ), 3185 'confirm_key_timestamp' => get_post_meta( $request_id, '_wp_user_request_confirm_key_timestamp', true ), 3186 ); 3166 3187 } -
src/wp-login.php
427 427 } 428 428 429 429 // validate action so as to default to the login screen 430 if ( ! in_array( $action, array( 'postpass', 'logout', 'lostpassword', 'retrievepassword', 'resetpass', 'rp', 'register', 'login', ' verifyaccount' ), true ) && false === has_filter( 'login_form_' . $action ) ) {430 if ( ! in_array( $action, array( 'postpass', 'logout', 'lostpassword', 'retrievepassword', 'resetpass', 'rp', 'register', 'login', 'confirmaction' ), true ) && false === has_filter( 'login_form_' . $action ) ) { 431 431 $action = 'login'; 432 432 } 433 433 … … 858 858 859 859 break; 860 860 861 case 'verifyaccount' : 862 if ( isset( $_GET['confirm_action'], $_GET['confirm_key'], $_GET['uid'] ) ) { 863 $key = sanitize_text_field( wp_unslash( $_GET['confirm_key'] ) ); 864 $uid = sanitize_text_field( wp_unslash( $_GET['uid'] ) ); 865 $action_name = sanitize_key( wp_unslash( $_GET['confirm_action'] ) ); 866 $result = wp_check_account_verification_key( $key, $uid, $action_name ); 861 case 'confirmaction' : 862 if ( ! isset( $_GET['request_id'] ) ) { 863 wp_die( __( 'Invalid request' ) ); 864 } 865 866 $request_id = (int) $_GET['request_id']; 867 868 if ( isset( $_GET['confirm_key'] ) ) { 869 $key = sanitize_text_field( wp_unslash( $_GET['confirm_key'] ) ); 870 $result = wp_validate_user_request_key( $request_id, $key ); 867 871 } else { 868 872 $result = new WP_Error( 'invalid_key', __( 'Invalid key' ) ); 869 873 } 870 874 871 875 if ( is_wp_error( $result ) ) { 872 /**873 * Fires an action hook when the account action was not confirmed.874 *875 * After running this action hook the page will die.876 *877 * @param WP_Error $result Error object.878 */879 do_action( 'account_action_failed', $result );880 881 876 wp_die( $result ); 882 877 } 883 878 … … 890 885 * After firing this action hook the page will redirect to wp-login a callback 891 886 * redirects or exits first. 892 887 * 893 * @param array $result { 894 * Data about the action which was confirmed. 895 * 896 * @type string $action Name of the action that was confirmed. 897 * @type string $email Email of the user who confirmed the action. 898 * } 888 * @param int $request_id Request ID. 899 889 */ 900 do_action( ' account_action_confirmed', $result);890 do_action( 'user_request_action_confirmed', $request_id ); 901 891 902 $message = '<p class="message">' . __( 'Action has been confirmed.' ) . '</p>'; 903 login_header( '', $message ); 892 $message = apply_filters( 'user_request_action_confirmed_message', '<p class="message">' . __( 'Action has been confirmed.' ) . '</p>', $request_id ); 893 894 login_header( __( 'User action confirmed.' ), $message ); 904 895 login_footer(); 905 896 exit; 906 897