580 wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), |
592 wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), |
581 home_url(), |
593 home_url(), |
582 wp_specialchars_decode( translate_user_role( $role['name'] ) ) |
594 wp_specialchars_decode( translate_user_role( $role['name'] ) ) |
583 ); |
595 ); |
584 } |
596 } |
585 |
|
586 /** |
|
587 * Resend an existing request and return the result. |
|
588 * |
|
589 * @since 4.9.6 |
|
590 * @access private |
|
591 * |
|
592 * @param int $request_id Request ID. |
|
593 * @return bool|WP_Error Returns true/false based on the success of sending the email, or a WP_Error object. |
|
594 */ |
|
595 function _wp_privacy_resend_request( $request_id ) { |
|
596 $request_id = absint( $request_id ); |
|
597 $request = get_post( $request_id ); |
|
598 |
|
599 if ( ! $request || 'user_request' !== $request->post_type ) { |
|
600 return new WP_Error( 'privacy_request_error', __( 'Invalid request.' ) ); |
|
601 } |
|
602 |
|
603 $result = wp_send_user_request( $request_id ); |
|
604 |
|
605 if ( is_wp_error( $result ) ) { |
|
606 return $result; |
|
607 } elseif ( ! $result ) { |
|
608 return new WP_Error( 'privacy_request_error', __( 'Unable to initiate confirmation request.' ) ); |
|
609 } |
|
610 |
|
611 return true; |
|
612 } |
|
613 |
|
614 /** |
|
615 * Marks a request as completed by the admin and logs the current timestamp. |
|
616 * |
|
617 * @since 4.9.6 |
|
618 * @access private |
|
619 * |
|
620 * @param int $request_id Request ID. |
|
621 * @return int|WP_Error $result Request ID on success or WP_Error. |
|
622 */ |
|
623 function _wp_privacy_completed_request( $request_id ) { |
|
624 $request_id = absint( $request_id ); |
|
625 $request = wp_get_user_request_data( $request_id ); |
|
626 |
|
627 if ( ! $request ) { |
|
628 return new WP_Error( 'privacy_request_error', __( 'Invalid request.' ) ); |
|
629 } |
|
630 |
|
631 update_post_meta( $request_id, '_wp_user_request_completed_timestamp', time() ); |
|
632 |
|
633 $result = wp_update_post( |
|
634 array( |
|
635 'ID' => $request_id, |
|
636 'post_status' => 'request-completed', |
|
637 ) |
|
638 ); |
|
639 |
|
640 return $result; |
|
641 } |
|
642 |
|
643 /** |
|
644 * Handle list table actions. |
|
645 * |
|
646 * @since 4.9.6 |
|
647 * @access private |
|
648 */ |
|
649 function _wp_personal_data_handle_actions() { |
|
650 if ( isset( $_POST['privacy_action_email_retry'] ) ) { |
|
651 check_admin_referer( 'bulk-privacy_requests' ); |
|
652 |
|
653 $request_id = absint( current( array_keys( (array) wp_unslash( $_POST['privacy_action_email_retry'] ) ) ) ); |
|
654 $result = _wp_privacy_resend_request( $request_id ); |
|
655 |
|
656 if ( is_wp_error( $result ) ) { |
|
657 add_settings_error( |
|
658 'privacy_action_email_retry', |
|
659 'privacy_action_email_retry', |
|
660 $result->get_error_message(), |
|
661 'error' |
|
662 ); |
|
663 } else { |
|
664 add_settings_error( |
|
665 'privacy_action_email_retry', |
|
666 'privacy_action_email_retry', |
|
667 __( 'Confirmation request sent again successfully.' ), |
|
668 'updated' |
|
669 ); |
|
670 } |
|
671 } elseif ( isset( $_POST['action'] ) ) { |
|
672 $action = isset( $_POST['action'] ) ? sanitize_key( wp_unslash( $_POST['action'] ) ) : ''; |
|
673 |
|
674 switch ( $action ) { |
|
675 case 'add_export_personal_data_request': |
|
676 case 'add_remove_personal_data_request': |
|
677 check_admin_referer( 'personal-data-request' ); |
|
678 |
|
679 if ( ! isset( $_POST['type_of_action'], $_POST['username_or_email_for_privacy_request'] ) ) { |
|
680 add_settings_error( |
|
681 'action_type', |
|
682 'action_type', |
|
683 __( 'Invalid action.' ), |
|
684 'error' |
|
685 ); |
|
686 } |
|
687 $action_type = sanitize_text_field( wp_unslash( $_POST['type_of_action'] ) ); |
|
688 $username_or_email_address = sanitize_text_field( wp_unslash( $_POST['username_or_email_for_privacy_request'] ) ); |
|
689 $email_address = ''; |
|
690 |
|
691 if ( ! in_array( $action_type, _wp_privacy_action_request_types(), true ) ) { |
|
692 add_settings_error( |
|
693 'action_type', |
|
694 'action_type', |
|
695 __( 'Invalid action.' ), |
|
696 'error' |
|
697 ); |
|
698 } |
|
699 |
|
700 if ( ! is_email( $username_or_email_address ) ) { |
|
701 $user = get_user_by( 'login', $username_or_email_address ); |
|
702 if ( ! $user instanceof WP_User ) { |
|
703 add_settings_error( |
|
704 'username_or_email_for_privacy_request', |
|
705 'username_or_email_for_privacy_request', |
|
706 __( 'Unable to add this request. A valid email address or username must be supplied.' ), |
|
707 'error' |
|
708 ); |
|
709 } else { |
|
710 $email_address = $user->user_email; |
|
711 } |
|
712 } else { |
|
713 $email_address = $username_or_email_address; |
|
714 } |
|
715 |
|
716 if ( empty( $email_address ) ) { |
|
717 break; |
|
718 } |
|
719 |
|
720 $request_id = wp_create_user_request( $email_address, $action_type ); |
|
721 |
|
722 if ( is_wp_error( $request_id ) ) { |
|
723 add_settings_error( |
|
724 'username_or_email_for_privacy_request', |
|
725 'username_or_email_for_privacy_request', |
|
726 $request_id->get_error_message(), |
|
727 'error' |
|
728 ); |
|
729 break; |
|
730 } elseif ( ! $request_id ) { |
|
731 add_settings_error( |
|
732 'username_or_email_for_privacy_request', |
|
733 'username_or_email_for_privacy_request', |
|
734 __( 'Unable to initiate confirmation request.' ), |
|
735 'error' |
|
736 ); |
|
737 break; |
|
738 } |
|
739 |
|
740 wp_send_user_request( $request_id ); |
|
741 |
|
742 add_settings_error( |
|
743 'username_or_email_for_privacy_request', |
|
744 'username_or_email_for_privacy_request', |
|
745 __( 'Confirmation request initiated successfully.' ), |
|
746 'updated' |
|
747 ); |
|
748 break; |
|
749 } |
|
750 } |
|
751 } |
|
752 |
|
753 /** |
|
754 * Cleans up failed and expired requests before displaying the list table. |
|
755 * |
|
756 * @since 4.9.6 |
|
757 * @access private |
|
758 */ |
|
759 function _wp_personal_data_cleanup_requests() { |
|
760 /** This filter is documented in wp-includes/user.php */ |
|
761 $expires = (int) apply_filters( 'user_request_key_expiration', DAY_IN_SECONDS ); |
|
762 |
|
763 $requests_query = new WP_Query( |
|
764 array( |
|
765 'post_type' => 'user_request', |
|
766 'posts_per_page' => -1, |
|
767 'post_status' => 'request-pending', |
|
768 'fields' => 'ids', |
|
769 'date_query' => array( |
|
770 array( |
|
771 'column' => 'post_modified_gmt', |
|
772 'before' => $expires . ' seconds ago', |
|
773 ), |
|
774 ), |
|
775 ) |
|
776 ); |
|
777 |
|
778 $request_ids = $requests_query->posts; |
|
779 |
|
780 foreach ( $request_ids as $request_id ) { |
|
781 wp_update_post( |
|
782 array( |
|
783 'ID' => $request_id, |
|
784 'post_status' => 'request-failed', |
|
785 'post_password' => '', |
|
786 ) |
|
787 ); |
|
788 } |
|
789 } |
|
790 |
|
791 /** |
|
792 * Personal data export. |
|
793 * |
|
794 * @since 4.9.6 |
|
795 * @access private |
|
796 */ |
|
797 function _wp_personal_data_export_page() { |
|
798 if ( ! current_user_can( 'export_others_personal_data' ) ) { |
|
799 wp_die( __( 'Sorry, you are not allowed to export personal data on this site.' ) ); |
|
800 } |
|
801 |
|
802 _wp_personal_data_handle_actions(); |
|
803 _wp_personal_data_cleanup_requests(); |
|
804 |
|
805 // "Borrow" xfn.js for now so we don't have to create new files. |
|
806 wp_enqueue_script( 'xfn' ); |
|
807 |
|
808 $requests_table = new WP_Privacy_Data_Export_Requests_Table( |
|
809 array( |
|
810 'plural' => 'privacy_requests', |
|
811 'singular' => 'privacy_request', |
|
812 'screen' => 'export_personal_data', |
|
813 ) |
|
814 ); |
|
815 |
|
816 $requests_table->screen->set_screen_reader_content( |
|
817 array( |
|
818 'heading_views' => __( 'Filter export personal data list' ), |
|
819 'heading_pagination' => __( 'Export personal data list navigation' ), |
|
820 'heading_list' => __( 'Export personal data list' ), |
|
821 ) |
|
822 ); |
|
823 |
|
824 $requests_table->process_bulk_action(); |
|
825 $requests_table->prepare_items(); |
|
826 ?> |
|
827 <div class="wrap nosubsub"> |
|
828 <h1><?php esc_html_e( 'Export Personal Data' ); ?></h1> |
|
829 <hr class="wp-header-end" /> |
|
830 |
|
831 <?php settings_errors(); ?> |
|
832 |
|
833 <form action="<?php echo esc_url( admin_url( 'tools.php?page=export_personal_data' ) ); ?>" method="post" class="wp-privacy-request-form"> |
|
834 <h2><?php esc_html_e( 'Add Data Export Request' ); ?></h2> |
|
835 <p><?php esc_html_e( 'An email will be sent to the user at this email address asking them to verify the request.' ); ?></p> |
|
836 |
|
837 <div class="wp-privacy-request-form-field"> |
|
838 <label for="username_or_email_for_privacy_request"><?php esc_html_e( 'Username or email address' ); ?></label> |
|
839 <input type="text" required class="regular-text" id="username_or_email_for_privacy_request" name="username_or_email_for_privacy_request" /> |
|
840 <?php submit_button( __( 'Send Request' ), 'secondary', 'submit', false ); ?> |
|
841 </div> |
|
842 <?php wp_nonce_field( 'personal-data-request' ); ?> |
|
843 <input type="hidden" name="action" value="add_export_personal_data_request" /> |
|
844 <input type="hidden" name="type_of_action" value="export_personal_data" /> |
|
845 </form> |
|
846 <hr /> |
|
847 |
|
848 <?php $requests_table->views(); ?> |
|
849 |
|
850 <form class="search-form wp-clearfix"> |
|
851 <?php $requests_table->search_box( __( 'Search Requests' ), 'requests' ); ?> |
|
852 <input type="hidden" name="page" value="export_personal_data" /> |
|
853 <input type="hidden" name="filter-status" value="<?php echo isset( $_REQUEST['filter-status'] ) ? esc_attr( sanitize_text_field( $_REQUEST['filter-status'] ) ) : ''; ?>" /> |
|
854 <input type="hidden" name="orderby" value="<?php echo isset( $_REQUEST['orderby'] ) ? esc_attr( sanitize_text_field( $_REQUEST['orderby'] ) ) : ''; ?>" /> |
|
855 <input type="hidden" name="order" value="<?php echo isset( $_REQUEST['order'] ) ? esc_attr( sanitize_text_field( $_REQUEST['order'] ) ) : ''; ?>" /> |
|
856 </form> |
|
857 |
|
858 <form method="post"> |
|
859 <?php |
|
860 $requests_table->display(); |
|
861 $requests_table->embed_scripts(); |
|
862 ?> |
|
863 </form> |
|
864 </div> |
|
865 <?php |
|
866 } |
|
867 |
|
868 /** |
|
869 * Personal data anonymization. |
|
870 * |
|
871 * @since 4.9.6 |
|
872 * @access private |
|
873 */ |
|
874 function _wp_personal_data_removal_page() { |
|
875 /* |
|
876 * Require both caps in order to make it explicitly clear that delegating |
|
877 * erasure from network admins to single-site admins will give them the |
|
878 * ability to affect global users, rather than being limited to the site |
|
879 * that they administer. |
|
880 */ |
|
881 if ( ! current_user_can( 'erase_others_personal_data' ) || ! current_user_can( 'delete_users' ) ) { |
|
882 wp_die( __( 'Sorry, you are not allowed to erase data on this site.' ) ); |
|
883 } |
|
884 |
|
885 _wp_personal_data_handle_actions(); |
|
886 _wp_personal_data_cleanup_requests(); |
|
887 |
|
888 // "Borrow" xfn.js for now so we don't have to create new files. |
|
889 wp_enqueue_script( 'xfn' ); |
|
890 |
|
891 $requests_table = new WP_Privacy_Data_Removal_Requests_Table( |
|
892 array( |
|
893 'plural' => 'privacy_requests', |
|
894 'singular' => 'privacy_request', |
|
895 'screen' => 'remove_personal_data', |
|
896 ) |
|
897 ); |
|
898 |
|
899 $requests_table->screen->set_screen_reader_content( |
|
900 array( |
|
901 'heading_views' => __( 'Filter erase personal data list' ), |
|
902 'heading_pagination' => __( 'Erase personal data list navigation' ), |
|
903 'heading_list' => __( 'Erase personal data list' ), |
|
904 ) |
|
905 ); |
|
906 |
|
907 $requests_table->process_bulk_action(); |
|
908 $requests_table->prepare_items(); |
|
909 |
|
910 ?> |
|
911 <div class="wrap nosubsub"> |
|
912 <h1><?php esc_html_e( 'Erase Personal Data' ); ?></h1> |
|
913 <hr class="wp-header-end" /> |
|
914 |
|
915 <?php settings_errors(); ?> |
|
916 |
|
917 <form action="<?php echo esc_url( admin_url( 'tools.php?page=remove_personal_data' ) ); ?>" method="post" class="wp-privacy-request-form"> |
|
918 <h2><?php esc_html_e( 'Add Data Erasure Request' ); ?></h2> |
|
919 <p><?php esc_html_e( 'An email will be sent to the user at this email address asking them to verify the request.' ); ?></p> |
|
920 |
|
921 <div class="wp-privacy-request-form-field"> |
|
922 <label for="username_or_email_for_privacy_request"><?php esc_html_e( 'Username or email address' ); ?></label> |
|
923 <input type="text" required class="regular-text" id="username_or_email_for_privacy_request" name="username_or_email_for_privacy_request" /> |
|
924 <?php submit_button( __( 'Send Request' ), 'secondary', 'submit', false ); ?> |
|
925 </div> |
|
926 <?php wp_nonce_field( 'personal-data-request' ); ?> |
|
927 <input type="hidden" name="action" value="add_remove_personal_data_request" /> |
|
928 <input type="hidden" name="type_of_action" value="remove_personal_data" /> |
|
929 </form> |
|
930 <hr /> |
|
931 |
|
932 <?php $requests_table->views(); ?> |
|
933 |
|
934 <form class="search-form wp-clearfix"> |
|
935 <?php $requests_table->search_box( __( 'Search Requests' ), 'requests' ); ?> |
|
936 <input type="hidden" name="page" value="remove_personal_data" /> |
|
937 <input type="hidden" name="filter-status" value="<?php echo isset( $_REQUEST['filter-status'] ) ? esc_attr( sanitize_text_field( $_REQUEST['filter-status'] ) ) : ''; ?>" /> |
|
938 <input type="hidden" name="orderby" value="<?php echo isset( $_REQUEST['orderby'] ) ? esc_attr( sanitize_text_field( $_REQUEST['orderby'] ) ) : ''; ?>" /> |
|
939 <input type="hidden" name="order" value="<?php echo isset( $_REQUEST['order'] ) ? esc_attr( sanitize_text_field( $_REQUEST['order'] ) ) : ''; ?>" /> |
|
940 </form> |
|
941 |
|
942 <form method="post"> |
|
943 <?php |
|
944 $requests_table->display(); |
|
945 $requests_table->embed_scripts(); |
|
946 ?> |
|
947 </form> |
|
948 </div> |
|
949 <?php |
|
950 } |
|
951 |
|
952 /** |
|
953 * Mark erasure requests as completed after processing is finished. |
|
954 * |
|
955 * This intercepts the Ajax responses to personal data eraser page requests, and |
|
956 * monitors the status of a request. Once all of the processing has finished, the |
|
957 * request is marked as completed. |
|
958 * |
|
959 * @since 4.9.6 |
|
960 * |
|
961 * @see wp_privacy_personal_data_erasure_page |
|
962 * |
|
963 * @param array $response The response from the personal data eraser for |
|
964 * the given page. |
|
965 * @param int $eraser_index The index of the personal data eraser. Begins |
|
966 * at 1. |
|
967 * @param string $email_address The email address of the user whose personal |
|
968 * data this is. |
|
969 * @param int $page The page of personal data for this eraser. |
|
970 * Begins at 1. |
|
971 * @param int $request_id The request ID for this personal data erasure. |
|
972 * @return array The filtered response. |
|
973 */ |
|
974 function wp_privacy_process_personal_data_erasure_page( $response, $eraser_index, $email_address, $page, $request_id ) { |
|
975 /* |
|
976 * If the eraser response is malformed, don't attempt to consume it; let it |
|
977 * pass through, so that the default Ajax processing will generate a warning |
|
978 * to the user. |
|
979 */ |
|
980 if ( ! is_array( $response ) ) { |
|
981 return $response; |
|
982 } |
|
983 |
|
984 if ( ! array_key_exists( 'done', $response ) ) { |
|
985 return $response; |
|
986 } |
|
987 |
|
988 if ( ! array_key_exists( 'items_removed', $response ) ) { |
|
989 return $response; |
|
990 } |
|
991 |
|
992 if ( ! array_key_exists( 'items_retained', $response ) ) { |
|
993 return $response; |
|
994 } |
|
995 |
|
996 if ( ! array_key_exists( 'messages', $response ) ) { |
|
997 return $response; |
|
998 } |
|
999 |
|
1000 $request = wp_get_user_request_data( $request_id ); |
|
1001 |
|
1002 if ( ! $request || 'remove_personal_data' !== $request->action_name ) { |
|
1003 wp_send_json_error( __( 'Invalid request ID when processing eraser data.' ) ); |
|
1004 } |
|
1005 |
|
1006 /** This filter is documented in wp-admin/includes/ajax-actions.php */ |
|
1007 $erasers = apply_filters( 'wp_privacy_personal_data_erasers', array() ); |
|
1008 $is_last_eraser = count( $erasers ) === $eraser_index; |
|
1009 $eraser_done = $response['done']; |
|
1010 |
|
1011 if ( ! $is_last_eraser || ! $eraser_done ) { |
|
1012 return $response; |
|
1013 } |
|
1014 |
|
1015 _wp_privacy_completed_request( $request_id ); |
|
1016 |
|
1017 /** |
|
1018 * Fires immediately after a personal data erasure request has been marked completed. |
|
1019 * |
|
1020 * @since 4.9.6 |
|
1021 * |
|
1022 * @param int $request_id The privacy request post ID associated with this request. |
|
1023 */ |
|
1024 do_action( 'wp_privacy_personal_data_erased', $request_id ); |
|
1025 |
|
1026 return $response; |
|
1027 } |
|
1028 |
|
1029 /** |
|
1030 * Add requests pages. |
|
1031 * |
|
1032 * @since 4.9.6 |
|
1033 * @access private |
|
1034 */ |
|
1035 function _wp_privacy_hook_requests_page() { |
|
1036 add_submenu_page( 'tools.php', __( 'Export Personal Data' ), __( 'Export Personal Data' ), 'export_others_personal_data', 'export_personal_data', '_wp_personal_data_export_page' ); |
|
1037 add_submenu_page( 'tools.php', __( 'Erase Personal Data' ), __( 'Erase Personal Data' ), 'erase_others_personal_data', 'remove_personal_data', '_wp_personal_data_removal_page' ); |
|
1038 } |
|
1039 |
|
1040 /** |
|
1041 * Add options for the privacy requests screens. |
|
1042 * |
|
1043 * @since 4.9.8 |
|
1044 * @access private |
|
1045 */ |
|
1046 function _wp_privacy_requests_screen_options() { |
|
1047 $args = array( |
|
1048 'option' => str_replace( 'tools_page_', '', get_current_screen()->id ) . '_requests_per_page', |
|
1049 ); |
|
1050 add_screen_option( 'per_page', $args ); |
|
1051 } |
|
1052 |
|
1053 // TODO: move the following classes in new files. |
|
1054 if ( ! class_exists( 'WP_List_Table' ) ) { |
|
1055 require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' ); |
|
1056 } |
|
1057 |
|
1058 /** |
|
1059 * WP_Privacy_Requests_Table class. |
|
1060 * |
|
1061 * @since 4.9.6 |
|
1062 */ |
|
1063 abstract class WP_Privacy_Requests_Table extends WP_List_Table { |
|
1064 |
|
1065 /** |
|
1066 * Action name for the requests this table will work with. Classes |
|
1067 * which inherit from WP_Privacy_Requests_Table should define this. |
|
1068 * |
|
1069 * Example: 'export_personal_data'. |
|
1070 * |
|
1071 * @since 4.9.6 |
|
1072 * |
|
1073 * @var string $request_type Name of action. |
|
1074 */ |
|
1075 protected $request_type = 'INVALID'; |
|
1076 |
|
1077 /** |
|
1078 * Post type to be used. |
|
1079 * |
|
1080 * @since 4.9.6 |
|
1081 * |
|
1082 * @var string $post_type The post type. |
|
1083 */ |
|
1084 protected $post_type = 'INVALID'; |
|
1085 |
|
1086 /** |
|
1087 * Get columns to show in the list table. |
|
1088 * |
|
1089 * @since 4.9.6 |
|
1090 * |
|
1091 * @return array Array of columns. |
|
1092 */ |
|
1093 public function get_columns() { |
|
1094 $columns = array( |
|
1095 'cb' => '<input type="checkbox" />', |
|
1096 'email' => __( 'Requester' ), |
|
1097 'status' => __( 'Status' ), |
|
1098 'created_timestamp' => __( 'Requested' ), |
|
1099 'next_steps' => __( 'Next Steps' ), |
|
1100 ); |
|
1101 return $columns; |
|
1102 } |
|
1103 |
|
1104 /** |
|
1105 * Get a list of sortable columns. |
|
1106 * |
|
1107 * @since 4.9.6 |
|
1108 * |
|
1109 * @return array Default sortable columns. |
|
1110 */ |
|
1111 protected function get_sortable_columns() { |
|
1112 // The initial sorting is by 'Requested' (post_date) and descending. |
|
1113 // With initial sorting, the first click on 'Requested' should be ascending. |
|
1114 // With 'Requester' sorting active, the next click on 'Requested' should be descending. |
|
1115 $desc_first = isset( $_GET['orderby'] ); |
|
1116 |
|
1117 return array( |
|
1118 'email' => 'requester', |
|
1119 'created_timestamp' => array( 'requested', $desc_first ), |
|
1120 ); |
|
1121 } |
|
1122 |
|
1123 /** |
|
1124 * Default primary column. |
|
1125 * |
|
1126 * @since 4.9.6 |
|
1127 * |
|
1128 * @return string Default primary column name. |
|
1129 */ |
|
1130 protected function get_default_primary_column_name() { |
|
1131 return 'email'; |
|
1132 } |
|
1133 |
|
1134 /** |
|
1135 * Count number of requests for each status. |
|
1136 * |
|
1137 * @since 4.9.6 |
|
1138 * |
|
1139 * @return object Number of posts for each status. |
|
1140 */ |
|
1141 protected function get_request_counts() { |
|
1142 global $wpdb; |
|
1143 |
|
1144 $cache_key = $this->post_type . '-' . $this->request_type; |
|
1145 $counts = wp_cache_get( $cache_key, 'counts' ); |
|
1146 |
|
1147 if ( false !== $counts ) { |
|
1148 return $counts; |
|
1149 } |
|
1150 |
|
1151 $query = " |
|
1152 SELECT post_status, COUNT( * ) AS num_posts |
|
1153 FROM {$wpdb->posts} |
|
1154 WHERE post_type = %s |
|
1155 AND post_name = %s |
|
1156 GROUP BY post_status"; |
|
1157 |
|
1158 $results = (array) $wpdb->get_results( $wpdb->prepare( $query, $this->post_type, $this->request_type ), ARRAY_A ); |
|
1159 $counts = array_fill_keys( get_post_stati(), 0 ); |
|
1160 |
|
1161 foreach ( $results as $row ) { |
|
1162 $counts[ $row['post_status'] ] = $row['num_posts']; |
|
1163 } |
|
1164 |
|
1165 $counts = (object) $counts; |
|
1166 wp_cache_set( $cache_key, $counts, 'counts' ); |
|
1167 |
|
1168 return $counts; |
|
1169 } |
|
1170 |
|
1171 /** |
|
1172 * Get an associative array ( id => link ) with the list of views available on this table. |
|
1173 * |
|
1174 * @since 4.9.6 |
|
1175 * |
|
1176 * @return array Associative array of views in the format of $view_name => $view_markup. |
|
1177 */ |
|
1178 protected function get_views() { |
|
1179 $current_status = isset( $_REQUEST['filter-status'] ) ? sanitize_text_field( $_REQUEST['filter-status'] ) : ''; |
|
1180 $statuses = _wp_privacy_statuses(); |
|
1181 $views = array(); |
|
1182 $admin_url = admin_url( 'tools.php?page=' . $this->request_type ); |
|
1183 $counts = $this->get_request_counts(); |
|
1184 $total_requests = absint( array_sum( (array) $counts ) ); |
|
1185 |
|
1186 $current_link_attributes = empty( $current_status ) ? ' class="current" aria-current="page"' : ''; |
|
1187 $status_label = sprintf( |
|
1188 /* translators: %s: all requests count */ |
|
1189 _nx( |
|
1190 'All <span class="count">(%s)</span>', |
|
1191 'All <span class="count">(%s)</span>', |
|
1192 $total_requests, |
|
1193 'requests' |
|
1194 ), |
|
1195 number_format_i18n( $total_requests ) |
|
1196 ); |
|
1197 |
|
1198 $views['all'] = sprintf( |
|
1199 '<a href="%s"%s>%s</a>', |
|
1200 esc_url( $admin_url ), |
|
1201 $current_link_attributes, |
|
1202 $status_label |
|
1203 ); |
|
1204 |
|
1205 foreach ( $statuses as $status => $label ) { |
|
1206 $post_status = get_post_status_object( $status ); |
|
1207 if ( ! $post_status ) { |
|
1208 continue; |
|
1209 } |
|
1210 |
|
1211 $current_link_attributes = $status === $current_status ? ' class="current" aria-current="page"' : ''; |
|
1212 $total_status_requests = absint( $counts->{$status} ); |
|
1213 $status_label = sprintf( |
|
1214 translate_nooped_plural( $post_status->label_count, $total_status_requests ), |
|
1215 number_format_i18n( $total_status_requests ) |
|
1216 ); |
|
1217 $status_link = add_query_arg( 'filter-status', $status, $admin_url ); |
|
1218 |
|
1219 $views[ $status ] = sprintf( |
|
1220 '<a href="%s"%s>%s</a>', |
|
1221 esc_url( $status_link ), |
|
1222 $current_link_attributes, |
|
1223 $status_label |
|
1224 ); |
|
1225 } |
|
1226 |
|
1227 return $views; |
|
1228 } |
|
1229 |
|
1230 /** |
|
1231 * Get bulk actions. |
|
1232 * |
|
1233 * @since 4.9.6 |
|
1234 * |
|
1235 * @return array List of bulk actions. |
|
1236 */ |
|
1237 protected function get_bulk_actions() { |
|
1238 return array( |
|
1239 'delete' => __( 'Remove' ), |
|
1240 'resend' => __( 'Resend email' ), |
|
1241 ); |
|
1242 } |
|
1243 |
|
1244 /** |
|
1245 * Process bulk actions. |
|
1246 * |
|
1247 * @since 4.9.6 |
|
1248 */ |
|
1249 public function process_bulk_action() { |
|
1250 $action = $this->current_action(); |
|
1251 $request_ids = isset( $_REQUEST['request_id'] ) ? wp_parse_id_list( wp_unslash( $_REQUEST['request_id'] ) ) : array(); |
|
1252 |
|
1253 $count = 0; |
|
1254 |
|
1255 if ( $request_ids ) { |
|
1256 check_admin_referer( 'bulk-privacy_requests' ); |
|
1257 } |
|
1258 |
|
1259 switch ( $action ) { |
|
1260 case 'delete': |
|
1261 foreach ( $request_ids as $request_id ) { |
|
1262 if ( wp_delete_post( $request_id, true ) ) { |
|
1263 $count ++; |
|
1264 } |
|
1265 } |
|
1266 |
|
1267 add_settings_error( |
|
1268 'bulk_action', |
|
1269 'bulk_action', |
|
1270 /* translators: %d: number of requests */ |
|
1271 sprintf( _n( 'Deleted %d request', 'Deleted %d requests', $count ), $count ), |
|
1272 'updated' |
|
1273 ); |
|
1274 break; |
|
1275 case 'resend': |
|
1276 foreach ( $request_ids as $request_id ) { |
|
1277 $resend = _wp_privacy_resend_request( $request_id ); |
|
1278 |
|
1279 if ( $resend && ! is_wp_error( $resend ) ) { |
|
1280 $count++; |
|
1281 } |
|
1282 } |
|
1283 |
|
1284 add_settings_error( |
|
1285 'bulk_action', |
|
1286 'bulk_action', |
|
1287 /* translators: %d: number of requests */ |
|
1288 sprintf( _n( 'Re-sent %d request', 'Re-sent %d requests', $count ), $count ), |
|
1289 'updated' |
|
1290 ); |
|
1291 break; |
|
1292 } |
|
1293 } |
|
1294 |
|
1295 /** |
|
1296 * Prepare items to output. |
|
1297 * |
|
1298 * @since 4.9.6 |
|
1299 * @since 5.1.0 Added support for column sorting. |
|
1300 */ |
|
1301 public function prepare_items() { |
|
1302 global $wpdb; |
|
1303 |
|
1304 $this->items = array(); |
|
1305 $posts_per_page = $this->get_items_per_page( $this->request_type . '_requests_per_page' ); |
|
1306 $args = array( |
|
1307 'post_type' => $this->post_type, |
|
1308 'post_name__in' => array( $this->request_type ), |
|
1309 'posts_per_page' => $posts_per_page, |
|
1310 'offset' => isset( $_REQUEST['paged'] ) ? max( 0, absint( $_REQUEST['paged'] ) - 1 ) * $posts_per_page : 0, |
|
1311 'post_status' => 'any', |
|
1312 's' => isset( $_REQUEST['s'] ) ? sanitize_text_field( $_REQUEST['s'] ) : '', |
|
1313 ); |
|
1314 |
|
1315 $orderby_mapping = array( |
|
1316 'requester' => 'post_title', |
|
1317 'requested' => 'post_date', |
|
1318 ); |
|
1319 |
|
1320 if ( isset( $_REQUEST['orderby'] ) && isset( $orderby_mapping[ $_REQUEST['orderby'] ] ) ) { |
|
1321 $args['orderby'] = $orderby_mapping[ $_REQUEST['orderby'] ]; |
|
1322 } |
|
1323 |
|
1324 if ( isset( $_REQUEST['order'] ) && in_array( strtoupper( $_REQUEST['order'] ), array( 'ASC', 'DESC' ), true ) ) { |
|
1325 $args['order'] = strtoupper( $_REQUEST['order'] ); |
|
1326 } |
|
1327 |
|
1328 if ( ! empty( $_REQUEST['filter-status'] ) ) { |
|
1329 $filter_status = isset( $_REQUEST['filter-status'] ) ? sanitize_text_field( $_REQUEST['filter-status'] ) : ''; |
|
1330 $args['post_status'] = $filter_status; |
|
1331 } |
|
1332 |
|
1333 $requests_query = new WP_Query( $args ); |
|
1334 $requests = $requests_query->posts; |
|
1335 |
|
1336 foreach ( $requests as $request ) { |
|
1337 $this->items[] = wp_get_user_request_data( $request->ID ); |
|
1338 } |
|
1339 |
|
1340 $this->items = array_filter( $this->items ); |
|
1341 |
|
1342 $this->set_pagination_args( |
|
1343 array( |
|
1344 'total_items' => $requests_query->found_posts, |
|
1345 'per_page' => $posts_per_page, |
|
1346 ) |
|
1347 ); |
|
1348 } |
|
1349 |
|
1350 /** |
|
1351 * Checkbox column. |
|
1352 * |
|
1353 * @since 4.9.6 |
|
1354 * |
|
1355 * @param WP_User_Request $item Item being shown. |
|
1356 * @return string Checkbox column markup. |
|
1357 */ |
|
1358 public function column_cb( $item ) { |
|
1359 return sprintf( '<input type="checkbox" name="request_id[]" value="%1$s" /><span class="spinner"></span>', esc_attr( $item->ID ) ); |
|
1360 } |
|
1361 |
|
1362 /** |
|
1363 * Status column. |
|
1364 * |
|
1365 * @since 4.9.6 |
|
1366 * |
|
1367 * @param WP_User_Request $item Item being shown. |
|
1368 * @return string Status column markup. |
|
1369 */ |
|
1370 public function column_status( $item ) { |
|
1371 $status = get_post_status( $item->ID ); |
|
1372 $status_object = get_post_status_object( $status ); |
|
1373 |
|
1374 if ( ! $status_object || empty( $status_object->label ) ) { |
|
1375 return '-'; |
|
1376 } |
|
1377 |
|
1378 $timestamp = false; |
|
1379 |
|
1380 switch ( $status ) { |
|
1381 case 'request-confirmed': |
|
1382 $timestamp = $item->confirmed_timestamp; |
|
1383 break; |
|
1384 case 'request-completed': |
|
1385 $timestamp = $item->completed_timestamp; |
|
1386 break; |
|
1387 } |
|
1388 |
|
1389 echo '<span class="status-label status-' . esc_attr( $status ) . '">'; |
|
1390 echo esc_html( $status_object->label ); |
|
1391 |
|
1392 if ( $timestamp ) { |
|
1393 echo ' (' . $this->get_timestamp_as_date( $timestamp ) . ')'; |
|
1394 } |
|
1395 |
|
1396 echo '</span>'; |
|
1397 } |
|
1398 |
|
1399 /** |
|
1400 * Convert timestamp for display. |
|
1401 * |
|
1402 * @since 4.9.6 |
|
1403 * |
|
1404 * @param int $timestamp Event timestamp. |
|
1405 * @return string Human readable date. |
|
1406 */ |
|
1407 protected function get_timestamp_as_date( $timestamp ) { |
|
1408 if ( empty( $timestamp ) ) { |
|
1409 return ''; |
|
1410 } |
|
1411 |
|
1412 $time_diff = time() - $timestamp; |
|
1413 |
|
1414 if ( $time_diff >= 0 && $time_diff < DAY_IN_SECONDS ) { |
|
1415 /* translators: human readable timestamp */ |
|
1416 return sprintf( __( '%s ago' ), human_time_diff( $timestamp ) ); |
|
1417 } |
|
1418 |
|
1419 return date_i18n( get_option( 'date_format' ), $timestamp ); |
|
1420 } |
|
1421 |
|
1422 /** |
|
1423 * Default column handler. |
|
1424 * |
|
1425 * @since 4.9.6 |
|
1426 * |
|
1427 * @param WP_User_Request $item Item being shown. |
|
1428 * @param string $column_name Name of column being shown. |
|
1429 * @return string Default column output. |
|
1430 */ |
|
1431 public function column_default( $item, $column_name ) { |
|
1432 $cell_value = $item->$column_name; |
|
1433 |
|
1434 if ( in_array( $column_name, array( 'created_timestamp' ), true ) ) { |
|
1435 return $this->get_timestamp_as_date( $cell_value ); |
|
1436 } |
|
1437 |
|
1438 return $cell_value; |
|
1439 } |
|
1440 |
|
1441 /** |
|
1442 * Actions column. Overridden by children. |
|
1443 * |
|
1444 * @since 4.9.6 |
|
1445 * |
|
1446 * @param WP_User_Request $item Item being shown. |
|
1447 * @return string Email column markup. |
|
1448 */ |
|
1449 public function column_email( $item ) { |
|
1450 return sprintf( '<a href="%1$s">%2$s</a> %3$s', esc_url( 'mailto:' . $item->email ), $item->email, $this->row_actions( array() ) ); |
|
1451 } |
|
1452 |
|
1453 /** |
|
1454 * Next steps column. Overridden by children. |
|
1455 * |
|
1456 * @since 4.9.6 |
|
1457 * |
|
1458 * @param WP_User_Request $item Item being shown. |
|
1459 */ |
|
1460 public function column_next_steps( $item ) {} |
|
1461 |
|
1462 /** |
|
1463 * Generates content for a single row of the table, |
|
1464 * |
|
1465 * @since 4.9.6 |
|
1466 * |
|
1467 * @param WP_User_Request $item The current item. |
|
1468 */ |
|
1469 public function single_row( $item ) { |
|
1470 $status = $item->status; |
|
1471 |
|
1472 echo '<tr id="request-' . esc_attr( $item->ID ) . '" class="status-' . esc_attr( $status ) . '">'; |
|
1473 $this->single_row_columns( $item ); |
|
1474 echo '</tr>'; |
|
1475 } |
|
1476 |
|
1477 /** |
|
1478 * Embed scripts used to perform actions. Overridden by children. |
|
1479 * |
|
1480 * @since 4.9.6 |
|
1481 */ |
|
1482 public function embed_scripts() {} |
|
1483 } |
|
1484 |
|
1485 /** |
|
1486 * WP_Privacy_Data_Export_Requests_Table class. |
|
1487 * |
|
1488 * @since 4.9.6 |
|
1489 */ |
|
1490 class WP_Privacy_Data_Export_Requests_Table extends WP_Privacy_Requests_Table { |
|
1491 /** |
|
1492 * Action name for the requests this table will work with. |
|
1493 * |
|
1494 * @since 4.9.6 |
|
1495 * |
|
1496 * @var string $request_type Name of action. |
|
1497 */ |
|
1498 protected $request_type = 'export_personal_data'; |
|
1499 |
|
1500 /** |
|
1501 * Post type for the requests. |
|
1502 * |
|
1503 * @since 4.9.6 |
|
1504 * |
|
1505 * @var string $post_type The post type. |
|
1506 */ |
|
1507 protected $post_type = 'user_request'; |
|
1508 |
|
1509 /** |
|
1510 * Actions column. |
|
1511 * |
|
1512 * @since 4.9.6 |
|
1513 * |
|
1514 * @param WP_User_Request $item Item being shown. |
|
1515 * @return string Email column markup. |
|
1516 */ |
|
1517 public function column_email( $item ) { |
|
1518 /** This filter is documented in wp-admin/includes/ajax-actions.php */ |
|
1519 $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() ); |
|
1520 $exporters_count = count( $exporters ); |
|
1521 $request_id = $item->ID; |
|
1522 $nonce = wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id ); |
|
1523 |
|
1524 $download_data_markup = '<div class="export-personal-data" ' . |
|
1525 'data-exporters-count="' . esc_attr( $exporters_count ) . '" ' . |
|
1526 'data-request-id="' . esc_attr( $request_id ) . '" ' . |
|
1527 'data-nonce="' . esc_attr( $nonce ) . |
|
1528 '">'; |
|
1529 |
|
1530 $download_data_markup .= '<span class="export-personal-data-idle"><button type="button" class="button-link export-personal-data-handle">' . __( 'Download Personal Data' ) . '</button></span>' . |
|
1531 '<span style="display:none" class="export-personal-data-processing" >' . __( 'Downloading Data...' ) . '</span>' . |
|
1532 '<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>' . |
|
1533 '<span style="display:none" class="export-personal-data-failed">' . __( 'Download failed.' ) . ' <button type="button" class="button-link">' . __( 'Retry' ) . '</button></span>'; |
|
1534 |
|
1535 $download_data_markup .= '</div>'; |
|
1536 |
|
1537 $row_actions = array( |
|
1538 'download-data' => $download_data_markup, |
|
1539 ); |
|
1540 |
|
1541 return sprintf( '<a href="%1$s">%2$s</a> %3$s', esc_url( 'mailto:' . $item->email ), $item->email, $this->row_actions( $row_actions ) ); |
|
1542 } |
|
1543 |
|
1544 /** |
|
1545 * Displays the next steps column. |
|
1546 * |
|
1547 * @since 4.9.6 |
|
1548 * |
|
1549 * @param WP_User_Request $item Item being shown. |
|
1550 */ |
|
1551 public function column_next_steps( $item ) { |
|
1552 $status = $item->status; |
|
1553 |
|
1554 switch ( $status ) { |
|
1555 case 'request-pending': |
|
1556 esc_html_e( 'Waiting for confirmation' ); |
|
1557 break; |
|
1558 case 'request-confirmed': |
|
1559 /** This filter is documented in wp-admin/includes/ajax-actions.php */ |
|
1560 $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() ); |
|
1561 $exporters_count = count( $exporters ); |
|
1562 $request_id = $item->ID; |
|
1563 $nonce = wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id ); |
|
1564 |
|
1565 echo '<div class="export-personal-data" ' . |
|
1566 'data-send-as-email="1" ' . |
|
1567 'data-exporters-count="' . esc_attr( $exporters_count ) . '" ' . |
|
1568 'data-request-id="' . esc_attr( $request_id ) . '" ' . |
|
1569 'data-nonce="' . esc_attr( $nonce ) . |
|
1570 '">'; |
|
1571 |
|
1572 ?> |
|
1573 <span class="export-personal-data-idle"><button type="button" class="button export-personal-data-handle"><?php _e( 'Send Export Link' ); ?></button></span> |
|
1574 <span style="display:none" class="export-personal-data-processing button updating-message" ><?php _e( 'Sending Email...' ); ?></span> |
|
1575 <span style="display:none" class="export-personal-data-success success-message" ><?php _e( 'Email sent.' ); ?></span> |
|
1576 <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> |
|
1577 <?php |
|
1578 |
|
1579 echo '</div>'; |
|
1580 break; |
|
1581 case 'request-failed': |
|
1582 submit_button( __( 'Retry' ), 'secondary', 'privacy_action_email_retry[' . $item->ID . ']', false ); |
|
1583 break; |
|
1584 case 'request-completed': |
|
1585 echo '<a href="' . esc_url( |
|
1586 wp_nonce_url( |
|
1587 add_query_arg( |
|
1588 array( |
|
1589 'action' => 'delete', |
|
1590 'request_id' => array( $item->ID ), |
|
1591 ), |
|
1592 admin_url( 'tools.php?page=export_personal_data' ) |
|
1593 ), |
|
1594 'bulk-privacy_requests' |
|
1595 ) |
|
1596 ) . '" class="button">' . esc_html__( 'Remove request' ) . '</a>'; |
|
1597 break; |
|
1598 } |
|
1599 } |
|
1600 } |
|
1601 |
|
1602 /** |
|
1603 * WP_Privacy_Data_Removal_Requests_Table class. |
|
1604 * |
|
1605 * @since 4.9.6 |
|
1606 */ |
|
1607 class WP_Privacy_Data_Removal_Requests_Table extends WP_Privacy_Requests_Table { |
|
1608 /** |
|
1609 * Action name for the requests this table will work with. |
|
1610 * |
|
1611 * @since 4.9.6 |
|
1612 * |
|
1613 * @var string $request_type Name of action. |
|
1614 */ |
|
1615 protected $request_type = 'remove_personal_data'; |
|
1616 |
|
1617 /** |
|
1618 * Post type for the requests. |
|
1619 * |
|
1620 * @since 4.9.6 |
|
1621 * |
|
1622 * @var string $post_type The post type. |
|
1623 */ |
|
1624 protected $post_type = 'user_request'; |
|
1625 |
|
1626 /** |
|
1627 * Actions column. |
|
1628 * |
|
1629 * @since 4.9.6 |
|
1630 * |
|
1631 * @param WP_User_Request $item Item being shown. |
|
1632 * @return string Email column markup. |
|
1633 */ |
|
1634 public function column_email( $item ) { |
|
1635 $row_actions = array(); |
|
1636 |
|
1637 // Allow the administrator to "force remove" the personal data even if confirmation has not yet been received. |
|
1638 $status = $item->status; |
|
1639 if ( 'request-confirmed' !== $status ) { |
|
1640 /** This filter is documented in wp-admin/includes/ajax-actions.php */ |
|
1641 $erasers = apply_filters( 'wp_privacy_personal_data_erasers', array() ); |
|
1642 $erasers_count = count( $erasers ); |
|
1643 $request_id = $item->ID; |
|
1644 $nonce = wp_create_nonce( 'wp-privacy-erase-personal-data-' . $request_id ); |
|
1645 |
|
1646 $remove_data_markup = '<div class="remove-personal-data force-remove-personal-data" ' . |
|
1647 'data-erasers-count="' . esc_attr( $erasers_count ) . '" ' . |
|
1648 'data-request-id="' . esc_attr( $request_id ) . '" ' . |
|
1649 'data-nonce="' . esc_attr( $nonce ) . |
|
1650 '">'; |
|
1651 |
|
1652 $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>' . |
|
1653 '<span style="display:none" class="remove-personal-data-processing" >' . __( 'Erasing Data...' ) . '</span>' . |
|
1654 '<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>'; |
|
1655 |
|
1656 $remove_data_markup .= '</div>'; |
|
1657 |
|
1658 $row_actions = array( |
|
1659 'remove-data' => $remove_data_markup, |
|
1660 ); |
|
1661 } |
|
1662 |
|
1663 return sprintf( '<a href="%1$s">%2$s</a> %3$s', esc_url( 'mailto:' . $item->email ), $item->email, $this->row_actions( $row_actions ) ); |
|
1664 } |
|
1665 |
|
1666 /** |
|
1667 * Next steps column. |
|
1668 * |
|
1669 * @since 4.9.6 |
|
1670 * |
|
1671 * @param WP_User_Request $item Item being shown. |
|
1672 */ |
|
1673 public function column_next_steps( $item ) { |
|
1674 $status = $item->status; |
|
1675 |
|
1676 switch ( $status ) { |
|
1677 case 'request-pending': |
|
1678 esc_html_e( 'Waiting for confirmation' ); |
|
1679 break; |
|
1680 case 'request-confirmed': |
|
1681 /** This filter is documented in wp-admin/includes/ajax-actions.php */ |
|
1682 $erasers = apply_filters( 'wp_privacy_personal_data_erasers', array() ); |
|
1683 $erasers_count = count( $erasers ); |
|
1684 $request_id = $item->ID; |
|
1685 $nonce = wp_create_nonce( 'wp-privacy-erase-personal-data-' . $request_id ); |
|
1686 |
|
1687 echo '<div class="remove-personal-data" ' . |
|
1688 'data-force-erase="1" ' . |
|
1689 'data-erasers-count="' . esc_attr( $erasers_count ) . '" ' . |
|
1690 'data-request-id="' . esc_attr( $request_id ) . '" ' . |
|
1691 'data-nonce="' . esc_attr( $nonce ) . |
|
1692 '">'; |
|
1693 |
|
1694 ?> |
|
1695 <span class="remove-personal-data-idle"><button type="button" class="button remove-personal-data-handle"><?php _e( 'Erase Personal Data' ); ?></button></span> |
|
1696 <span style="display:none" class="remove-personal-data-processing button updating-message" ><?php _e( 'Erasing Data...' ); ?></span> |
|
1697 <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> |
|
1698 <?php |
|
1699 |
|
1700 echo '</div>'; |
|
1701 |
|
1702 break; |
|
1703 case 'request-failed': |
|
1704 submit_button( __( 'Retry' ), 'secondary', 'privacy_action_email_retry[' . $item->ID . ']', false ); |
|
1705 break; |
|
1706 case 'request-completed': |
|
1707 echo '<a href="' . esc_url( |
|
1708 wp_nonce_url( |
|
1709 add_query_arg( |
|
1710 array( |
|
1711 'action' => 'delete', |
|
1712 'request_id' => array( $item->ID ), |
|
1713 ), |
|
1714 admin_url( 'tools.php?page=remove_personal_data' ) |
|
1715 ), |
|
1716 'bulk-privacy_requests' |
|
1717 ) |
|
1718 ) . '" class="button">' . esc_html__( 'Remove request' ) . '</a>'; |
|
1719 break; |
|
1720 } |
|
1721 } |
|
1722 |
|
1723 } |
|