Ticket #43895: 43895.2.diff
File 43895.2.diff, 188.8 KB (added by , 6 years ago) |
---|
-
Gruntfile.js
264 264 [ WORKING_DIR + 'wp-admin/js/tags-suggest.js' ]: [ './src/js/_enqueues/admin/tags-suggest.js' ], 265 265 [ WORKING_DIR + 'wp-admin/js/tags.js' ]: [ './src/js/_enqueues/admin/tags.js' ], 266 266 [ WORKING_DIR + 'wp-admin/js/site-health.js' ]: [ './src/js/_enqueues/admin/site-health.js' ], 267 [ WORKING_DIR + 'wp-admin/js/privacy-tools.js' ]: [ './src/js/_enqueues/admin/privacy-tools.js' ], 267 268 [ WORKING_DIR + 'wp-admin/js/theme-plugin-editor.js' ]: [ './src/js/_enqueues/wp/theme-plugin-editor.js' ], 268 269 [ WORKING_DIR + 'wp-admin/js/theme.js' ]: [ './src/js/_enqueues/wp/theme.js' ], 269 270 [ WORKING_DIR + 'wp-admin/js/updates.js' ]: [ './src/js/_enqueues/wp/updates.js' ], -
src/js/_enqueues/admin/privacy-tools.js
1 /** 2 * Interactions used by the User Privacy tools in WordPress. 3 * 4 * @output wp-admin/js/privacy-tools.js 5 */ 6 7 // Privacy request action handling 8 jQuery( document ).ready( function( $ ) { 9 var strings = window.privacyToolsL10n || {}; 10 11 function setActionState( $action, state ) { 12 $action.children().hide(); 13 $action.children( '.' + state ).show(); 14 } 15 16 function clearResultsAfterRow( $requestRow ) { 17 $requestRow.removeClass( 'has-request-results' ); 18 19 if ( $requestRow.next().hasClass( 'request-results' ) ) { 20 $requestRow.next().remove(); 21 } 22 } 23 24 function appendResultsAfterRow( $requestRow, classes, summaryMessage, additionalMessages ) { 25 var itemList = '', 26 resultRowClasses = 'request-results'; 27 28 clearResultsAfterRow( $requestRow ); 29 30 if ( additionalMessages.length ) { 31 $.each( additionalMessages, function( index, value ) { 32 itemList = itemList + '<li>' + value + '</li>'; 33 }); 34 itemList = '<ul>' + itemList + '</ul>'; 35 } 36 37 $requestRow.addClass( 'has-request-results' ); 38 39 if ( $requestRow.hasClass( 'status-request-confirmed' ) ) { 40 resultRowClasses = resultRowClasses + ' status-request-confirmed'; 41 } 42 43 if ( $requestRow.hasClass( 'status-request-failed' ) ) { 44 resultRowClasses = resultRowClasses + ' status-request-failed'; 45 } 46 47 $requestRow.after( function() { 48 return '<tr class="' + resultRowClasses + '"><th colspan="5">' + 49 '<div class="notice inline notice-alt ' + classes + '">' + 50 '<p>' + summaryMessage + '</p>' + 51 itemList + 52 '</div>' + 53 '</td>' + 54 '</tr>'; 55 }); 56 } 57 58 $( '.export-personal-data-handle' ).click( function( event ) { 59 var $this = $( this ), 60 $action = $this.parents( '.export-personal-data' ), 61 $requestRow = $this.parents( 'tr' ), 62 requestID = $action.data( 'request-id' ), 63 nonce = $action.data( 'nonce' ), 64 exportersCount = $action.data( 'exporters-count' ), 65 sendAsEmail = $action.data( 'send-as-email' ) ? true : false; 66 67 event.preventDefault(); 68 event.stopPropagation(); 69 70 $action.blur(); 71 clearResultsAfterRow( $requestRow ); 72 73 function onExportDoneSuccess( zipUrl ) { 74 setActionState( $action, 'export-personal-data-success' ); 75 if ( 'undefined' !== typeof zipUrl ) { 76 window.location = zipUrl; 77 } else if ( ! sendAsEmail ) { 78 onExportFailure( strings.noExportFile ); 79 } 80 } 81 82 function onExportFailure( errorMessage ) { 83 setActionState( $action, 'export-personal-data-failed' ); 84 if ( errorMessage ) { 85 appendResultsAfterRow( $requestRow, 'notice-error', strings.exportError, [ errorMessage ] ); 86 } 87 } 88 89 function doNextExport( exporterIndex, pageIndex ) { 90 $.ajax( 91 { 92 url: window.ajaxurl, 93 data: { 94 action: 'wp-privacy-export-personal-data', 95 exporter: exporterIndex, 96 id: requestID, 97 page: pageIndex, 98 security: nonce, 99 sendAsEmail: sendAsEmail 100 }, 101 method: 'post' 102 } 103 ).done( function( response ) { 104 var responseData = response.data; 105 106 if ( ! response.success ) { 107 108 // e.g. invalid request ID 109 onExportFailure( response.data ); 110 return; 111 } 112 113 if ( ! responseData.done ) { 114 setTimeout( doNextExport( exporterIndex, pageIndex + 1 ) ); 115 } else { 116 if ( exporterIndex < exportersCount ) { 117 setTimeout( doNextExport( exporterIndex + 1, 1 ) ); 118 } else { 119 onExportDoneSuccess( responseData.url ); 120 } 121 } 122 }).fail( function( jqxhr, textStatus, error ) { 123 124 // e.g. Nonce failure 125 onExportFailure( error ); 126 }); 127 } 128 129 // And now, let's begin 130 setActionState( $action, 'export-personal-data-processing' ); 131 doNextExport( 1, 1 ); 132 }); 133 134 $( '.remove-personal-data-handle' ).click( function( event ) { 135 var $this = $( this ), 136 $action = $this.parents( '.remove-personal-data' ), 137 $requestRow = $this.parents( 'tr' ), 138 requestID = $action.data( 'request-id' ), 139 nonce = $action.data( 'nonce' ), 140 erasersCount = $action.data( 'erasers-count' ), 141 hasRemoved = false, 142 hasRetained = false, 143 messages = []; 144 145 event.stopPropagation(); 146 147 $action.blur(); 148 clearResultsAfterRow( $requestRow ); 149 150 function onErasureDoneSuccess() { 151 var summaryMessage = strings.noDataFound; 152 var classes = 'notice-success'; 153 154 setActionState( $action, 'remove-personal-data-idle' ); 155 156 if ( false === hasRemoved ) { 157 if ( false === hasRetained ) { 158 summaryMessage = strings.noDataFound; 159 } else { 160 summaryMessage = strings.noneRemoved; 161 classes = 'notice-warning'; 162 } 163 } else { 164 if ( false === hasRetained ) { 165 summaryMessage = strings.foundAndRemoved; 166 } else { 167 summaryMessage = strings.someNotRemoved; 168 classes = 'notice-warning'; 169 } 170 } 171 appendResultsAfterRow( $requestRow, 'notice-success', summaryMessage, messages ); 172 } 173 174 function onErasureFailure() { 175 setActionState( $action, 'remove-personal-data-failed' ); 176 appendResultsAfterRow( $requestRow, 'notice-error', strings.removalError, [] ); 177 } 178 179 function doNextErasure( eraserIndex, pageIndex ) { 180 $.ajax({ 181 url: window.ajaxurl, 182 data: { 183 action: 'wp-privacy-erase-personal-data', 184 eraser: eraserIndex, 185 id: requestID, 186 page: pageIndex, 187 security: nonce 188 }, 189 method: 'post' 190 }).done( function( response ) { 191 var responseData = response.data; 192 193 if ( ! response.success ) { 194 onErasureFailure(); 195 return; 196 } 197 if ( responseData.items_removed ) { 198 hasRemoved = hasRemoved || responseData.items_removed; 199 } 200 if ( responseData.items_retained ) { 201 hasRetained = hasRetained || responseData.items_retained; 202 } 203 if ( responseData.messages ) { 204 messages = messages.concat( responseData.messages ); 205 } 206 if ( ! responseData.done ) { 207 setTimeout( doNextErasure( eraserIndex, pageIndex + 1 ) ); 208 } else { 209 if ( eraserIndex < erasersCount ) { 210 setTimeout( doNextErasure( eraserIndex + 1, 1 ) ); 211 } else { 212 onErasureDoneSuccess(); 213 } 214 } 215 }).fail( function() { 216 onErasureFailure(); 217 }); 218 } 219 220 // And now, let's begin 221 setActionState( $action, 'remove-personal-data-processing' ); 222 223 doNextErasure( 1, 1 ); 224 }); 225 226 // Privacy policy page, copy button. 227 $( document ).on( 'click', function( event ) { 228 var $target = $( event.target ); 229 var $parent, $container, range; 230 231 if ( $target.is( 'button.privacy-text-copy' ) ) { 232 $parent = $target.parent().parent(); 233 $container = $parent.find( 'div.wp-suggested-text' ); 234 235 if ( ! $container.length ) { 236 $container = $parent.find( 'div.policy-text' ); 237 } 238 239 if ( $container.length ) { 240 try { 241 window.getSelection().removeAllRanges(); 242 range = document.createRange(); 243 $container.addClass( 'hide-privacy-policy-tutorial' ); 244 245 range.selectNodeContents( $container[0] ); 246 window.getSelection().addRange( range ); 247 document.execCommand( 'copy' ); 248 249 $container.removeClass( 'hide-privacy-policy-tutorial' ); 250 window.getSelection().removeAllRanges(); 251 } catch ( er ) {} 252 } 253 } 254 }); 255 }); 256 -
src/js/_enqueues/admin/xfn.js
Property changes on: src/js/_enqueues/admin/privacy-tools.js ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property
21 21 $( '#link_rel' ).val( ( isMe ) ? 'me' : inputs.substr( 0,inputs.length - 1 ) ); 22 22 }); 23 23 }); 24 25 // Privacy request action handling26 jQuery( document ).ready( function( $ ) {27 var strings = window.privacyToolsL10n || {};28 29 function setActionState( $action, state ) {30 $action.children().hide();31 $action.children( '.' + state ).show();32 }33 34 function clearResultsAfterRow( $requestRow ) {35 $requestRow.removeClass( 'has-request-results' );36 37 if ( $requestRow.next().hasClass( 'request-results' ) ) {38 $requestRow.next().remove();39 }40 }41 42 function appendResultsAfterRow( $requestRow, classes, summaryMessage, additionalMessages ) {43 var itemList = '',44 resultRowClasses = 'request-results';45 46 clearResultsAfterRow( $requestRow );47 48 if ( additionalMessages.length ) {49 $.each( additionalMessages, function( index, value ) {50 itemList = itemList + '<li>' + value + '</li>';51 });52 itemList = '<ul>' + itemList + '</ul>';53 }54 55 $requestRow.addClass( 'has-request-results' );56 57 if ( $requestRow.hasClass( 'status-request-confirmed' ) ) {58 resultRowClasses = resultRowClasses + ' status-request-confirmed';59 }60 61 if ( $requestRow.hasClass( 'status-request-failed' ) ) {62 resultRowClasses = resultRowClasses + ' status-request-failed';63 }64 65 $requestRow.after( function() {66 return '<tr class="' + resultRowClasses + '"><th colspan="5">' +67 '<div class="notice inline notice-alt ' + classes + '">' +68 '<p>' + summaryMessage + '</p>' +69 itemList +70 '</div>' +71 '</td>' +72 '</tr>';73 });74 }75 76 $( '.export-personal-data-handle' ).click( function( event ) {77 78 var $this = $( this ),79 $action = $this.parents( '.export-personal-data' ),80 $requestRow = $this.parents( 'tr' ),81 requestID = $action.data( 'request-id' ),82 nonce = $action.data( 'nonce' ),83 exportersCount = $action.data( 'exporters-count' ),84 sendAsEmail = $action.data( 'send-as-email' ) ? true : false;85 86 event.preventDefault();87 event.stopPropagation();88 89 $action.blur();90 clearResultsAfterRow( $requestRow );91 92 function onExportDoneSuccess( zipUrl ) {93 setActionState( $action, 'export-personal-data-success' );94 if ( 'undefined' !== typeof zipUrl ) {95 window.location = zipUrl;96 } else if ( ! sendAsEmail ) {97 onExportFailure( strings.noExportFile );98 }99 }100 101 function onExportFailure( errorMessage ) {102 setActionState( $action, 'export-personal-data-failed' );103 if ( errorMessage ) {104 appendResultsAfterRow( $requestRow, 'notice-error', strings.exportError, [ errorMessage ] );105 }106 }107 108 function doNextExport( exporterIndex, pageIndex ) {109 $.ajax(110 {111 url: window.ajaxurl,112 data: {113 action: 'wp-privacy-export-personal-data',114 exporter: exporterIndex,115 id: requestID,116 page: pageIndex,117 security: nonce,118 sendAsEmail: sendAsEmail119 },120 method: 'post'121 }122 ).done( function( response ) {123 var responseData = response.data;124 125 if ( ! response.success ) {126 127 // e.g. invalid request ID128 onExportFailure( response.data );129 return;130 }131 132 if ( ! responseData.done ) {133 setTimeout( doNextExport( exporterIndex, pageIndex + 1 ) );134 } else {135 if ( exporterIndex < exportersCount ) {136 setTimeout( doNextExport( exporterIndex + 1, 1 ) );137 } else {138 onExportDoneSuccess( responseData.url );139 }140 }141 }).fail( function( jqxhr, textStatus, error ) {142 143 // e.g. Nonce failure144 onExportFailure( error );145 });146 }147 148 // And now, let's begin149 setActionState( $action, 'export-personal-data-processing' );150 doNextExport( 1, 1 );151 });152 153 $( '.remove-personal-data-handle' ).click( function( event ) {154 155 var $this = $( this ),156 $action = $this.parents( '.remove-personal-data' ),157 $requestRow = $this.parents( 'tr' ),158 requestID = $action.data( 'request-id' ),159 nonce = $action.data( 'nonce' ),160 erasersCount = $action.data( 'erasers-count' ),161 hasRemoved = false,162 hasRetained = false,163 messages = [];164 165 event.stopPropagation();166 167 $action.blur();168 clearResultsAfterRow( $requestRow );169 170 function onErasureDoneSuccess() {171 var summaryMessage = strings.noDataFound;172 var classes = 'notice-success';173 174 setActionState( $action, 'remove-personal-data-idle' );175 176 if ( false === hasRemoved ) {177 if ( false === hasRetained ) {178 summaryMessage = strings.noDataFound;179 } else {180 summaryMessage = strings.noneRemoved;181 classes = 'notice-warning';182 }183 } else {184 if ( false === hasRetained ) {185 summaryMessage = strings.foundAndRemoved;186 } else {187 summaryMessage = strings.someNotRemoved;188 classes = 'notice-warning';189 }190 }191 appendResultsAfterRow( $requestRow, 'notice-success', summaryMessage, messages );192 }193 194 function onErasureFailure() {195 setActionState( $action, 'remove-personal-data-failed' );196 appendResultsAfterRow( $requestRow, 'notice-error', strings.removalError, [] );197 }198 199 function doNextErasure( eraserIndex, pageIndex ) {200 $.ajax({201 url: window.ajaxurl,202 data: {203 action: 'wp-privacy-erase-personal-data',204 eraser: eraserIndex,205 id: requestID,206 page: pageIndex,207 security: nonce208 },209 method: 'post'210 }).done( function( response ) {211 var responseData = response.data;212 213 if ( ! response.success ) {214 onErasureFailure();215 return;216 }217 if ( responseData.items_removed ) {218 hasRemoved = hasRemoved || responseData.items_removed;219 }220 if ( responseData.items_retained ) {221 hasRetained = hasRetained || responseData.items_retained;222 }223 if ( responseData.messages ) {224 messages = messages.concat( responseData.messages );225 }226 if ( ! responseData.done ) {227 setTimeout( doNextErasure( eraserIndex, pageIndex + 1 ) );228 } else {229 if ( eraserIndex < erasersCount ) {230 setTimeout( doNextErasure( eraserIndex + 1, 1 ) );231 } else {232 onErasureDoneSuccess();233 }234 }235 }).fail( function() {236 onErasureFailure();237 });238 }239 240 // And now, let's begin241 setActionState( $action, 'remove-personal-data-processing' );242 243 doNextErasure( 1, 1 );244 });245 });246 247 ( function( $ ) {248 249 // Privacy policy page, copy button.250 $( document ).on( 'click', function( event ) {251 var $target = $( event.target );252 var $parent, $container, range;253 254 if ( $target.is( 'button.privacy-text-copy' ) ) {255 $parent = $target.parent().parent();256 $container = $parent.find( 'div.wp-suggested-text' );257 258 if ( ! $container.length ) {259 $container = $parent.find( 'div.policy-text' );260 }261 262 if ( $container.length ) {263 try {264 window.getSelection().removeAllRanges();265 range = document.createRange();266 $container.addClass( 'hide-privacy-policy-tutorial' );267 268 range.selectNodeContents( $container[0] );269 window.getSelection().addRange( range );270 document.execCommand( 'copy' );271 272 $container.removeClass( 'hide-privacy-policy-tutorial' );273 window.getSelection().removeAllRanges();274 } catch ( er ) {}275 }276 }277 });278 279 } ( jQuery ) ); -
src/wp-admin/erase-personal-data.php
1 <?php 2 /** 3 * Privacy tools, Erase Personal Data screen. 4 * 5 * @package WordPress 6 * @subpackage Administration 7 */ 8 9 /** WordPress Administration Bootstrap */ 10 require_once( dirname( __FILE__ ) . '/admin.php' ); 11 12 if ( ! current_user_can( 'erase_others_personal_data' ) || ! current_user_can( 'delete_users' ) ) { 13 wp_die( __( 'Sorry, you are not allowed to erase data on this site.' ) ); 14 } 15 16 if ( ! class_exists( 'WP_Privacy_Data_Removal_Requests_Table' ) ) { 17 require_once( ABSPATH . 'wp-admin/includes/class-wp-privacy-data-removal-requests-table.php' ); 18 } 19 20 // Handle list table actions. 21 _wp_personal_data_handle_actions(); 22 23 // Cleans up failed and expired requests before displaying the list table. 24 _wp_personal_data_cleanup_requests(); 25 26 wp_enqueue_script( 'privacy-tools' ); 27 28 $args = array( 29 'plural' => 'privacy_requests', 30 'singular' => 'privacy_request', 31 ); 32 33 $requests_table = _get_list_table( 'WP_Privacy_Data_Removal_Requests_List_Table', $args ); 34 35 $requests_table->screen->set_screen_reader_content( 36 array( 37 'heading_views' => __( 'Filter erase personal data list' ), 38 'heading_pagination' => __( 'Erase personal data list navigation' ), 39 'heading_list' => __( 'Erase personal data list' ), 40 ) 41 ); 42 43 $requests_table->process_bulk_action(); 44 $requests_table->prepare_items(); 45 46 require_once( ABSPATH . 'wp-admin/admin-header.php' ); 47 ?> 48 49 <div class="wrap nosubsub"> 50 <h1><?php esc_html_e( 'Erase Personal Data' ); ?></h1> 51 <hr class="wp-header-end" /> 52 53 <?php settings_errors(); ?> 54 55 <form action="<?php echo esc_url( admin_url( 'erase-personal-data.php' ) ); ?>" method="post" class="wp-privacy-request-form"> 56 <h2><?php esc_html_e( 'Add Data Erasure Request' ); ?></h2> 57 <p><?php esc_html_e( 'An email will be sent to the user at this email address asking them to verify the request.' ); ?></p> 58 59 <div class="wp-privacy-request-form-field"> 60 <label for="username_or_email_for_privacy_request"><?php esc_html_e( 'Username or email address' ); ?></label> 61 <input type="text" required class="regular-text" id="username_or_email_for_privacy_request" name="username_or_email_for_privacy_request" /> 62 <?php submit_button( __( 'Send Request' ), 'secondary', 'submit', false ); ?> 63 </div> 64 <?php wp_nonce_field( 'personal-data-request' ); ?> 65 <input type="hidden" name="action" value="add_remove_personal_data_request" /> 66 <input type="hidden" name="type_of_action" value="remove_personal_data" /> 67 </form> 68 <hr /> 69 70 <?php $requests_table->views(); ?> 71 72 <form class="search-form wp-clearfix"> 73 <?php $requests_table->search_box( __( 'Search Requests' ), 'requests' ); ?> 74 <input type="hidden" name="page" value="remove_personal_data" /> 75 <input type="hidden" name="filter-status" value="<?php echo isset( $_REQUEST['filter-status'] ) ? esc_attr( sanitize_text_field( $_REQUEST['filter-status'] ) ) : ''; ?>" /> 76 <input type="hidden" name="orderby" value="<?php echo isset( $_REQUEST['orderby'] ) ? esc_attr( sanitize_text_field( $_REQUEST['orderby'] ) ) : ''; ?>" /> 77 <input type="hidden" name="order" value="<?php echo isset( $_REQUEST['order'] ) ? esc_attr( sanitize_text_field( $_REQUEST['order'] ) ) : ''; ?>" /> 78 </form> 79 80 <form method="post"> 81 <?php 82 $requests_table->display(); 83 $requests_table->embed_scripts(); 84 ?> 85 </form> 86 </div> 87 88 <?php 89 include( ABSPATH . 'wp-admin/admin-footer.php' ); -
src/wp-admin/export-personal-data.php
Property changes on: src/wp-admin/erase-personal-data.php ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property
1 <?php 2 /** 3 * Privacy tools, Export Personal Data screen. 4 * 5 * @package WordPress 6 * @subpackage Administration 7 */ 8 9 /** WordPress Administration Bootstrap */ 10 require_once( dirname( __FILE__ ) . '/admin.php' ); 11 12 if ( ! current_user_can( 'export_others_personal_data' ) ) { 13 wp_die( __( 'Sorry, you are not allowed to export personal data on this site.' ) ); 14 } 15 16 if ( ! class_exists( 'WP_Privacy_Data_Export_Requests_Table' ) ) { 17 require_once( ABSPATH . 'wp-admin/includes/class-wp-privacy-data-export-requests-table.php' ); 18 } 19 20 // Handle list table actions. 21 _wp_personal_data_handle_actions(); 22 23 // Cleans up failed and expired requests before displaying the list table. 24 _wp_personal_data_cleanup_requests(); 25 26 wp_enqueue_script( 'privacy-tools' ); 27 28 $args = array( 29 'plural' => 'privacy_requests', 30 'singular' => 'privacy_request', 31 ); 32 33 $requests_table = _get_list_table( 'WP_Privacy_Data_Export_Requests_List_Table', $args ); 34 35 $requests_table->screen->set_screen_reader_content( 36 array( 37 'heading_views' => __( 'Filter export personal data list' ), 38 'heading_pagination' => __( 'Export personal data list navigation' ), 39 'heading_list' => __( 'Export personal data list' ), 40 ) 41 ); 42 43 $requests_table->process_bulk_action(); 44 $requests_table->prepare_items(); 45 46 require_once( ABSPATH . 'wp-admin/admin-header.php' ); 47 ?> 48 49 <div class="wrap nosubsub"> 50 <h1><?php esc_html_e( 'Export Personal Data' ); ?></h1> 51 <hr class="wp-header-end" /> 52 53 <?php settings_errors(); ?> 54 55 <form action="<?php echo esc_url( admin_url( 'export-personal-data.php' ) ); ?>" method="post" class="wp-privacy-request-form"> 56 <h2><?php esc_html_e( 'Add Data Export Request' ); ?></h2> 57 <p><?php esc_html_e( 'An email will be sent to the user at this email address asking them to verify the request.' ); ?></p> 58 59 <div class="wp-privacy-request-form-field"> 60 <label for="username_or_email_for_privacy_request"><?php esc_html_e( 'Username or email address' ); ?></label> 61 <input type="text" required class="regular-text" id="username_or_email_for_privacy_request" name="username_or_email_for_privacy_request" /> 62 <?php submit_button( __( 'Send Request' ), 'secondary', 'submit', false ); ?> 63 </div> 64 <?php wp_nonce_field( 'personal-data-request' ); ?> 65 <input type="hidden" name="action" value="add_export_personal_data_request" /> 66 <input type="hidden" name="type_of_action" value="export_personal_data" /> 67 </form> 68 <hr /> 69 70 <?php $requests_table->views(); ?> 71 72 <form class="search-form wp-clearfix"> 73 <?php $requests_table->search_box( __( 'Search Requests' ), 'requests' ); ?> 74 <input type="hidden" name="page" value="export_personal_data" /> 75 <input type="hidden" name="filter-status" value="<?php echo isset( $_REQUEST['filter-status'] ) ? esc_attr( sanitize_text_field( $_REQUEST['filter-status'] ) ) : ''; ?>" /> 76 <input type="hidden" name="orderby" value="<?php echo isset( $_REQUEST['orderby'] ) ? esc_attr( sanitize_text_field( $_REQUEST['orderby'] ) ) : ''; ?>" /> 77 <input type="hidden" name="order" value="<?php echo isset( $_REQUEST['order'] ) ? esc_attr( sanitize_text_field( $_REQUEST['order'] ) ) : ''; ?>" /> 78 </form> 79 80 <form method="post"> 81 <?php 82 $requests_table->display(); 83 $requests_table->embed_scripts(); 84 ?> 85 </form> 86 </div> 87 88 <?php 89 include( ABSPATH . 'wp-admin/admin-footer.php' ); -
src/wp-admin/includes/admin-filters.php
Property changes on: src/wp-admin/export-personal-data.php ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property
46 46 add_action( 'admin_head', '_ipad_meta' ); 47 47 48 48 // Privacy tools 49 add_action( 'admin_menu', '_wp_privacy_hook_requests_page' );50 49 add_action( 'load-tools_page_export_personal_data', '_wp_privacy_requests_screen_options' ); 51 50 add_action( 'load-tools_page_remove_personal_data', '_wp_privacy_requests_screen_options' ); 52 51 -
src/wp-admin/includes/admin.php
40 40 /** WordPress Misc Administration API */ 41 41 require_once( ABSPATH . 'wp-admin/includes/misc.php' ); 42 42 43 /** WordPress Misc Administration API */ 44 require_once( ABSPATH . 'wp-admin/includes/class-wp-privacy-policy-content.php' ); 45 43 46 /** WordPress Options Administration API */ 44 47 require_once( ABSPATH . 'wp-admin/includes/options.php' ); 45 48 … … 67 70 /** WordPress Theme Administration API */ 68 71 require_once( ABSPATH . 'wp-admin/includes/theme.php' ); 69 72 73 /** WordPress Privacy Functions */ 74 require_once( ABSPATH . 'wp-admin/includes/privacy-tools.php' ); 75 76 /** WordPress Privacy List Table classes. */ 77 // Previously in wp-admin/includes/user.php. Need to be loaded for backwards compatibility. 78 require_once( ABSPATH . 'wp-admin/includes/class-wp-privacy-requests-table.php' ); 79 require_once( ABSPATH . 'wp-admin/includes/class-wp-privacy-data-export-requests-list-table.php' ); 80 require_once( ABSPATH . 'wp-admin/includes/class-wp-privacy-data-removal-requests-list-table.php' ); 81 70 82 /** WordPress User Administration API */ 71 83 require_once( ABSPATH . 'wp-admin/includes/user.php' ); 72 84 -
src/wp-admin/includes/class-wp-privacy-data-export-requests-list-table.php
1 <?php 2 /** 3 * List Table API: WP_Privacy_Data_Export_Requests_List_Table class 4 * 5 * @package WordPress 6 * @subpackage Administration 7 * @since 4.9.6 8 */ 9 10 if ( ! class_exists( 'WP_Privacy_Requests_Table' ) ) { 11 require_once( ABSPATH . 'wp-admin/includes/class-wp-privacy-requests-table.php' ); 12 } 13 14 /** 15 * WP_Privacy_Data_Export_Requests_Table class. 16 * 17 * @since 4.9.6 18 */ 19 class WP_Privacy_Data_Export_Requests_List_Table extends WP_Privacy_Requests_Table { 20 /** 21 * Action name for the requests this table will work with. 22 * 23 * @since 4.9.6 24 * 25 * @var string $request_type Name of action. 26 */ 27 protected $request_type = 'export_personal_data'; 28 29 /** 30 * Post type for the requests. 31 * 32 * @since 4.9.6 33 * 34 * @var string $post_type The post type. 35 */ 36 protected $post_type = 'user_request'; 37 38 /** 39 * Actions column. 40 * 41 * @since 4.9.6 42 * 43 * @param WP_User_Request $item Item being shown. 44 * @return string Email column markup. 45 */ 46 public function column_email( $item ) { 47 /** This filter is documented in wp-admin/includes/ajax-actions.php */ 48 $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() ); 49 $exporters_count = count( $exporters ); 50 $request_id = $item->ID; 51 $nonce = wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id ); 52 53 $download_data_markup = '<div class="export-personal-data" ' . 54 'data-exporters-count="' . esc_attr( $exporters_count ) . '" ' . 55 'data-request-id="' . esc_attr( $request_id ) . '" ' . 56 'data-nonce="' . esc_attr( $nonce ) . 57 '">'; 58 59 $download_data_markup .= '<span class="export-personal-data-idle"><button type="button" class="button-link export-personal-data-handle">' . __( 'Download Personal Data' ) . '</button></span>' . 60 '<span style="display:none" class="export-personal-data-processing" >' . __( 'Downloading Data...' ) . '</span>' . 61 '<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>' . 62 '<span style="display:none" class="export-personal-data-failed">' . __( 'Download failed.' ) . ' <button type="button" class="button-link">' . __( 'Retry' ) . '</button></span>'; 63 64 $download_data_markup .= '</div>'; 65 66 $row_actions = array( 67 'download-data' => $download_data_markup, 68 ); 69 70 return sprintf( '<a href="%1$s">%2$s</a> %3$s', esc_url( 'mailto:' . $item->email ), $item->email, $this->row_actions( $row_actions ) ); 71 } 72 73 /** 74 * Displays the next steps column. 75 * 76 * @since 4.9.6 77 * 78 * @param WP_User_Request $item Item being shown. 79 */ 80 public function column_next_steps( $item ) { 81 $status = $item->status; 82 83 switch ( $status ) { 84 case 'request-pending': 85 esc_html_e( 'Waiting for confirmation' ); 86 break; 87 case 'request-confirmed': 88 /** This filter is documented in wp-admin/includes/ajax-actions.php */ 89 $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() ); 90 $exporters_count = count( $exporters ); 91 $request_id = $item->ID; 92 $nonce = wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id ); 93 94 echo '<div class="export-personal-data" ' . 95 'data-send-as-email="1" ' . 96 'data-exporters-count="' . esc_attr( $exporters_count ) . '" ' . 97 'data-request-id="' . esc_attr( $request_id ) . '" ' . 98 'data-nonce="' . esc_attr( $nonce ) . 99 '">'; 100 101 ?> 102 <span class="export-personal-data-idle"><button type="button" class="button export-personal-data-handle"><?php _e( 'Send Export Link' ); ?></button></span> 103 <span style="display:none" class="export-personal-data-processing button updating-message" ><?php _e( 'Sending Email...' ); ?></span> 104 <span style="display:none" class="export-personal-data-success success-message" ><?php _e( 'Email sent.' ); ?></span> 105 <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> 106 <?php 107 108 echo '</div>'; 109 break; 110 case 'request-failed': 111 submit_button( __( 'Retry' ), 'secondary', 'privacy_action_email_retry[' . $item->ID . ']', false ); 112 break; 113 case 'request-completed': 114 echo '<a href="' . esc_url( 115 wp_nonce_url( 116 add_query_arg( 117 array( 118 'action' => 'delete', 119 'request_id' => array( $item->ID ), 120 ), 121 admin_url( 'export-personal-data.php' ) 122 ), 123 'bulk-privacy_requests' 124 ) 125 ) . '" class="button">' . esc_html__( 'Remove request' ) . '</a>'; 126 break; 127 } 128 } 129 } -
src/wp-admin/includes/class-wp-privacy-data-removal-requests-list-table.php
Property changes on: src/wp-admin/includes/class-wp-privacy-data-export-requests-list-table.php ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property
1 <?php 2 /** 3 * List Table API: WP_Privacy_Data_Removal_Requests_List_Table class 4 * 5 * @package WordPress 6 * @subpackage Administration 7 * @since 4.9.6 8 */ 9 10 if ( ! class_exists( 'WP_Privacy_Requests_Table' ) ) { 11 require_once( ABSPATH . 'wp-admin/includes/class-wp-privacy-requests-table.php' ); 12 } 13 14 /** 15 * WP_Privacy_Data_Removal_Requests_Table class. 16 * 17 * @since 4.9.6 18 */ 19 class WP_Privacy_Data_Removal_Requests_List_Table extends WP_Privacy_Requests_Table { 20 /** 21 * Action name for the requests this table will work with. 22 * 23 * @since 4.9.6 24 * 25 * @var string $request_type Name of action. 26 */ 27 protected $request_type = 'remove_personal_data'; 28 29 /** 30 * Post type for the requests. 31 * 32 * @since 4.9.6 33 * 34 * @var string $post_type The post type. 35 */ 36 protected $post_type = 'user_request'; 37 38 /** 39 * Actions column. 40 * 41 * @since 4.9.6 42 * 43 * @param WP_User_Request $item Item being shown. 44 * @return string Email column markup. 45 */ 46 public function column_email( $item ) { 47 $row_actions = array(); 48 49 // Allow the administrator to "force remove" the personal data even if confirmation has not yet been received. 50 $status = $item->status; 51 if ( 'request-confirmed' !== $status ) { 52 /** This filter is documented in wp-admin/includes/ajax-actions.php */ 53 $erasers = apply_filters( 'wp_privacy_personal_data_erasers', array() ); 54 $erasers_count = count( $erasers ); 55 $request_id = $item->ID; 56 $nonce = wp_create_nonce( 'wp-privacy-erase-personal-data-' . $request_id ); 57 58 $remove_data_markup = '<div class="remove-personal-data force-remove-personal-data" ' . 59 'data-erasers-count="' . esc_attr( $erasers_count ) . '" ' . 60 'data-request-id="' . esc_attr( $request_id ) . '" ' . 61 'data-nonce="' . esc_attr( $nonce ) . 62 '">'; 63 64 $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>' . 65 '<span style="display:none" class="remove-personal-data-processing" >' . __( 'Erasing Data...' ) . '</span>' . 66 '<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>'; 67 68 $remove_data_markup .= '</div>'; 69 70 $row_actions = array( 71 'remove-data' => $remove_data_markup, 72 ); 73 } 74 75 return sprintf( '<a href="%1$s">%2$s</a> %3$s', esc_url( 'mailto:' . $item->email ), $item->email, $this->row_actions( $row_actions ) ); 76 } 77 78 /** 79 * Next steps column. 80 * 81 * @since 4.9.6 82 * 83 * @param WP_User_Request $item Item being shown. 84 */ 85 public function column_next_steps( $item ) { 86 $status = $item->status; 87 88 switch ( $status ) { 89 case 'request-pending': 90 esc_html_e( 'Waiting for confirmation' ); 91 break; 92 case 'request-confirmed': 93 /** This filter is documented in wp-admin/includes/ajax-actions.php */ 94 $erasers = apply_filters( 'wp_privacy_personal_data_erasers', array() ); 95 $erasers_count = count( $erasers ); 96 $request_id = $item->ID; 97 $nonce = wp_create_nonce( 'wp-privacy-erase-personal-data-' . $request_id ); 98 99 echo '<div class="remove-personal-data" ' . 100 'data-force-erase="1" ' . 101 'data-erasers-count="' . esc_attr( $erasers_count ) . '" ' . 102 'data-request-id="' . esc_attr( $request_id ) . '" ' . 103 'data-nonce="' . esc_attr( $nonce ) . 104 '">'; 105 106 ?> 107 <span class="remove-personal-data-idle"><button type="button" class="button remove-personal-data-handle"><?php _e( 'Erase Personal Data' ); ?></button></span> 108 <span style="display:none" class="remove-personal-data-processing button updating-message" ><?php _e( 'Erasing Data...' ); ?></span> 109 <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> 110 <?php 111 112 echo '</div>'; 113 114 break; 115 case 'request-failed': 116 submit_button( __( 'Retry' ), 'secondary', 'privacy_action_email_retry[' . $item->ID . ']', false ); 117 break; 118 case 'request-completed': 119 echo '<a href="' . esc_url( 120 wp_nonce_url( 121 add_query_arg( 122 array( 123 'action' => 'delete', 124 'request_id' => array( $item->ID ), 125 ), 126 admin_url( 'erase-personal-data.php' ) 127 ), 128 'bulk-privacy_requests' 129 ) 130 ) . '" class="button">' . esc_html__( 'Remove request' ) . '</a>'; 131 break; 132 } 133 } 134 135 } -
src/wp-admin/includes/class-wp-privacy-policy-content.php
Property changes on: src/wp-admin/includes/class-wp-privacy-data-removal-requests-list-table.php ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property
1 <?php 2 /** 3 * WP_Privacy_Policy_Content class. 4 * 5 * @package WordPress 6 * @subpackage Administration 7 * @since 4.9.6 8 */ 9 10 final class WP_Privacy_Policy_Content { 11 12 private static $policy_content = array(); 13 14 /** 15 * Constructor 16 * 17 * @since 4.9.6 18 */ 19 private function __construct() {} 20 21 /** 22 * Add content to the postbox shown when editing the privacy policy. 23 * 24 * Plugins and themes should suggest text for inclusion in the site's privacy policy. 25 * The suggested text should contain information about any functionality that affects user privacy, 26 * and will be shown in the Suggested Privacy Policy Content postbox. 27 * 28 * Intended for use from `wp_add_privacy_policy_content()`. 29 * 30 * @since 4.9.6 31 * 32 * @param string $plugin_name The name of the plugin or theme that is suggesting content for the site's privacy policy. 33 * @param string $policy_text The suggested content for inclusion in the policy. 34 */ 35 public static function add( $plugin_name, $policy_text ) { 36 if ( empty( $plugin_name ) || empty( $policy_text ) ) { 37 return; 38 } 39 40 $data = array( 41 'plugin_name' => $plugin_name, 42 'policy_text' => $policy_text, 43 ); 44 45 if ( ! in_array( $data, self::$policy_content, true ) ) { 46 self::$policy_content[] = $data; 47 } 48 } 49 50 /** 51 * Quick check if any privacy info has changed. 52 * 53 * @since 4.9.6 54 */ 55 public static function text_change_check() { 56 57 $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' ); 58 59 // The site doesn't have a privacy policy. 60 if ( empty( $policy_page_id ) ) { 61 return false; 62 } 63 64 if ( ! current_user_can( 'edit_post', $policy_page_id ) ) { 65 return false; 66 } 67 68 $old = (array) get_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content' ); 69 70 // Updates are not relevant if the user has not reviewed any suggestions yet. 71 if ( empty( $old ) ) { 72 return false; 73 } 74 75 $cached = get_option( '_wp_suggested_policy_text_has_changed' ); 76 77 /* 78 * When this function is called before `admin_init`, `self::$policy_content` 79 * has not been populated yet, so use the cached result from the last 80 * execution instead. 81 */ 82 if ( ! did_action( 'admin_init' ) ) { 83 return 'changed' === $cached; 84 } 85 86 $new = self::$policy_content; 87 88 // Remove the extra values added to the meta. 89 foreach ( $old as $key => $data ) { 90 if ( ! empty( $data['removed'] ) ) { 91 unset( $old[ $key ] ); 92 continue; 93 } 94 95 $old[ $key ] = array( 96 'plugin_name' => $data['plugin_name'], 97 'policy_text' => $data['policy_text'], 98 ); 99 } 100 101 // Normalize the order of texts, to facilitate comparison. 102 sort( $old ); 103 sort( $new ); 104 105 // The == operator (equal, not identical) was used intentionally. 106 // See http://php.net/manual/en/language.operators.array.php 107 if ( $new != $old ) { 108 // A plugin was activated or deactivated, or some policy text has changed. 109 // Show a notice on the relevant screens to inform the admin. 110 add_action( 'admin_notices', array( 'WP_Privacy_Policy_Content', 'policy_text_changed_notice' ) ); 111 $state = 'changed'; 112 } else { 113 $state = 'not-changed'; 114 } 115 116 // Cache the result for use before `admin_init` (see above). 117 if ( $cached !== $state ) { 118 update_option( '_wp_suggested_policy_text_has_changed', $state ); 119 } 120 121 return 'changed' === $state; 122 } 123 124 /** 125 * Output a warning when some privacy info has changed. 126 * 127 * @since 4.9.6 128 */ 129 public static function policy_text_changed_notice() { 130 global $post; 131 132 $screen = get_current_screen()->id; 133 134 if ( 'privacy' !== $screen ) { 135 return; 136 } 137 138 ?> 139 <div class="policy-text-updated notice notice-warning is-dismissible"> 140 <p> 141 <?php 142 printf( 143 /* translators: %s: Privacy Policy Guide URL */ 144 __( 'The suggested privacy policy text has changed. Please <a href="%s">review the guide</a> and update your privacy policy.' ), 145 esc_url( admin_url( 'privacy-policy-guide.php' ) ) 146 ); 147 ?> 148 </p> 149 </div> 150 <?php 151 } 152 153 /** 154 * Update the cached policy info when the policy page is updated. 155 * 156 * @since 4.9.6 157 * @access private 158 */ 159 public static function _policy_page_updated( $post_id ) { 160 $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' ); 161 162 if ( ! $policy_page_id || $policy_page_id !== (int) $post_id ) { 163 return; 164 } 165 166 // Remove updated|removed status. 167 $old = (array) get_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content' ); 168 $done = array(); 169 $update_cache = false; 170 171 foreach ( $old as $old_key => $old_data ) { 172 if ( ! empty( $old_data['removed'] ) ) { 173 // Remove the old policy text. 174 $update_cache = true; 175 continue; 176 } 177 178 if ( ! empty( $old_data['updated'] ) ) { 179 // 'updated' is now 'added'. 180 $done[] = array( 181 'plugin_name' => $old_data['plugin_name'], 182 'policy_text' => $old_data['policy_text'], 183 'added' => $old_data['updated'], 184 ); 185 $update_cache = true; 186 } else { 187 $done[] = $old_data; 188 } 189 } 190 191 if ( $update_cache ) { 192 delete_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content' ); 193 // Update the cache. 194 foreach ( $done as $data ) { 195 add_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content', $data ); 196 } 197 } 198 } 199 200 /** 201 * Check for updated, added or removed privacy policy information from plugins. 202 * 203 * Caches the current info in post_meta of the policy page. 204 * 205 * @since 4.9.6 206 * 207 * @return array The privacy policy text/informtion added by core and plugins. 208 */ 209 public static function get_suggested_policy_text() { 210 $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' ); 211 $checked = array(); 212 $time = time(); 213 $update_cache = false; 214 $new = self::$policy_content; 215 $old = array(); 216 217 if ( $policy_page_id ) { 218 $old = (array) get_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content' ); 219 } 220 221 // Check for no-changes and updates. 222 foreach ( $new as $new_key => $new_data ) { 223 foreach ( $old as $old_key => $old_data ) { 224 $found = false; 225 226 if ( $new_data['policy_text'] === $old_data['policy_text'] ) { 227 // Use the new plugin name in case it was changed, translated, etc. 228 if ( $old_data['plugin_name'] !== $new_data['plugin_name'] ) { 229 $old_data['plugin_name'] = $new_data['plugin_name']; 230 $update_cache = true; 231 } 232 233 // A plugin was re-activated. 234 if ( ! empty( $old_data['removed'] ) ) { 235 unset( $old_data['removed'] ); 236 $old_data['added'] = $time; 237 $update_cache = true; 238 } 239 240 $checked[] = $old_data; 241 $found = true; 242 } elseif ( $new_data['plugin_name'] === $old_data['plugin_name'] ) { 243 // The info for the policy was updated. 244 $checked[] = array( 245 'plugin_name' => $new_data['plugin_name'], 246 'policy_text' => $new_data['policy_text'], 247 'updated' => $time, 248 ); 249 $found = $update_cache = true; 250 } 251 252 if ( $found ) { 253 unset( $new[ $new_key ], $old[ $old_key ] ); 254 continue 2; 255 } 256 } 257 } 258 259 if ( ! empty( $new ) ) { 260 // A plugin was activated. 261 foreach ( $new as $new_data ) { 262 if ( ! empty( $new_data['plugin_name'] ) && ! empty( $new_data['policy_text'] ) ) { 263 $new_data['added'] = $time; 264 $checked[] = $new_data; 265 } 266 } 267 $update_cache = true; 268 } 269 270 if ( ! empty( $old ) ) { 271 // A plugin was deactivated. 272 foreach ( $old as $old_data ) { 273 if ( ! empty( $old_data['plugin_name'] ) && ! empty( $old_data['policy_text'] ) ) { 274 $data = array( 275 'plugin_name' => $old_data['plugin_name'], 276 'policy_text' => $old_data['policy_text'], 277 'removed' => $time, 278 ); 279 280 $checked[] = $data; 281 } 282 } 283 $update_cache = true; 284 } 285 286 if ( $update_cache && $policy_page_id ) { 287 delete_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content' ); 288 // Update the cache. 289 foreach ( $checked as $data ) { 290 add_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content', $data ); 291 } 292 } 293 294 return $checked; 295 } 296 297 /** 298 * Add a notice with a link to the guide when editing the privacy policy page. 299 * 300 * @since 4.9.6 301 * @since 5.0.0 The $post parameter is now optional. 302 * 303 * @param WP_Post|null $post The currently edited post. Default null. 304 */ 305 public static function notice( $post = null ) { 306 if ( is_null( $post ) ) { 307 global $post; 308 } else { 309 $post = get_post( $post ); 310 } 311 312 if ( ! ( $post instanceof WP_Post ) ) { 313 return; 314 } 315 316 if ( ! current_user_can( 'manage_privacy_options' ) ) { 317 return; 318 } 319 320 $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' ); 321 322 if ( ! $policy_page_id || $policy_page_id !== $post->ID ) { 323 return; 324 } 325 326 $message = __( 'Need help putting together your new Privacy Policy page? Check out our guide for recommendations on what content to include, along with policies suggested by your plugins and theme.' ); 327 $url = esc_url( admin_url( 'privacy-policy-guide.php' ) ); 328 $label = __( 'View Privacy Policy Guide.' ); 329 330 if ( get_current_screen()->is_block_editor() ) { 331 wp_enqueue_script( 'wp-notices' ); 332 $action = array( 333 'url' => $url, 334 'label' => $label, 335 ); 336 wp_add_inline_script( 337 'wp-notices', 338 sprintf( 339 'wp.data.dispatch( "core/notices" ).createWarningNotice( "%s", { actions: [ %s ], isDismissible: false } )', 340 $message, 341 wp_json_encode( $action ) 342 ), 343 'after' 344 ); 345 } else { 346 ?> 347 <div class="notice notice-warning inline wp-pp-notice"> 348 <p> 349 <?php 350 echo $message; 351 printf( 352 ' <a href="%s" target="_blank">%s <span class="screen-reader-text">%s</span></a>', 353 $url, 354 $label, 355 /* translators: accessibility text */ 356 __( '(opens in a new tab)' ) 357 ); 358 ?> 359 </p> 360 </div> 361 <?php 362 } 363 } 364 365 /** 366 * Output the privacy policy guide together with content from the theme and plugins. 367 * 368 * @since 4.9.6 369 */ 370 public static function privacy_policy_guide() { 371 372 $content_array = self::get_suggested_policy_text(); 373 374 $content = ''; 375 $toc = array( '<li><a href="#wp-privacy-policy-guide-introduction">' . __( 'Introduction' ) . '</a></li>' ); 376 $date_format = __( 'F j, Y' ); 377 $copy = __( 'Copy this section to clipboard' ); 378 $return_to_top = '<a href="#" class="return-to-top">' . __( '↑ Return to Top' ) . '</a>'; 379 380 foreach ( $content_array as $section ) { 381 $class = $meta = $removed = ''; 382 383 if ( ! empty( $section['removed'] ) ) { 384 $class = ' text-removed'; 385 $date = date_i18n( $date_format, $section['removed'] ); 386 $meta = sprintf( __( 'Removed %s.' ), $date ); 387 388 $removed = __( 'You deactivated this plugin on %s and may no longer need this policy.' ); 389 $removed = '<div class="error inline"><p>' . sprintf( $removed, $date ) . '</p></div>'; 390 } elseif ( ! empty( $section['updated'] ) ) { 391 $class = ' text-updated'; 392 $date = date_i18n( $date_format, $section['updated'] ); 393 $meta = sprintf( __( 'Updated %s.' ), $date ); 394 } 395 396 if ( $meta ) { 397 $meta = '<br><span class="privacy-text-meta">' . $meta . '</span>'; 398 } 399 400 $plugin_name = esc_html( $section['plugin_name'] ); 401 $toc_id = 'wp-privacy-policy-guide-' . sanitize_title( $plugin_name ); 402 $toc[] = sprintf( '<li><a href="#%1$s">%2$s</a>' . $meta . '</li>', $toc_id, $plugin_name ); 403 404 $content .= '<div class="privacy-text-section' . $class . '">'; 405 $content .= '<a id="' . $toc_id . '"> </a>'; 406 /* translators: %s: plugin name */ 407 $content .= '<h2>' . sprintf( __( 'Source: %s' ), $plugin_name ) . '</h2>'; 408 $content .= $removed; 409 410 $content .= '<div class="policy-text">' . $section['policy_text'] . '</div>'; 411 $content .= $return_to_top; 412 413 if ( empty( $section['removed'] ) ) { 414 $content .= '<div class="privacy-text-actions">'; 415 $content .= '<button type="button" class="privacy-text-copy button">'; 416 $content .= $copy; 417 $content .= '<span class="screen-reader-text">' . sprintf( __( 'Copy suggested policy text from %s.' ), $plugin_name ) . '</span>'; 418 $content .= '</button>'; 419 $content .= '</div>'; 420 } 421 422 $content .= "</div>\n"; // End of .privacy-text-section. 423 } 424 425 if ( count( $toc ) > 2 ) { 426 ?> 427 <div class="privacy-text-box-toc"> 428 <p><?php _e( 'Table of Contents' ); ?></p> 429 <ol> 430 <?php echo implode( "\n", $toc ); ?> 431 </ol> 432 </div> 433 <?php 434 } 435 436 ?> 437 <div class="privacy-text-box"> 438 <div class="privacy-text-box-head"> 439 <a id="wp-privacy-policy-guide-introduction"> </a> 440 <h2><?php _e( 'Introduction' ); ?></h2> 441 <p><?php _e( 'Hello,' ); ?></p> 442 <p><?php _e( 'This text template will help you to create your web site’s privacy policy.' ); ?></p> 443 <p><?php _e( 'We have suggested the sections you will need. Under each section heading you will find a short summary of what information you should provide, which will help you to get started. Some sections include suggested policy content, others will have to be completed with information from your theme and plugins.' ); ?></p> 444 <p><?php _e( 'Please edit your privacy policy content, making sure to delete the summaries, and adding any information from your theme and plugins. Once you publish your policy page, remember to add it to your navigation menu.' ); ?></p> 445 <p><?php _e( 'It is your responsibility to write a comprehensive privacy policy, to make sure it reflects all national and international legal requirements on privacy, and to keep your policy current and accurate.' ); ?></p> 446 </div> 447 448 <div class="privacy-text-box-body"> 449 <?php echo $content; ?> 450 </div> 451 </div> 452 <?php 453 } 454 455 /** 456 * Return the default suggested privacy policy content. 457 * 458 * @since 4.9.6 459 * @since 5.0.0 Added the `$blocks` parameter. 460 * 461 * @param bool $description Whether to include the descriptions under the section headings. Default false. 462 * @param bool $blocks Whether to format the content for the block editor. Default true. 463 * @return string The default policy content. 464 */ 465 public static function get_default_content( $description = false, $blocks = true ) { 466 $suggested_text = $description ? '<strong class="privacy-policy-tutorial">' . __( 'Suggested text:' ) . ' </strong>' : ''; 467 $content = ''; 468 $strings = array(); 469 470 // Start of the suggested privacy policy text. 471 if ( $description ) { 472 $strings[] = '<div class="wp-suggested-text">'; 473 } 474 475 /* translators: default privacy policy heading. */ 476 $strings[] = '<h2>' . __( 'Who we are' ) . '</h2>'; 477 478 if ( $description ) { 479 /* translators: privacy policy tutorial. */ 480 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this section you should note your site URL, as well as the name of the company, organization, or individual behind it, and some accurate contact information.' ) . '</p>'; 481 /* translators: privacy policy tutorial. */ 482 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'The amount of information you may be required to show will vary depending on your local or national business regulations. You may, for example, be required to display a physical address, a registered address, or your company registration number.' ) . '</p>'; 483 } 484 485 /* translators: default privacy policy text, %s Site URL. */ 486 $strings[] = '<p>' . $suggested_text . sprintf( __( 'Our website address is: %s.' ), get_bloginfo( 'url', 'display' ) ) . '</p>'; 487 488 /* translators: default privacy policy heading. */ 489 $strings[] = '<h2>' . __( 'What personal data we collect and why we collect it' ) . '</h2>'; 490 491 if ( $description ) { 492 /* translators: privacy policy tutorial. */ 493 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this section you should note what personal data you collect from users and site visitors. This may include personal data, such as name, email address, personal account preferences; transactional data, such as purchase information; and technical data, such as information about cookies.' ) . '</p>'; 494 /* translators: privacy policy tutorial. */ 495 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'You should also note any collection and retention of sensitive personal data, such as data concerning health.' ) . '</p>'; 496 /* translators: privacy policy tutorial. */ 497 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In addition to listing what personal data you collect, you need to note why you collect it. These explanations must note either the legal basis for your data collection and retention or the active consent the user has given.' ) . '</p>'; 498 /* translators: privacy policy tutorial. */ 499 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'Personal data is not just created by a user’s interactions with your site. Personal data is also generated from technical processes such as contact forms, comments, cookies, analytics, and third party embeds.' ) . '</p>'; 500 /* translators: privacy policy tutorial. */ 501 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'By default WordPress does not collect any personal data about visitors, and only collects the data shown on the User Profile screen from registered users. However some of your plugins may collect personal data. You should add the relevant information below.' ) . '</p>'; 502 } 503 504 /* translators: default privacy policy heading. */ 505 $strings[] = '<h3>' . __( 'Comments' ) . '</h3>'; 506 507 if ( $description ) { 508 /* translators: privacy policy tutorial. */ 509 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this subsection you should note what information is captured through comments. We have noted the data which WordPress collects by default.' ) . '</p>'; 510 } 511 512 /* translators: default privacy policy text. */ 513 $strings[] = '<p>' . $suggested_text . __( 'When visitors leave comments on the site we collect the data shown in the comments form, and also the visitor’s IP address and browser user agent string to help spam detection.' ) . '</p>'; 514 /* translators: default privacy policy text. */ 515 $strings[] = '<p>' . __( 'An anonymized string created from your email address (also called a hash) may be provided to the Gravatar service to see if you are using it. The Gravatar service privacy policy is available here: https://automattic.com/privacy/. After approval of your comment, your profile picture is visible to the public in the context of your comment.' ) . '</p>'; 516 517 /* translators: default privacy policy heading. */ 518 $strings[] = '<h3>' . __( 'Media' ) . '</h3>'; 519 520 if ( $description ) { 521 /* translators: privacy policy tutorial. */ 522 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this subsection you should note what information may be disclosed by users who can upload media files. All uploaded files are usually publicly accessible.' ) . '</p>'; 523 } 524 525 /* translators: default privacy policy text. */ 526 $strings[] = '<p>' . $suggested_text . __( 'If you upload images to the website, you should avoid uploading images with embedded location data (EXIF GPS) included. Visitors to the website can download and extract any location data from images on the website.' ) . '</p>'; 527 528 /* translators: default privacy policy heading. */ 529 $strings[] = '<h3>' . __( 'Contact forms' ) . '</h3>'; 530 531 if ( $description ) { 532 /* translators: privacy policy tutorial. */ 533 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'By default, WordPress does not include a contact form. If you use a contact form plugin, use this subsection to note what personal data is captured when someone submits a contact form, and how long you keep it. For example, you may note that you keep contact form submissions for a certain period for customer service purposes, but you do not use the information submitted through them for marketing purposes.' ) . '</p>'; 534 } 535 536 /* translators: default privacy policy heading. */ 537 $strings[] = '<h3>' . __( 'Cookies' ) . '</h3>'; 538 539 if ( $description ) { 540 /* translators: privacy policy tutorial. */ 541 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this subsection you should list the cookies your web site uses, including those set by your plugins, social media, and analytics. We have provided the cookies which WordPress installs by default.' ) . '</p>'; 542 } 543 544 /* translators: default privacy policy text. */ 545 $strings[] = '<p>' . $suggested_text . __( 'If you leave a comment on our site you may opt-in to saving your name, email address and website in cookies. These are for your convenience so that you do not have to fill in your details again when you leave another comment. These cookies will last for one year.' ) . '</p>'; 546 /* translators: default privacy policy text. */ 547 $strings[] = '<p>' . __( 'If you visit our login page, we will set a temporary cookie to determine if your browser accepts cookies. This cookie contains no personal data and is discarded when you close your browser.' ) . '</p>'; 548 /* translators: default privacy policy text. */ 549 $strings[] = '<p>' . __( 'When you log in, we will also set up several cookies to save your login information and your screen display choices. Login cookies last for two days, and screen options cookies last for a year. If you select "Remember Me", your login will persist for two weeks. If you log out of your account, the login cookies will be removed.' ) . '</p>'; 550 /* translators: default privacy policy text. */ 551 $strings[] = '<p>' . __( 'If you edit or publish an article, an additional cookie will be saved in your browser. This cookie includes no personal data and simply indicates the post ID of the article you just edited. It expires after 1 day.' ) . '</p>'; 552 553 /* translators: default privacy policy heading. */ 554 $strings[] = '<h3>' . __( 'Embedded content from other websites' ) . '</h3>'; 555 /* translators: default privacy policy text. */ 556 $strings[] = '<p>' . $suggested_text . __( 'Articles on this site may include embedded content (e.g. videos, images, articles, etc.). Embedded content from other websites behaves in the exact same way as if the visitor has visited the other website.' ) . '</p>'; 557 /* translators: default privacy policy text. */ 558 $strings[] = '<p>' . __( 'These websites may collect data about you, use cookies, embed additional third-party tracking, and monitor your interaction with that embedded content, including tracking your interaction with the embedded content if you have an account and are logged in to that website.' ) . '</p>'; 559 560 /* translators: default privacy policy heading. */ 561 $strings[] = '<h3>' . __( 'Analytics' ) . '</h3>'; 562 563 if ( $description ) { 564 /* translators: privacy policy tutorial. */ 565 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this subsection you should note what analytics package you use, how users can opt out of analytics tracking, and a link to your analytics provider’s privacy policy, if any.' ) . '</p>'; 566 /* translators: privacy policy tutorial. */ 567 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'By default WordPress does not collect any analytics data. However, many web hosting accounts collect some anonymous analytics data. You may also have installed a WordPress plugin that provides analytics services. In that case, add information from that plugin here.' ) . '</p>'; 568 } 569 570 /* translators: default privacy policy heading. */ 571 $strings[] = '<h2>' . __( 'Who we share your data with' ) . '</h2>'; 572 573 if ( $description ) { 574 /* translators: privacy policy tutorial. */ 575 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this section you should name and list all third party providers with whom you share site data, including partners, cloud-based services, payment processors, and third party service providers, and note what data you share with them and why. Link to their own privacy policies if possible.' ) . '</p>'; 576 /* translators: privacy policy tutorial. */ 577 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'By default WordPress does not share any personal data with anyone.' ) . '</p>'; 578 } 579 580 /* translators: default privacy policy heading. */ 581 $strings[] = '<h2>' . __( 'How long we retain your data' ) . '</h2>'; 582 583 if ( $description ) { 584 /* translators: privacy policy tutorial. */ 585 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this section you should explain how long you retain personal data collected or processed by the web site. While it is your responsibility to come up with the schedule of how long you keep each dataset for and why you keep it, that information does need to be listed here. For example, you may want to say that you keep contact form entries for six months, analytics records for a year, and customer purchase records for ten years.' ) . '</p>'; 586 } 587 588 /* translators: default privacy policy text. */ 589 $strings[] = '<p>' . $suggested_text . __( 'If you leave a comment, the comment and its metadata are retained indefinitely. This is so we can recognize and approve any follow-up comments automatically instead of holding them in a moderation queue.' ) . '</p>'; 590 /* translators: default privacy policy text. */ 591 $strings[] = '<p>' . __( 'For users that register on our website (if any), we also store the personal information they provide in their user profile. All users can see, edit, or delete their personal information at any time (except they cannot change their username). Website administrators can also see and edit that information.' ) . '</p>'; 592 593 /* translators: default privacy policy heading. */ 594 $strings[] = '<h2>' . __( 'What rights you have over your data' ) . '</h2>'; 595 596 if ( $description ) { 597 /* translators: privacy policy tutorial. */ 598 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this section you should explain what rights your users have over their data and how they can invoke those rights.' ) . '</p>'; 599 } 600 601 /* translators: default privacy policy text. */ 602 $strings[] = '<p>' . $suggested_text . __( 'If you have an account on this site, or have left comments, you can request to receive an exported file of the personal data we hold about you, including any data you have provided to us. You can also request that we erase any personal data we hold about you. This does not include any data we are obliged to keep for administrative, legal, or security purposes.' ) . '</p>'; 603 604 /* translators: default privacy policy heading. */ 605 $strings[] = '<h2>' . __( 'Where we send your data' ) . '</h2>'; 606 607 if ( $description ) { 608 /* translators: privacy policy tutorial. */ 609 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this section you should list all transfers of your site data outside the European Union and describe the means by which that data is safeguarded to European data protection standards. This could include your web hosting, cloud storage, or other third party services.' ) . '</p>'; 610 /* translators: privacy policy tutorial. */ 611 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'European data protection law requires data about European residents which is transferred outside the European Union to be safeguarded to the same standards as if the data was in Europe. So in addition to listing where data goes, you should describe how you ensure that these standards are met either by yourself or by your third party providers, whether that is through an agreement such as Privacy Shield, model clauses in your contracts, or binding corporate rules.' ) . '</p>'; 612 } 613 614 /* translators: default privacy policy text. */ 615 $strings[] = '<p>' . $suggested_text . __( 'Visitor comments may be checked through an automated spam detection service.' ) . '</p>'; 616 617 /* translators: default privacy policy heading. */ 618 $strings[] = '<h2>' . __( 'Your contact information' ) . '</h2>'; 619 620 if ( $description ) { 621 /* translators: privacy policy tutorial. */ 622 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this section you should provide a contact method for privacy-specific concerns. If you are required to have a Data Protection Officer, list their name and full contact details here as well.' ) . '</p>'; 623 } 624 625 /* translators: default privacy policy heading. */ 626 $strings[] = '<h2>' . __( 'Additional information' ) . '</h2>'; 627 628 if ( $description ) { 629 /* translators: privacy policy tutorial. */ 630 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'If you use your site for commercial purposes and you engage in more complex collection or processing of personal data, you should note the following information in your privacy policy in addition to the information we have already discussed.' ) . '</p>'; 631 } 632 633 /* translators: default privacy policy heading. */ 634 $strings[] = '<h3>' . __( 'How we protect your data' ) . '</h3>'; 635 636 if ( $description ) { 637 /* translators: privacy policy tutorial. */ 638 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this section you should explain what measures you have taken to protect your users’ data. This could include technical measures such as encryption; security measures such as two factor authentication; and measures such as staff training in data protection. If you have carried out a Privacy Impact Assessment, you can mention it here too.' ) . '</p>'; 639 } 640 641 /* translators: default privacy policy heading. */ 642 $strings[] = '<h3>' . __( 'What data breach procedures we have in place' ) . '</h3>'; 643 644 if ( $description ) { 645 /* translators: privacy policy tutorial. */ 646 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this section you should explain what procedures you have in place to deal with data breaches, either potential or real, such as internal reporting systems, contact mechanisms, or bug bounties.' ) . '</p>'; 647 } 648 649 /* translators: default privacy policy heading. */ 650 $strings[] = '<h3>' . __( 'What third parties we receive data from' ) . '</h3>'; 651 652 if ( $description ) { 653 /* translators: privacy policy tutorial. */ 654 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'If your web site receives data about users from third parties, including advertisers, this information must be included within the section of your privacy policy dealing with third party data.' ) . '</p>'; 655 } 656 657 /* translators: default privacy policy heading. */ 658 $strings[] = '<h3>' . __( 'What automated decision making and/or profiling we do with user data' ) . '</h3>'; 659 660 if ( $description ) { 661 /* translators: privacy policy tutorial. */ 662 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'If your web site provides a service which includes automated decision making - for example, allowing customers to apply for credit, or aggregating their data into an advertising profile - you must note that this is taking place, and include information about how that information is used, what decisions are made with that aggregated data, and what rights users have over decisions made without human intervention.' ) . '</p>'; 663 } 664 665 /* translators: default privacy policy heading. */ 666 $strings[] = '<h3>' . __( 'Industry regulatory disclosure requirements' ) . '</h3>'; 667 668 if ( $description ) { 669 /* translators: privacy policy tutorial. */ 670 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'If you are a member of a regulated industry, or if you are subject to additional privacy laws, you may be required to disclose that information here.' ) . '</p>'; 671 $strings[] = '</div>'; 672 } 673 674 if ( $blocks ) { 675 foreach ( $strings as $key => $string ) { 676 if ( 0 === strpos( $string, '<p>' ) ) { 677 $strings[ $key ] = '<!-- wp:paragraph -->' . $string . '<!-- /wp:paragraph -->'; 678 } 679 680 if ( 0 === strpos( $string, '<h2>' ) ) { 681 $strings[ $key ] = '<!-- wp:heading -->' . $string . '<!-- /wp:heading -->'; 682 } 683 684 if ( 0 === strpos( $string, '<h3>' ) ) { 685 $strings[ $key ] = '<!-- wp:heading {"level":3} -->' . $string . '<!-- /wp:heading -->'; 686 } 687 } 688 } 689 690 $content = implode( '', $strings ); 691 // End of the suggested privacy policy text. 692 693 /** 694 * Filters the default content suggested for inclusion in a privacy policy. 695 * 696 * @since 4.9.6 697 * @since 5.0.0 Added the `$strings`, `$description`, and `$blocks` parameters. 698 * 699 * @param string $content The default policy content. 700 * @param array $strings An array of privacy policy content strings. 701 * @param bool $description Whether policy descriptions should be included. 702 * @param bool $blocks Whether the content should be formatted for the block editor. 703 */ 704 return apply_filters( 'wp_get_default_privacy_policy_content', $content, $strings, $description, $blocks ); 705 } 706 707 /** 708 * Add the suggested privacy policy text to the policy postbox. 709 * 710 * @since 4.9.6 711 */ 712 public static function add_suggested_content() { 713 $content = self::get_default_content( true, false ); 714 wp_add_privacy_policy_content( __( 'WordPress' ), $content ); 715 } 716 } -
src/wp-admin/includes/class-wp-privacy-requests-table.php
Property changes on: src/wp-admin/includes/class-wp-privacy-policy-content.php ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property
1 <?php 2 /** 3 * List Table API: WP_Privacy_Requests_Table class 4 * 5 * @package WordPress 6 * @subpackage Administration 7 * @since 4.9.6 8 */ 9 10 abstract class WP_Privacy_Requests_Table extends WP_List_Table { 11 12 /** 13 * Action name for the requests this table will work with. Classes 14 * which inherit from WP_Privacy_Requests_Table should define this. 15 * 16 * Example: 'export_personal_data'. 17 * 18 * @since 4.9.6 19 * 20 * @var string $request_type Name of action. 21 */ 22 protected $request_type = 'INVALID'; 23 24 /** 25 * Post type to be used. 26 * 27 * @since 4.9.6 28 * 29 * @var string $post_type The post type. 30 */ 31 protected $post_type = 'INVALID'; 32 33 /** 34 * Get columns to show in the list table. 35 * 36 * @since 4.9.6 37 * 38 * @return array Array of columns. 39 */ 40 public function get_columns() { 41 $columns = array( 42 'cb' => '<input type="checkbox" />', 43 'email' => __( 'Requester' ), 44 'status' => __( 'Status' ), 45 'created_timestamp' => __( 'Requested' ), 46 'next_steps' => __( 'Next Steps' ), 47 ); 48 return $columns; 49 } 50 51 /** 52 * Normalize the admin URL to the current page (by request_type). 53 * 54 * @since 5.3.0 55 * 56 * @return string URL to the current admin page. 57 */ 58 protected function get_admin_url() { 59 $pagenow = str_replace( '_', '-', $this->request_type ); 60 61 if ( 'remove-personal-data' === $pagenow ) { 62 $pagenow = 'erase-personal-data'; 63 } 64 65 return admin_url( $pagenow . '.php' ); 66 } 67 68 /** 69 * Get a list of sortable columns. 70 * 71 * @since 4.9.6 72 * 73 * @return array Default sortable columns. 74 */ 75 protected function get_sortable_columns() { 76 // The initial sorting is by 'Requested' (post_date) and descending. 77 // With initial sorting, the first click on 'Requested' should be ascending. 78 // With 'Requester' sorting active, the next click on 'Requested' should be descending. 79 $desc_first = isset( $_GET['orderby'] ); 80 81 return array( 82 'email' => 'requester', 83 'created_timestamp' => array( 'requested', $desc_first ), 84 ); 85 } 86 87 /** 88 * Default primary column. 89 * 90 * @since 4.9.6 91 * 92 * @return string Default primary column name. 93 */ 94 protected function get_default_primary_column_name() { 95 return 'email'; 96 } 97 98 /** 99 * Count number of requests for each status. 100 * 101 * @since 4.9.6 102 * 103 * @return object Number of posts for each status. 104 */ 105 protected function get_request_counts() { 106 global $wpdb; 107 108 $cache_key = $this->post_type . '-' . $this->request_type; 109 $counts = wp_cache_get( $cache_key, 'counts' ); 110 111 if ( false !== $counts ) { 112 return $counts; 113 } 114 115 $query = " 116 SELECT post_status, COUNT( * ) AS num_posts 117 FROM {$wpdb->posts} 118 WHERE post_type = %s 119 AND post_name = %s 120 GROUP BY post_status"; 121 122 $results = (array) $wpdb->get_results( $wpdb->prepare( $query, $this->post_type, $this->request_type ), ARRAY_A ); 123 $counts = array_fill_keys( get_post_stati(), 0 ); 124 125 foreach ( $results as $row ) { 126 $counts[ $row['post_status'] ] = $row['num_posts']; 127 } 128 129 $counts = (object) $counts; 130 wp_cache_set( $cache_key, $counts, 'counts' ); 131 132 return $counts; 133 } 134 135 /** 136 * Get an associative array ( id => link ) with the list of views available on this table. 137 * 138 * @since 4.9.6 139 * 140 * @return array Associative array of views in the format of $view_name => $view_markup. 141 */ 142 protected function get_views() { 143 $current_status = isset( $_REQUEST['filter-status'] ) ? sanitize_text_field( $_REQUEST['filter-status'] ) : ''; 144 $statuses = _wp_privacy_statuses(); 145 $views = array(); 146 $counts = $this->get_request_counts(); 147 $total_requests = absint( array_sum( (array) $counts ) ); 148 149 // Normalized admin URL 150 $admin_url = $this->get_admin_url(); 151 152 $current_link_attributes = empty( $current_status ) ? ' class="current" aria-current="page"' : ''; 153 $status_label = sprintf( 154 /* translators: %s: all requests count */ 155 _nx( 156 'All <span class="count">(%s)</span>', 157 'All <span class="count">(%s)</span>', 158 $total_requests, 159 'requests' 160 ), 161 number_format_i18n( $total_requests ) 162 ); 163 164 $views['all'] = sprintf( 165 '<a href="%s"%s>%s</a>', 166 esc_url( $admin_url ), 167 $current_link_attributes, 168 $status_label 169 ); 170 171 foreach ( $statuses as $status => $label ) { 172 $post_status = get_post_status_object( $status ); 173 if ( ! $post_status ) { 174 continue; 175 } 176 177 $current_link_attributes = $status === $current_status ? ' class="current" aria-current="page"' : ''; 178 $total_status_requests = absint( $counts->{$status} ); 179 180 $status_label = sprintf( 181 translate_nooped_plural( $post_status->label_count, $total_status_requests ), 182 number_format_i18n( $total_status_requests ) 183 ); 184 185 $status_link = add_query_arg( 'filter-status', $status, $admin_url ); 186 187 $views[ $status ] = sprintf( 188 '<a href="%s"%s>%s</a>', 189 esc_url( $status_link ), 190 $current_link_attributes, 191 $status_label 192 ); 193 } 194 195 return $views; 196 } 197 198 /** 199 * Get bulk actions. 200 * 201 * @since 4.9.6 202 * 203 * @return array List of bulk actions. 204 */ 205 protected function get_bulk_actions() { 206 return array( 207 'delete' => __( 'Remove' ), 208 'resend' => __( 'Resend email' ), 209 ); 210 } 211 212 /** 213 * Process bulk actions. 214 * 215 * @since 4.9.6 216 */ 217 public function process_bulk_action() { 218 $action = $this->current_action(); 219 $request_ids = isset( $_REQUEST['request_id'] ) ? wp_parse_id_list( wp_unslash( $_REQUEST['request_id'] ) ) : array(); 220 221 $count = 0; 222 223 if ( $request_ids ) { 224 check_admin_referer( 'bulk-privacy_requests' ); 225 } 226 227 switch ( $action ) { 228 case 'delete': 229 foreach ( $request_ids as $request_id ) { 230 if ( wp_delete_post( $request_id, true ) ) { 231 $count ++; 232 } 233 } 234 235 add_settings_error( 236 'bulk_action', 237 'bulk_action', 238 /* translators: %d: number of requests */ 239 sprintf( _n( 'Deleted %d request', 'Deleted %d requests', $count ), $count ), 240 'updated' 241 ); 242 break; 243 case 'resend': 244 foreach ( $request_ids as $request_id ) { 245 $resend = _wp_privacy_resend_request( $request_id ); 246 247 if ( $resend && ! is_wp_error( $resend ) ) { 248 $count++; 249 } 250 } 251 252 add_settings_error( 253 'bulk_action', 254 'bulk_action', 255 /* translators: %d: number of requests */ 256 sprintf( _n( 'Re-sent %d request', 'Re-sent %d requests', $count ), $count ), 257 'updated' 258 ); 259 break; 260 } 261 } 262 263 /** 264 * Prepare items to output. 265 * 266 * @since 4.9.6 267 * @since 5.1.0 Added support for column sorting. 268 */ 269 public function prepare_items() { 270 global $wpdb; 271 272 $this->items = array(); 273 $posts_per_page = $this->get_items_per_page( $this->request_type . '_requests_per_page' ); 274 $args = array( 275 'post_type' => $this->post_type, 276 'post_name__in' => array( $this->request_type ), 277 'posts_per_page' => $posts_per_page, 278 'offset' => isset( $_REQUEST['paged'] ) ? max( 0, absint( $_REQUEST['paged'] ) - 1 ) * $posts_per_page : 0, 279 'post_status' => 'any', 280 's' => isset( $_REQUEST['s'] ) ? sanitize_text_field( $_REQUEST['s'] ) : '', 281 ); 282 283 $orderby_mapping = array( 284 'requester' => 'post_title', 285 'requested' => 'post_date', 286 ); 287 288 if ( isset( $_REQUEST['orderby'] ) && isset( $orderby_mapping[ $_REQUEST['orderby'] ] ) ) { 289 $args['orderby'] = $orderby_mapping[ $_REQUEST['orderby'] ]; 290 } 291 292 if ( isset( $_REQUEST['order'] ) && in_array( strtoupper( $_REQUEST['order'] ), array( 'ASC', 'DESC' ), true ) ) { 293 $args['order'] = strtoupper( $_REQUEST['order'] ); 294 } 295 296 if ( ! empty( $_REQUEST['filter-status'] ) ) { 297 $filter_status = isset( $_REQUEST['filter-status'] ) ? sanitize_text_field( $_REQUEST['filter-status'] ) : ''; 298 $args['post_status'] = $filter_status; 299 } 300 301 $requests_query = new WP_Query( $args ); 302 $requests = $requests_query->posts; 303 304 foreach ( $requests as $request ) { 305 $this->items[] = wp_get_user_request_data( $request->ID ); 306 } 307 308 $this->items = array_filter( $this->items ); 309 310 $this->set_pagination_args( 311 array( 312 'total_items' => $requests_query->found_posts, 313 'per_page' => $posts_per_page, 314 ) 315 ); 316 } 317 318 /** 319 * Checkbox column. 320 * 321 * @since 4.9.6 322 * 323 * @param WP_User_Request $item Item being shown. 324 * @return string Checkbox column markup. 325 */ 326 public function column_cb( $item ) { 327 return sprintf( '<input type="checkbox" name="request_id[]" value="%1$s" /><span class="spinner"></span>', esc_attr( $item->ID ) ); 328 } 329 330 /** 331 * Status column. 332 * 333 * @since 4.9.6 334 * 335 * @param WP_User_Request $item Item being shown. 336 * @return string Status column markup. 337 */ 338 public function column_status( $item ) { 339 $status = get_post_status( $item->ID ); 340 $status_object = get_post_status_object( $status ); 341 342 if ( ! $status_object || empty( $status_object->label ) ) { 343 return '-'; 344 } 345 346 $timestamp = false; 347 348 switch ( $status ) { 349 case 'request-confirmed': 350 $timestamp = $item->confirmed_timestamp; 351 break; 352 case 'request-completed': 353 $timestamp = $item->completed_timestamp; 354 break; 355 } 356 357 echo '<span class="status-label status-' . esc_attr( $status ) . '">'; 358 echo esc_html( $status_object->label ); 359 360 if ( $timestamp ) { 361 echo ' (' . $this->get_timestamp_as_date( $timestamp ) . ')'; 362 } 363 364 echo '</span>'; 365 } 366 367 /** 368 * Convert timestamp for display. 369 * 370 * @since 4.9.6 371 * 372 * @param int $timestamp Event timestamp. 373 * @return string Human readable date. 374 */ 375 protected function get_timestamp_as_date( $timestamp ) { 376 if ( empty( $timestamp ) ) { 377 return ''; 378 } 379 380 $time_diff = time() - $timestamp; 381 382 if ( $time_diff >= 0 && $time_diff < DAY_IN_SECONDS ) { 383 /* translators: human readable timestamp */ 384 return sprintf( __( '%s ago' ), human_time_diff( $timestamp ) ); 385 } 386 387 return date_i18n( get_option( 'date_format' ), $timestamp ); 388 } 389 390 /** 391 * Default column handler. 392 * 393 * @since 4.9.6 394 * 395 * @param WP_User_Request $item Item being shown. 396 * @param string $column_name Name of column being shown. 397 * @return string Default column output. 398 */ 399 public function column_default( $item, $column_name ) { 400 $cell_value = $item->$column_name; 401 402 if ( in_array( $column_name, array( 'created_timestamp' ), true ) ) { 403 return $this->get_timestamp_as_date( $cell_value ); 404 } 405 406 return $cell_value; 407 } 408 409 /** 410 * Actions column. Overridden by children. 411 * 412 * @since 4.9.6 413 * 414 * @param WP_User_Request $item Item being shown. 415 * @return string Email column markup. 416 */ 417 public function column_email( $item ) { 418 return sprintf( '<a href="%1$s">%2$s</a> %3$s', esc_url( 'mailto:' . $item->email ), $item->email, $this->row_actions( array() ) ); 419 } 420 421 /** 422 * Next steps column. Overridden by children. 423 * 424 * @since 4.9.6 425 * 426 * @param WP_User_Request $item Item being shown. 427 */ 428 public function column_next_steps( $item ) {} 429 430 /** 431 * Generates content for a single row of the table, 432 * 433 * @since 4.9.6 434 * 435 * @param WP_User_Request $item The current item. 436 */ 437 public function single_row( $item ) { 438 $status = $item->status; 439 440 echo '<tr id="request-' . esc_attr( $item->ID ) . '" class="status-' . esc_attr( $status ) . '">'; 441 $this->single_row_columns( $item ); 442 echo '</tr>'; 443 } 444 445 /** 446 * Embed scripts used to perform actions. Overridden by children. 447 * 448 * @since 4.9.6 449 */ 450 public function embed_scripts() {} 451 } -
src/wp-admin/includes/deprecated.php
Property changes on: src/wp-admin/includes/class-wp-privacy-requests-table.php ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property
1514 1514 </script> 1515 1515 <?php 1516 1516 } 1517 1518 /** 1519 * Previous class for list table for privacy data export requests. 1520 * 1521 * @since 4.9.6 1522 * @deprecated 5.3.0 1523 */ 1524 class WP_Privacy_Data_Export_Requests_Table extends WP_Privacy_Data_Export_Requests_List_Table { 1525 function __construct( $args ) { 1526 _deprecated_function( __CLASS__, '5.3.0', 'WP_Privacy_Data_Export_Requests_List_Table' ); 1527 1528 if ( ! isset( $args['screen'] ) || $args['screen'] === 'export_personal_data' ) { 1529 $args['screen'] = 'export-personal-data'; 1530 } 1531 1532 parent::__construct( $args ); 1533 } 1534 } 1535 1536 /** 1537 * Previous class for list table for privacy data erasure requests. 1538 * 1539 * @since 4.9.6 1540 * @deprecated 5.3.0 1541 */ 1542 class WP_Privacy_Data_Removal_Requests_Table extends WP_Privacy_Data_Removal_Requests_List_Table { 1543 function __construct( $args ) { 1544 _deprecated_function( __CLASS__, '5.3.0', 'WP_Privacy_Data_Removal_Requests_List_Table' ); 1545 1546 if ( ! isset( $args['screen'] ) || $args['screen'] === 'remove_personal_data' ) { 1547 $args['screen'] = 'erase-personal-data'; 1548 } 1549 1550 parent::__construct( $args ); 1551 } 1552 } -
src/wp-admin/includes/list-table.php
33 33 'WP_Themes_List_Table' => 'themes', 34 34 'WP_Theme_Install_List_Table' => array( 'themes', 'theme-install' ), 35 35 'WP_Plugins_List_Table' => 'plugins', 36 36 37 // Network Admin 37 'WP_MS_Sites_List_Table' => 'ms-sites', 38 'WP_MS_Users_List_Table' => 'ms-users', 39 'WP_MS_Themes_List_Table' => 'ms-themes', 38 'WP_MS_Sites_List_Table' => 'ms-sites', 39 'WP_MS_Users_List_Table' => 'ms-users', 40 'WP_MS_Themes_List_Table' => 'ms-themes', 41 42 // Privacy requests tables 43 'WP_Privacy_Data_Export_Requests_List_Table' => 'privacy-data-export-requests', 44 'WP_Privacy_Data_Removal_Requests_List_Table' => 'privacy-data-removal-requests', 40 45 ); 41 46 42 47 if ( isset( $core_classes[ $class ] ) ) { -
src/wp-admin/includes/misc.php
1319 1319 } 1320 1320 1321 1321 /** 1322 * WP_Privacy_Policy_Content class.1323 * TODO: move this to a new file.1324 *1325 * @since 4.9.61326 */1327 final class WP_Privacy_Policy_Content {1328 1329 private static $policy_content = array();1330 1331 /**1332 * Constructor1333 *1334 * @since 4.9.61335 */1336 private function __construct() {}1337 1338 /**1339 * Add content to the postbox shown when editing the privacy policy.1340 *1341 * Plugins and themes should suggest text for inclusion in the site's privacy policy.1342 * The suggested text should contain information about any functionality that affects user privacy,1343 * and will be shown in the Suggested Privacy Policy Content postbox.1344 *1345 * Intended for use from `wp_add_privacy_policy_content()`.1346 *1347 * @since 4.9.61348 *1349 * @param string $plugin_name The name of the plugin or theme that is suggesting content for the site's privacy policy.1350 * @param string $policy_text The suggested content for inclusion in the policy.1351 */1352 public static function add( $plugin_name, $policy_text ) {1353 if ( empty( $plugin_name ) || empty( $policy_text ) ) {1354 return;1355 }1356 1357 $data = array(1358 'plugin_name' => $plugin_name,1359 'policy_text' => $policy_text,1360 );1361 1362 if ( ! in_array( $data, self::$policy_content, true ) ) {1363 self::$policy_content[] = $data;1364 }1365 }1366 1367 /**1368 * Quick check if any privacy info has changed.1369 *1370 * @since 4.9.61371 */1372 public static function text_change_check() {1373 1374 $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );1375 1376 // The site doesn't have a privacy policy.1377 if ( empty( $policy_page_id ) ) {1378 return false;1379 }1380 1381 if ( ! current_user_can( 'edit_post', $policy_page_id ) ) {1382 return false;1383 }1384 1385 $old = (array) get_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content' );1386 1387 // Updates are not relevant if the user has not reviewed any suggestions yet.1388 if ( empty( $old ) ) {1389 return false;1390 }1391 1392 $cached = get_option( '_wp_suggested_policy_text_has_changed' );1393 1394 /*1395 * When this function is called before `admin_init`, `self::$policy_content`1396 * has not been populated yet, so use the cached result from the last1397 * execution instead.1398 */1399 if ( ! did_action( 'admin_init' ) ) {1400 return 'changed' === $cached;1401 }1402 1403 $new = self::$policy_content;1404 1405 // Remove the extra values added to the meta.1406 foreach ( $old as $key => $data ) {1407 if ( ! empty( $data['removed'] ) ) {1408 unset( $old[ $key ] );1409 continue;1410 }1411 1412 $old[ $key ] = array(1413 'plugin_name' => $data['plugin_name'],1414 'policy_text' => $data['policy_text'],1415 );1416 }1417 1418 // Normalize the order of texts, to facilitate comparison.1419 sort( $old );1420 sort( $new );1421 1422 // The == operator (equal, not identical) was used intentionally.1423 // See http://php.net/manual/en/language.operators.array.php1424 if ( $new != $old ) {1425 // A plugin was activated or deactivated, or some policy text has changed.1426 // Show a notice on the relevant screens to inform the admin.1427 add_action( 'admin_notices', array( 'WP_Privacy_Policy_Content', 'policy_text_changed_notice' ) );1428 $state = 'changed';1429 } else {1430 $state = 'not-changed';1431 }1432 1433 // Cache the result for use before `admin_init` (see above).1434 if ( $cached !== $state ) {1435 update_option( '_wp_suggested_policy_text_has_changed', $state );1436 }1437 1438 return 'changed' === $state;1439 }1440 1441 /**1442 * Output a warning when some privacy info has changed.1443 *1444 * @since 4.9.61445 */1446 public static function policy_text_changed_notice() {1447 global $post;1448 1449 $screen = get_current_screen()->id;1450 1451 if ( 'privacy' !== $screen ) {1452 return;1453 }1454 1455 ?>1456 <div class="policy-text-updated notice notice-warning is-dismissible">1457 <p>1458 <?php1459 printf(1460 /* translators: %s: Privacy Policy Guide URL */1461 __( 'The suggested privacy policy text has changed. Please <a href="%s">review the guide</a> and update your privacy policy.' ),1462 esc_url( admin_url( 'tools.php?wp-privacy-policy-guide=1' ) )1463 );1464 ?>1465 </p>1466 </div>1467 <?php1468 }1469 1470 /**1471 * Update the cached policy info when the policy page is updated.1472 *1473 * @since 4.9.61474 * @access private1475 */1476 public static function _policy_page_updated( $post_id ) {1477 $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );1478 1479 if ( ! $policy_page_id || $policy_page_id !== (int) $post_id ) {1480 return;1481 }1482 1483 // Remove updated|removed status.1484 $old = (array) get_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content' );1485 $done = array();1486 $update_cache = false;1487 1488 foreach ( $old as $old_key => $old_data ) {1489 if ( ! empty( $old_data['removed'] ) ) {1490 // Remove the old policy text.1491 $update_cache = true;1492 continue;1493 }1494 1495 if ( ! empty( $old_data['updated'] ) ) {1496 // 'updated' is now 'added'.1497 $done[] = array(1498 'plugin_name' => $old_data['plugin_name'],1499 'policy_text' => $old_data['policy_text'],1500 'added' => $old_data['updated'],1501 );1502 $update_cache = true;1503 } else {1504 $done[] = $old_data;1505 }1506 }1507 1508 if ( $update_cache ) {1509 delete_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content' );1510 // Update the cache.1511 foreach ( $done as $data ) {1512 add_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content', $data );1513 }1514 }1515 }1516 1517 /**1518 * Check for updated, added or removed privacy policy information from plugins.1519 *1520 * Caches the current info in post_meta of the policy page.1521 *1522 * @since 4.9.61523 *1524 * @return array The privacy policy text/informtion added by core and plugins.1525 */1526 public static function get_suggested_policy_text() {1527 $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );1528 $checked = array();1529 $time = time();1530 $update_cache = false;1531 $new = self::$policy_content;1532 $old = array();1533 1534 if ( $policy_page_id ) {1535 $old = (array) get_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content' );1536 }1537 1538 // Check for no-changes and updates.1539 foreach ( $new as $new_key => $new_data ) {1540 foreach ( $old as $old_key => $old_data ) {1541 $found = false;1542 1543 if ( $new_data['policy_text'] === $old_data['policy_text'] ) {1544 // Use the new plugin name in case it was changed, translated, etc.1545 if ( $old_data['plugin_name'] !== $new_data['plugin_name'] ) {1546 $old_data['plugin_name'] = $new_data['plugin_name'];1547 $update_cache = true;1548 }1549 1550 // A plugin was re-activated.1551 if ( ! empty( $old_data['removed'] ) ) {1552 unset( $old_data['removed'] );1553 $old_data['added'] = $time;1554 $update_cache = true;1555 }1556 1557 $checked[] = $old_data;1558 $found = true;1559 } elseif ( $new_data['plugin_name'] === $old_data['plugin_name'] ) {1560 // The info for the policy was updated.1561 $checked[] = array(1562 'plugin_name' => $new_data['plugin_name'],1563 'policy_text' => $new_data['policy_text'],1564 'updated' => $time,1565 );1566 $found = $update_cache = true;1567 }1568 1569 if ( $found ) {1570 unset( $new[ $new_key ], $old[ $old_key ] );1571 continue 2;1572 }1573 }1574 }1575 1576 if ( ! empty( $new ) ) {1577 // A plugin was activated.1578 foreach ( $new as $new_data ) {1579 if ( ! empty( $new_data['plugin_name'] ) && ! empty( $new_data['policy_text'] ) ) {1580 $new_data['added'] = $time;1581 $checked[] = $new_data;1582 }1583 }1584 $update_cache = true;1585 }1586 1587 if ( ! empty( $old ) ) {1588 // A plugin was deactivated.1589 foreach ( $old as $old_data ) {1590 if ( ! empty( $old_data['plugin_name'] ) && ! empty( $old_data['policy_text'] ) ) {1591 $data = array(1592 'plugin_name' => $old_data['plugin_name'],1593 'policy_text' => $old_data['policy_text'],1594 'removed' => $time,1595 );1596 1597 $checked[] = $data;1598 }1599 }1600 $update_cache = true;1601 }1602 1603 if ( $update_cache && $policy_page_id ) {1604 delete_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content' );1605 // Update the cache.1606 foreach ( $checked as $data ) {1607 add_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content', $data );1608 }1609 }1610 1611 return $checked;1612 }1613 1614 /**1615 * Add a notice with a link to the guide when editing the privacy policy page.1616 *1617 * @since 4.9.61618 * @since 5.0.0 The $post parameter is now optional.1619 *1620 * @param WP_Post|null $post The currently edited post. Default null.1621 */1622 public static function notice( $post = null ) {1623 if ( is_null( $post ) ) {1624 global $post;1625 } else {1626 $post = get_post( $post );1627 }1628 1629 if ( ! ( $post instanceof WP_Post ) ) {1630 return;1631 }1632 1633 if ( ! current_user_can( 'manage_privacy_options' ) ) {1634 return;1635 }1636 1637 $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );1638 1639 if ( ! $policy_page_id || $policy_page_id !== $post->ID ) {1640 return;1641 }1642 1643 $message = __( 'Need help putting together your new Privacy Policy page? Check out our guide for recommendations on what content to include, along with policies suggested by your plugins and theme.' );1644 $url = esc_url( admin_url( 'tools.php?wp-privacy-policy-guide=1' ) );1645 $label = __( 'View Privacy Policy Guide.' );1646 1647 if ( get_current_screen()->is_block_editor() ) {1648 wp_enqueue_script( 'wp-notices' );1649 $action = array(1650 'url' => $url,1651 'label' => $label,1652 );1653 wp_add_inline_script(1654 'wp-notices',1655 sprintf(1656 'wp.data.dispatch( "core/notices" ).createWarningNotice( "%s", { actions: [ %s ], isDismissible: false } )',1657 $message,1658 wp_json_encode( $action )1659 ),1660 'after'1661 );1662 } else {1663 ?>1664 <div class="notice notice-warning inline wp-pp-notice">1665 <p>1666 <?php1667 echo $message;1668 printf(1669 ' <a href="%s" target="_blank">%s <span class="screen-reader-text">%s</span></a>',1670 $url,1671 $label,1672 /* translators: accessibility text */1673 __( '(opens in a new tab)' )1674 );1675 ?>1676 </p>1677 </div>1678 <?php1679 }1680 }1681 1682 /**1683 * Output the privacy policy guide together with content from the theme and plugins.1684 *1685 * @since 4.9.61686 */1687 public static function privacy_policy_guide() {1688 1689 $content_array = self::get_suggested_policy_text();1690 1691 $content = '';1692 $toc = array( '<li><a href="#wp-privacy-policy-guide-introduction">' . __( 'Introduction' ) . '</a></li>' );1693 $date_format = __( 'F j, Y' );1694 $copy = __( 'Copy this section to clipboard' );1695 $return_to_top = '<a href="#" class="return-to-top">' . __( '↑ Return to Top' ) . '</a>';1696 1697 foreach ( $content_array as $section ) {1698 $class = $meta = $removed = '';1699 1700 if ( ! empty( $section['removed'] ) ) {1701 $class = ' text-removed';1702 $date = date_i18n( $date_format, $section['removed'] );1703 $meta = sprintf( __( 'Removed %s.' ), $date );1704 1705 $removed = __( 'You deactivated this plugin on %s and may no longer need this policy.' );1706 $removed = '<div class="error inline"><p>' . sprintf( $removed, $date ) . '</p></div>';1707 } elseif ( ! empty( $section['updated'] ) ) {1708 $class = ' text-updated';1709 $date = date_i18n( $date_format, $section['updated'] );1710 $meta = sprintf( __( 'Updated %s.' ), $date );1711 }1712 1713 if ( $meta ) {1714 $meta = '<br><span class="privacy-text-meta">' . $meta . '</span>';1715 }1716 1717 $plugin_name = esc_html( $section['plugin_name'] );1718 $toc_id = 'wp-privacy-policy-guide-' . sanitize_title( $plugin_name );1719 $toc[] = sprintf( '<li><a href="#%1$s">%2$s</a>' . $meta . '</li>', $toc_id, $plugin_name );1720 1721 $content .= '<div class="privacy-text-section' . $class . '">';1722 $content .= '<a id="' . $toc_id . '"> </a>';1723 /* translators: %s: plugin name */1724 $content .= '<h2>' . sprintf( __( 'Source: %s' ), $plugin_name ) . '</h2>';1725 $content .= $removed;1726 1727 $content .= '<div class="policy-text">' . $section['policy_text'] . '</div>';1728 $content .= $return_to_top;1729 1730 if ( empty( $section['removed'] ) ) {1731 $content .= '<div class="privacy-text-actions">';1732 $content .= '<button type="button" class="privacy-text-copy button">';1733 $content .= $copy;1734 $content .= '<span class="screen-reader-text">' . sprintf( __( 'Copy suggested policy text from %s.' ), $plugin_name ) . '</span>';1735 $content .= '</button>';1736 $content .= '</div>';1737 }1738 1739 $content .= "</div>\n"; // End of .privacy-text-section.1740 }1741 1742 if ( count( $toc ) > 2 ) {1743 ?>1744 <div class="privacy-text-box-toc">1745 <p><?php _e( 'Table of Contents' ); ?></p>1746 <ol>1747 <?php echo implode( "\n", $toc ); ?>1748 </ol>1749 </div>1750 <?php1751 }1752 1753 ?>1754 <div class="privacy-text-box">1755 <div class="privacy-text-box-head">1756 <a id="wp-privacy-policy-guide-introduction"> </a>1757 <h2><?php _e( 'Introduction' ); ?></h2>1758 <p><?php _e( 'Hello,' ); ?></p>1759 <p><?php _e( 'This text template will help you to create your web site’s privacy policy.' ); ?></p>1760 <p><?php _e( 'We have suggested the sections you will need. Under each section heading you will find a short summary of what information you should provide, which will help you to get started. Some sections include suggested policy content, others will have to be completed with information from your theme and plugins.' ); ?></p>1761 <p><?php _e( 'Please edit your privacy policy content, making sure to delete the summaries, and adding any information from your theme and plugins. Once you publish your policy page, remember to add it to your navigation menu.' ); ?></p>1762 <p><?php _e( 'It is your responsibility to write a comprehensive privacy policy, to make sure it reflects all national and international legal requirements on privacy, and to keep your policy current and accurate.' ); ?></p>1763 </div>1764 1765 <div class="privacy-text-box-body">1766 <?php echo $content; ?>1767 </div>1768 </div>1769 <?php1770 }1771 1772 /**1773 * Return the default suggested privacy policy content.1774 *1775 * @since 4.9.61776 * @since 5.0.0 Added the `$blocks` parameter.1777 *1778 * @param bool $description Whether to include the descriptions under the section headings. Default false.1779 * @param bool $blocks Whether to format the content for the block editor. Default true.1780 * @return string The default policy content.1781 */1782 public static function get_default_content( $description = false, $blocks = true ) {1783 $suggested_text = $description ? '<strong class="privacy-policy-tutorial">' . __( 'Suggested text:' ) . ' </strong>' : '';1784 $content = '';1785 $strings = array();1786 1787 // Start of the suggested privacy policy text.1788 if ( $description ) {1789 $strings[] = '<div class="wp-suggested-text">';1790 }1791 1792 /* translators: default privacy policy heading. */1793 $strings[] = '<h2>' . __( 'Who we are' ) . '</h2>';1794 1795 if ( $description ) {1796 /* translators: privacy policy tutorial. */1797 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this section you should note your site URL, as well as the name of the company, organization, or individual behind it, and some accurate contact information.' ) . '</p>';1798 /* translators: privacy policy tutorial. */1799 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'The amount of information you may be required to show will vary depending on your local or national business regulations. You may, for example, be required to display a physical address, a registered address, or your company registration number.' ) . '</p>';1800 }1801 1802 /* translators: default privacy policy text, %s Site URL. */1803 $strings[] = '<p>' . $suggested_text . sprintf( __( 'Our website address is: %s.' ), get_bloginfo( 'url', 'display' ) ) . '</p>';1804 1805 /* translators: default privacy policy heading. */1806 $strings[] = '<h2>' . __( 'What personal data we collect and why we collect it' ) . '</h2>';1807 1808 if ( $description ) {1809 /* translators: privacy policy tutorial. */1810 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this section you should note what personal data you collect from users and site visitors. This may include personal data, such as name, email address, personal account preferences; transactional data, such as purchase information; and technical data, such as information about cookies.' ) . '</p>';1811 /* translators: privacy policy tutorial. */1812 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'You should also note any collection and retention of sensitive personal data, such as data concerning health.' ) . '</p>';1813 /* translators: privacy policy tutorial. */1814 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In addition to listing what personal data you collect, you need to note why you collect it. These explanations must note either the legal basis for your data collection and retention or the active consent the user has given.' ) . '</p>';1815 /* translators: privacy policy tutorial. */1816 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'Personal data is not just created by a user’s interactions with your site. Personal data is also generated from technical processes such as contact forms, comments, cookies, analytics, and third party embeds.' ) . '</p>';1817 /* translators: privacy policy tutorial. */1818 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'By default WordPress does not collect any personal data about visitors, and only collects the data shown on the User Profile screen from registered users. However some of your plugins may collect personal data. You should add the relevant information below.' ) . '</p>';1819 }1820 1821 /* translators: default privacy policy heading. */1822 $strings[] = '<h3>' . __( 'Comments' ) . '</h3>';1823 1824 if ( $description ) {1825 /* translators: privacy policy tutorial. */1826 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this subsection you should note what information is captured through comments. We have noted the data which WordPress collects by default.' ) . '</p>';1827 }1828 1829 /* translators: default privacy policy text. */1830 $strings[] = '<p>' . $suggested_text . __( 'When visitors leave comments on the site we collect the data shown in the comments form, and also the visitor’s IP address and browser user agent string to help spam detection.' ) . '</p>';1831 /* translators: default privacy policy text. */1832 $strings[] = '<p>' . __( 'An anonymized string created from your email address (also called a hash) may be provided to the Gravatar service to see if you are using it. The Gravatar service privacy policy is available here: https://automattic.com/privacy/. After approval of your comment, your profile picture is visible to the public in the context of your comment.' ) . '</p>';1833 1834 /* translators: default privacy policy heading. */1835 $strings[] = '<h3>' . __( 'Media' ) . '</h3>';1836 1837 if ( $description ) {1838 /* translators: privacy policy tutorial. */1839 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this subsection you should note what information may be disclosed by users who can upload media files. All uploaded files are usually publicly accessible.' ) . '</p>';1840 }1841 1842 /* translators: default privacy policy text. */1843 $strings[] = '<p>' . $suggested_text . __( 'If you upload images to the website, you should avoid uploading images with embedded location data (EXIF GPS) included. Visitors to the website can download and extract any location data from images on the website.' ) . '</p>';1844 1845 /* translators: default privacy policy heading. */1846 $strings[] = '<h3>' . __( 'Contact forms' ) . '</h3>';1847 1848 if ( $description ) {1849 /* translators: privacy policy tutorial. */1850 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'By default, WordPress does not include a contact form. If you use a contact form plugin, use this subsection to note what personal data is captured when someone submits a contact form, and how long you keep it. For example, you may note that you keep contact form submissions for a certain period for customer service purposes, but you do not use the information submitted through them for marketing purposes.' ) . '</p>';1851 }1852 1853 /* translators: default privacy policy heading. */1854 $strings[] = '<h3>' . __( 'Cookies' ) . '</h3>';1855 1856 if ( $description ) {1857 /* translators: privacy policy tutorial. */1858 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this subsection you should list the cookies your web site uses, including those set by your plugins, social media, and analytics. We have provided the cookies which WordPress installs by default.' ) . '</p>';1859 }1860 1861 /* translators: default privacy policy text. */1862 $strings[] = '<p>' . $suggested_text . __( 'If you leave a comment on our site you may opt-in to saving your name, email address and website in cookies. These are for your convenience so that you do not have to fill in your details again when you leave another comment. These cookies will last for one year.' ) . '</p>';1863 /* translators: default privacy policy text. */1864 $strings[] = '<p>' . __( 'If you visit our login page, we will set a temporary cookie to determine if your browser accepts cookies. This cookie contains no personal data and is discarded when you close your browser.' ) . '</p>';1865 /* translators: default privacy policy text. */1866 $strings[] = '<p>' . __( 'When you log in, we will also set up several cookies to save your login information and your screen display choices. Login cookies last for two days, and screen options cookies last for a year. If you select "Remember Me", your login will persist for two weeks. If you log out of your account, the login cookies will be removed.' ) . '</p>';1867 /* translators: default privacy policy text. */1868 $strings[] = '<p>' . __( 'If you edit or publish an article, an additional cookie will be saved in your browser. This cookie includes no personal data and simply indicates the post ID of the article you just edited. It expires after 1 day.' ) . '</p>';1869 1870 /* translators: default privacy policy heading. */1871 $strings[] = '<h3>' . __( 'Embedded content from other websites' ) . '</h3>';1872 /* translators: default privacy policy text. */1873 $strings[] = '<p>' . $suggested_text . __( 'Articles on this site may include embedded content (e.g. videos, images, articles, etc.). Embedded content from other websites behaves in the exact same way as if the visitor has visited the other website.' ) . '</p>';1874 /* translators: default privacy policy text. */1875 $strings[] = '<p>' . __( 'These websites may collect data about you, use cookies, embed additional third-party tracking, and monitor your interaction with that embedded content, including tracking your interaction with the embedded content if you have an account and are logged in to that website.' ) . '</p>';1876 1877 /* translators: default privacy policy heading. */1878 $strings[] = '<h3>' . __( 'Analytics' ) . '</h3>';1879 1880 if ( $description ) {1881 /* translators: privacy policy tutorial. */1882 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this subsection you should note what analytics package you use, how users can opt out of analytics tracking, and a link to your analytics provider’s privacy policy, if any.' ) . '</p>';1883 /* translators: privacy policy tutorial. */1884 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'By default WordPress does not collect any analytics data. However, many web hosting accounts collect some anonymous analytics data. You may also have installed a WordPress plugin that provides analytics services. In that case, add information from that plugin here.' ) . '</p>';1885 }1886 1887 /* translators: default privacy policy heading. */1888 $strings[] = '<h2>' . __( 'Who we share your data with' ) . '</h2>';1889 1890 if ( $description ) {1891 /* translators: privacy policy tutorial. */1892 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this section you should name and list all third party providers with whom you share site data, including partners, cloud-based services, payment processors, and third party service providers, and note what data you share with them and why. Link to their own privacy policies if possible.' ) . '</p>';1893 /* translators: privacy policy tutorial. */1894 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'By default WordPress does not share any personal data with anyone.' ) . '</p>';1895 }1896 1897 /* translators: default privacy policy heading. */1898 $strings[] = '<h2>' . __( 'How long we retain your data' ) . '</h2>';1899 1900 if ( $description ) {1901 /* translators: privacy policy tutorial. */1902 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this section you should explain how long you retain personal data collected or processed by the web site. While it is your responsibility to come up with the schedule of how long you keep each dataset for and why you keep it, that information does need to be listed here. For example, you may want to say that you keep contact form entries for six months, analytics records for a year, and customer purchase records for ten years.' ) . '</p>';1903 }1904 1905 /* translators: default privacy policy text. */1906 $strings[] = '<p>' . $suggested_text . __( 'If you leave a comment, the comment and its metadata are retained indefinitely. This is so we can recognize and approve any follow-up comments automatically instead of holding them in a moderation queue.' ) . '</p>';1907 /* translators: default privacy policy text. */1908 $strings[] = '<p>' . __( 'For users that register on our website (if any), we also store the personal information they provide in their user profile. All users can see, edit, or delete their personal information at any time (except they cannot change their username). Website administrators can also see and edit that information.' ) . '</p>';1909 1910 /* translators: default privacy policy heading. */1911 $strings[] = '<h2>' . __( 'What rights you have over your data' ) . '</h2>';1912 1913 if ( $description ) {1914 /* translators: privacy policy tutorial. */1915 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this section you should explain what rights your users have over their data and how they can invoke those rights.' ) . '</p>';1916 }1917 1918 /* translators: default privacy policy text. */1919 $strings[] = '<p>' . $suggested_text . __( 'If you have an account on this site, or have left comments, you can request to receive an exported file of the personal data we hold about you, including any data you have provided to us. You can also request that we erase any personal data we hold about you. This does not include any data we are obliged to keep for administrative, legal, or security purposes.' ) . '</p>';1920 1921 /* translators: default privacy policy heading. */1922 $strings[] = '<h2>' . __( 'Where we send your data' ) . '</h2>';1923 1924 if ( $description ) {1925 /* translators: privacy policy tutorial. */1926 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this section you should list all transfers of your site data outside the European Union and describe the means by which that data is safeguarded to European data protection standards. This could include your web hosting, cloud storage, or other third party services.' ) . '</p>';1927 /* translators: privacy policy tutorial. */1928 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'European data protection law requires data about European residents which is transferred outside the European Union to be safeguarded to the same standards as if the data was in Europe. So in addition to listing where data goes, you should describe how you ensure that these standards are met either by yourself or by your third party providers, whether that is through an agreement such as Privacy Shield, model clauses in your contracts, or binding corporate rules.' ) . '</p>';1929 }1930 1931 /* translators: default privacy policy text. */1932 $strings[] = '<p>' . $suggested_text . __( 'Visitor comments may be checked through an automated spam detection service.' ) . '</p>';1933 1934 /* translators: default privacy policy heading. */1935 $strings[] = '<h2>' . __( 'Your contact information' ) . '</h2>';1936 1937 if ( $description ) {1938 /* translators: privacy policy tutorial. */1939 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this section you should provide a contact method for privacy-specific concerns. If you are required to have a Data Protection Officer, list their name and full contact details here as well.' ) . '</p>';1940 }1941 1942 /* translators: default privacy policy heading. */1943 $strings[] = '<h2>' . __( 'Additional information' ) . '</h2>';1944 1945 if ( $description ) {1946 /* translators: privacy policy tutorial. */1947 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'If you use your site for commercial purposes and you engage in more complex collection or processing of personal data, you should note the following information in your privacy policy in addition to the information we have already discussed.' ) . '</p>';1948 }1949 1950 /* translators: default privacy policy heading. */1951 $strings[] = '<h3>' . __( 'How we protect your data' ) . '</h3>';1952 1953 if ( $description ) {1954 /* translators: privacy policy tutorial. */1955 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this section you should explain what measures you have taken to protect your users’ data. This could include technical measures such as encryption; security measures such as two factor authentication; and measures such as staff training in data protection. If you have carried out a Privacy Impact Assessment, you can mention it here too.' ) . '</p>';1956 }1957 1958 /* translators: default privacy policy heading. */1959 $strings[] = '<h3>' . __( 'What data breach procedures we have in place' ) . '</h3>';1960 1961 if ( $description ) {1962 /* translators: privacy policy tutorial. */1963 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this section you should explain what procedures you have in place to deal with data breaches, either potential or real, such as internal reporting systems, contact mechanisms, or bug bounties.' ) . '</p>';1964 }1965 1966 /* translators: default privacy policy heading. */1967 $strings[] = '<h3>' . __( 'What third parties we receive data from' ) . '</h3>';1968 1969 if ( $description ) {1970 /* translators: privacy policy tutorial. */1971 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'If your web site receives data about users from third parties, including advertisers, this information must be included within the section of your privacy policy dealing with third party data.' ) . '</p>';1972 }1973 1974 /* translators: default privacy policy heading. */1975 $strings[] = '<h3>' . __( 'What automated decision making and/or profiling we do with user data' ) . '</h3>';1976 1977 if ( $description ) {1978 /* translators: privacy policy tutorial. */1979 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'If your web site provides a service which includes automated decision making - for example, allowing customers to apply for credit, or aggregating their data into an advertising profile - you must note that this is taking place, and include information about how that information is used, what decisions are made with that aggregated data, and what rights users have over decisions made without human intervention.' ) . '</p>';1980 }1981 1982 /* translators: default privacy policy heading. */1983 $strings[] = '<h3>' . __( 'Industry regulatory disclosure requirements' ) . '</h3>';1984 1985 if ( $description ) {1986 /* translators: privacy policy tutorial. */1987 $strings[] = '<p class="privacy-policy-tutorial">' . __( 'If you are a member of a regulated industry, or if you are subject to additional privacy laws, you may be required to disclose that information here.' ) . '</p>';1988 $strings[] = '</div>';1989 }1990 1991 if ( $blocks ) {1992 foreach ( $strings as $key => $string ) {1993 if ( 0 === strpos( $string, '<p>' ) ) {1994 $strings[ $key ] = '<!-- wp:paragraph -->' . $string . '<!-- /wp:paragraph -->';1995 }1996 1997 if ( 0 === strpos( $string, '<h2>' ) ) {1998 $strings[ $key ] = '<!-- wp:heading -->' . $string . '<!-- /wp:heading -->';1999 }2000 2001 if ( 0 === strpos( $string, '<h3>' ) ) {2002 $strings[ $key ] = '<!-- wp:heading {"level":3} -->' . $string . '<!-- /wp:heading -->';2003 }2004 }2005 }2006 2007 $content = implode( '', $strings );2008 // End of the suggested privacy policy text.2009 2010 /**2011 * Filters the default content suggested for inclusion in a privacy policy.2012 *2013 * @since 4.9.62014 * @since 5.0.0 Added the `$strings`, `$description`, and `$blocks` parameters.2015 *2016 * @param string $content The default policy content.2017 * @param array $strings An array of privacy policy content strings.2018 * @param bool $description Whether policy descriptions should be included.2019 * @param bool $blocks Whether the content should be formatted for the block editor.2020 */2021 return apply_filters( 'wp_get_default_privacy_policy_content', $content, $strings, $description, $blocks );2022 }2023 2024 /**2025 * Add the suggested privacy policy text to the policy postbox.2026 *2027 * @since 4.9.62028 */2029 public static function add_suggested_content() {2030 $content = self::get_default_content( true, false );2031 wp_add_privacy_policy_content( __( 'WordPress' ), $content );2032 }2033 }2034 2035 /**2036 1322 * Checks if the user needs to update PHP. 2037 1323 * 2038 1324 * @since 5.1.0 -
src/wp-admin/includes/plugin.php
2187 2187 } 2188 2188 2189 2189 if ( ! class_exists( 'WP_Privacy_Policy_Content' ) ) { 2190 require_once( ABSPATH . 'wp-admin/includes/ misc.php' );2190 require_once( ABSPATH . 'wp-admin/includes/class-wp-privacy-policy-content.php' ); 2191 2191 } 2192 2192 2193 2193 WP_Privacy_Policy_Content::add( $plugin_name, $policy_text ); -
src/wp-admin/includes/privacy-tools.php
1 <?php 2 /** 3 * WordPress Administration Privacy Tools API. 4 * 5 * @package WordPress 6 * @subpackage Administration 7 */ 8 9 /** 10 * Resend an existing request and return the result. 11 * 12 * @since 4.9.6 13 * @access private 14 * 15 * @param int $request_id Request ID. 16 * @return bool|WP_Error Returns true/false based on the success of sending the email, or a WP_Error object. 17 */ 18 function _wp_privacy_resend_request( $request_id ) { 19 $request_id = absint( $request_id ); 20 $request = get_post( $request_id ); 21 22 if ( ! $request || 'user_request' !== $request->post_type ) { 23 return new WP_Error( 'privacy_request_error', __( 'Invalid request.' ) ); 24 } 25 26 $result = wp_send_user_request( $request_id ); 27 28 if ( is_wp_error( $result ) ) { 29 return $result; 30 } elseif ( ! $result ) { 31 return new WP_Error( 'privacy_request_error', __( 'Unable to initiate confirmation request.' ) ); 32 } 33 34 return true; 35 } 36 37 /** 38 * Marks a request as completed by the admin and logs the current timestamp. 39 * 40 * @since 4.9.6 41 * @access private 42 * 43 * @param int $request_id Request ID. 44 * @return int|WP_Error $result Request ID on success or WP_Error. 45 */ 46 function _wp_privacy_completed_request( $request_id ) { 47 $request_id = absint( $request_id ); 48 $request = wp_get_user_request_data( $request_id ); 49 50 if ( ! $request ) { 51 return new WP_Error( 'privacy_request_error', __( 'Invalid request.' ) ); 52 } 53 54 update_post_meta( $request_id, '_wp_user_request_completed_timestamp', time() ); 55 56 $result = wp_update_post( 57 array( 58 'ID' => $request_id, 59 'post_status' => 'request-completed', 60 ) 61 ); 62 63 return $result; 64 } 65 66 /** 67 * Handle list table actions. 68 * 69 * @since 4.9.6 70 * @access private 71 */ 72 function _wp_personal_data_handle_actions() { 73 if ( isset( $_POST['privacy_action_email_retry'] ) ) { 74 check_admin_referer( 'bulk-privacy_requests' ); 75 76 $request_id = absint( current( array_keys( (array) wp_unslash( $_POST['privacy_action_email_retry'] ) ) ) ); 77 $result = _wp_privacy_resend_request( $request_id ); 78 79 if ( is_wp_error( $result ) ) { 80 add_settings_error( 81 'privacy_action_email_retry', 82 'privacy_action_email_retry', 83 $result->get_error_message(), 84 'error' 85 ); 86 } else { 87 add_settings_error( 88 'privacy_action_email_retry', 89 'privacy_action_email_retry', 90 __( 'Confirmation request sent again successfully.' ), 91 'updated' 92 ); 93 } 94 } elseif ( isset( $_POST['action'] ) ) { 95 $action = isset( $_POST['action'] ) ? sanitize_key( wp_unslash( $_POST['action'] ) ) : ''; 96 97 switch ( $action ) { 98 case 'add_export_personal_data_request': 99 case 'add_remove_personal_data_request': 100 check_admin_referer( 'personal-data-request' ); 101 102 if ( ! isset( $_POST['type_of_action'], $_POST['username_or_email_for_privacy_request'] ) ) { 103 add_settings_error( 104 'action_type', 105 'action_type', 106 __( 'Invalid action.' ), 107 'error' 108 ); 109 } 110 $action_type = sanitize_text_field( wp_unslash( $_POST['type_of_action'] ) ); 111 $username_or_email_address = sanitize_text_field( wp_unslash( $_POST['username_or_email_for_privacy_request'] ) ); 112 $email_address = ''; 113 114 if ( ! in_array( $action_type, _wp_privacy_action_request_types(), true ) ) { 115 add_settings_error( 116 'action_type', 117 'action_type', 118 __( 'Invalid action.' ), 119 'error' 120 ); 121 } 122 123 if ( ! is_email( $username_or_email_address ) ) { 124 $user = get_user_by( 'login', $username_or_email_address ); 125 if ( ! $user instanceof WP_User ) { 126 add_settings_error( 127 'username_or_email_for_privacy_request', 128 'username_or_email_for_privacy_request', 129 __( 'Unable to add this request. A valid email address or username must be supplied.' ), 130 'error' 131 ); 132 } else { 133 $email_address = $user->user_email; 134 } 135 } else { 136 $email_address = $username_or_email_address; 137 } 138 139 if ( empty( $email_address ) ) { 140 break; 141 } 142 143 $request_id = wp_create_user_request( $email_address, $action_type ); 144 145 if ( is_wp_error( $request_id ) ) { 146 add_settings_error( 147 'username_or_email_for_privacy_request', 148 'username_or_email_for_privacy_request', 149 $request_id->get_error_message(), 150 'error' 151 ); 152 break; 153 } elseif ( ! $request_id ) { 154 add_settings_error( 155 'username_or_email_for_privacy_request', 156 'username_or_email_for_privacy_request', 157 __( 'Unable to initiate confirmation request.' ), 158 'error' 159 ); 160 break; 161 } 162 163 wp_send_user_request( $request_id ); 164 165 add_settings_error( 166 'username_or_email_for_privacy_request', 167 'username_or_email_for_privacy_request', 168 __( 'Confirmation request initiated successfully.' ), 169 'updated' 170 ); 171 break; 172 } 173 } 174 } 175 176 /** 177 * Cleans up failed and expired requests before displaying the list table. 178 * 179 * @since 4.9.6 180 * @access private 181 */ 182 function _wp_personal_data_cleanup_requests() { 183 /** This filter is documented in wp-includes/user.php */ 184 $expires = (int) apply_filters( 'user_request_key_expiration', DAY_IN_SECONDS ); 185 186 $requests_query = new WP_Query( 187 array( 188 'post_type' => 'user_request', 189 'posts_per_page' => -1, 190 'post_status' => 'request-pending', 191 'fields' => 'ids', 192 'date_query' => array( 193 array( 194 'column' => 'post_modified_gmt', 195 'before' => $expires . ' seconds ago', 196 ), 197 ), 198 ) 199 ); 200 201 $request_ids = $requests_query->posts; 202 203 foreach ( $request_ids as $request_id ) { 204 wp_update_post( 205 array( 206 'ID' => $request_id, 207 'post_status' => 'request-failed', 208 'post_password' => '', 209 ) 210 ); 211 } 212 } 213 214 /** 215 * Add options for the privacy requests screens. 216 * 217 * @since 4.9.8 218 * @access private 219 */ 220 function _wp_privacy_requests_screen_options() { 221 $args = array( 222 'option' => str_replace( 'tools_page_', '', get_current_screen()->id ) . '_requests_per_page', 223 ); 224 add_screen_option( 'per_page', $args ); 225 } 226 227 /** 228 * Mark erasure requests as completed after processing is finished. 229 * 230 * This intercepts the Ajax responses to personal data eraser page requests, and 231 * monitors the status of a request. Once all of the processing has finished, the 232 * request is marked as completed. 233 * 234 * @since 4.9.6 235 * 236 * @see wp_privacy_personal_data_erasure_page 237 * 238 * @param array $response The response from the personal data eraser for 239 * the given page. 240 * @param int $eraser_index The index of the personal data eraser. Begins 241 * at 1. 242 * @param string $email_address The email address of the user whose personal 243 * data this is. 244 * @param int $page The page of personal data for this eraser. 245 * Begins at 1. 246 * @param int $request_id The request ID for this personal data erasure. 247 * @return array The filtered response. 248 */ 249 function wp_privacy_process_personal_data_erasure_page( $response, $eraser_index, $email_address, $page, $request_id ) { 250 /* 251 * If the eraser response is malformed, don't attempt to consume it; let it 252 * pass through, so that the default Ajax processing will generate a warning 253 * to the user. 254 */ 255 if ( ! is_array( $response ) ) { 256 return $response; 257 } 258 259 if ( ! array_key_exists( 'done', $response ) ) { 260 return $response; 261 } 262 263 if ( ! array_key_exists( 'items_removed', $response ) ) { 264 return $response; 265 } 266 267 if ( ! array_key_exists( 'items_retained', $response ) ) { 268 return $response; 269 } 270 271 if ( ! array_key_exists( 'messages', $response ) ) { 272 return $response; 273 } 274 275 $request = wp_get_user_request_data( $request_id ); 276 277 if ( ! $request || 'remove_personal_data' !== $request->action_name ) { 278 wp_send_json_error( __( 'Invalid request ID when processing eraser data.' ) ); 279 } 280 281 /** This filter is documented in wp-admin/includes/ajax-actions.php */ 282 $erasers = apply_filters( 'wp_privacy_personal_data_erasers', array() ); 283 $is_last_eraser = count( $erasers ) === $eraser_index; 284 $eraser_done = $response['done']; 285 286 if ( ! $is_last_eraser || ! $eraser_done ) { 287 return $response; 288 } 289 290 _wp_privacy_completed_request( $request_id ); 291 292 /** 293 * Fires immediately after a personal data erasure request has been marked completed. 294 * 295 * @since 4.9.6 296 * 297 * @param int $request_id The privacy request post ID associated with this request. 298 */ 299 do_action( 'wp_privacy_personal_data_erased', $request_id ); 300 301 return $response; 302 } -
src/wp-admin/includes/upgrade.php
Property changes on: src/wp-admin/includes/privacy-tools.php ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property
342 342 $privacy_policy_content = get_site_option( 'default_privacy_policy_content' ); 343 343 } else { 344 344 if ( ! class_exists( 'WP_Privacy_Policy_Content' ) ) { 345 include_once( ABSPATH . 'wp-admin/includes/ misc.php' );345 include_once( ABSPATH . 'wp-admin/includes/class-wp-privacy-policy-content.php' ); 346 346 } 347 347 348 348 $privacy_policy_content = WP_Privacy_Policy_Content::get_default_content(); -
src/wp-admin/includes/user.php
582 582 wp_specialchars_decode( translate_user_role( $role['name'] ) ) 583 583 ); 584 584 } 585 586 /**587 * Resend an existing request and return the result.588 *589 * @since 4.9.6590 * @access private591 *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.6618 * @access private619 *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.6647 * @access private648 */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.6757 * @access private758 */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.6795 * @access private796 */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 <?php860 $requests_table->display();861 $requests_table->embed_scripts();862 ?>863 </form>864 </div>865 <?php866 }867 868 /**869 * Personal data anonymization.870 *871 * @since 4.9.6872 * @access private873 */874 function _wp_personal_data_removal_page() {875 /*876 * Require both caps in order to make it explicitly clear that delegating877 * erasure from network admins to single-site admins will give them the878 * ability to affect global users, rather than being limited to the site879 * 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 <?php944 $requests_table->display();945 $requests_table->embed_scripts();946 ?>947 </form>948 </div>949 <?php950 }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, and956 * monitors the status of a request. Once all of the processing has finished, the957 * request is marked as completed.958 *959 * @since 4.9.6960 *961 * @see wp_privacy_personal_data_erasure_page962 *963 * @param array $response The response from the personal data eraser for964 * the given page.965 * @param int $eraser_index The index of the personal data eraser. Begins966 * at 1.967 * @param string $email_address The email address of the user whose personal968 * 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 it977 * pass through, so that the default Ajax processing will generate a warning978 * 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.61021 *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.61033 * @access private1034 */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.81044 * @access private1045 */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.61062 */1063 abstract class WP_Privacy_Requests_Table extends WP_List_Table {1064 1065 /**1066 * Action name for the requests this table will work with. Classes1067 * which inherit from WP_Privacy_Requests_Table should define this.1068 *1069 * Example: 'export_personal_data'.1070 *1071 * @since 4.9.61072 *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.61081 *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.61090 *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.61108 *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.61127 *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.61138 *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_posts1153 FROM {$wpdb->posts}1154 WHERE post_type = %s1155 AND post_name = %s1156 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.61175 *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_label1203 );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_label1224 );1225 }1226 1227 return $views;1228 }1229 1230 /**1231 * Get bulk actions.1232 *1233 * @since 4.9.61234 *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.61248 */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.61299 * @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.61354 *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.61366 *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.61403 *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.61426 *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.61445 *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.61457 *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.61466 *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.61481 */1482 public function embed_scripts() {}1483 }1484 1485 /**1486 * WP_Privacy_Data_Export_Requests_Table class.1487 *1488 * @since 4.9.61489 */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.61495 *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.61504 *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.61513 *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.61548 *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 <?php1578 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.61606 */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.61612 *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.61621 *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.61630 *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.61670 *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 <?php1699 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 } -
src/wp-admin/menu.php
264 264 $submenu['tools.php'][10] = array( __( 'Import' ), 'import', 'import.php' ); 265 265 $submenu['tools.php'][15] = array( __( 'Export' ), 'export', 'export.php' ); 266 266 $submenu['tools.php'][20] = array( __( 'Site Health' ), 'install_plugins', 'site-health.php' ); 267 $submenu['tools.php'][25] = array( __( 'Export Personal Data' ), 'export_others_personal_data', 'export-personal-data.php' ); 268 $submenu['tools.php'][30] = array( __( 'Erase Personal Data' ), 'erase_others_personal_data', 'erase-personal-data.php' ); 267 269 if ( is_multisite() && ! is_main_site() ) { 268 $submenu['tools.php'][ 25] = array( __( 'Delete Site' ), 'delete_site', 'ms-delete-site.php' );270 $submenu['tools.php'][35] = array( __( 'Delete Site' ), 'delete_site', 'ms-delete-site.php' ); 269 271 } 270 272 if ( ! is_multisite() && defined( 'WP_ALLOW_MULTISITE' ) && WP_ALLOW_MULTISITE ) { 271 273 $submenu['tools.php'][50] = array( __( 'Network Setup' ), 'setup_network', 'network.php' ); … … 278 280 $submenu['options-general.php'][25] = array( __( 'Discussion' ), 'manage_options', 'options-discussion.php' ); 279 281 $submenu['options-general.php'][30] = array( __( 'Media' ), 'manage_options', 'options-media.php' ); 280 282 $submenu['options-general.php'][40] = array( __( 'Permalinks' ), 'manage_options', 'options-permalink.php' ); 281 $submenu['options-general.php'][45] = array( __( 'Privacy' ), 'manage_privacy_options', ' privacy.php' );283 $submenu['options-general.php'][45] = array( __( 'Privacy' ), 'manage_privacy_options', 'options-privacy.php' ); 282 284 283 285 $_wp_last_utility_menu = 80; // The index of the last top-level menu in the utility menu group 284 286 -
src/wp-admin/options-privacy.php
1 <?php 2 /** 3 * Privacy Settings Screen. 4 * 5 * @package WordPress 6 * @subpackage Administration 7 */ 8 9 /** WordPress Administration Bootstrap */ 10 require_once( dirname( __FILE__ ) . '/admin.php' ); 11 12 if ( ! current_user_can( 'manage_privacy_options' ) ) { 13 wp_die( __( 'Sorry, you are not allowed to manage privacy on this site.' ) ); 14 } 15 16 $action = isset( $_POST['action'] ) ? $_POST['action'] : ''; 17 18 if ( ! empty( $action ) ) { 19 check_admin_referer( $action ); 20 21 if ( 'set-privacy-page' === $action ) { 22 $privacy_policy_page_id = isset( $_POST['page_for_privacy_policy'] ) ? (int) $_POST['page_for_privacy_policy'] : 0; 23 update_option( 'wp_page_for_privacy_policy', $privacy_policy_page_id ); 24 25 $privacy_page_updated_message = __( 'Privacy Policy page updated successfully.' ); 26 27 if ( $privacy_policy_page_id ) { 28 /* 29 * Don't always link to the menu customizer: 30 * 31 * - Unpublished pages can't be selected by default. 32 * - `WP_Customize_Nav_Menus::__construct()` checks the user's capabilities. 33 * - Themes might not "officially" support menus. 34 */ 35 if ( 36 'publish' === get_post_status( $privacy_policy_page_id ) 37 && current_user_can( 'edit_theme_options' ) 38 && current_theme_supports( 'menus' ) 39 ) { 40 $privacy_page_updated_message = sprintf( 41 /* translators: %s: URL to Customizer -> Menus */ 42 __( 'Privacy Policy page setting updated successfully. Remember to <a href="%s">update your menus</a>!' ), 43 esc_url( add_query_arg( 'autofocus[panel]', 'nav_menus', admin_url( 'customize.php' ) ) ) 44 ); 45 } 46 } 47 48 add_settings_error( 49 'page_for_privacy_policy', 50 'page_for_privacy_policy', 51 $privacy_page_updated_message, 52 'updated' 53 ); 54 } elseif ( 'create-privacy-page' === $action ) { 55 56 if ( ! class_exists( 'WP_Privacy_Policy_Content' ) ) { 57 require_once( ABSPATH . 'wp-admin/includes/class-wp-privacy-policy-content.php' ); 58 } 59 60 $privacy_policy_page_content = WP_Privacy_Policy_Content::get_default_content(); 61 $privacy_policy_page_id = wp_insert_post( 62 array( 63 'post_title' => __( 'Privacy Policy' ), 64 'post_status' => 'draft', 65 'post_type' => 'page', 66 'post_content' => $privacy_policy_page_content, 67 ), 68 true 69 ); 70 71 if ( is_wp_error( $privacy_policy_page_id ) ) { 72 add_settings_error( 73 'page_for_privacy_policy', 74 'page_for_privacy_policy', 75 __( 'Unable to create a Privacy Policy page.' ), 76 'error' 77 ); 78 } else { 79 update_option( 'wp_page_for_privacy_policy', $privacy_policy_page_id ); 80 81 wp_redirect( admin_url( 'post.php?post=' . $privacy_policy_page_id . '&action=edit' ) ); 82 exit; 83 } 84 } 85 } 86 87 // If a Privacy Policy page ID is available, make sure the page actually exists. If not, display an error. 88 $privacy_policy_page_exists = false; 89 $privacy_policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' ); 90 91 if ( ! empty( $privacy_policy_page_id ) ) { 92 93 $privacy_policy_page = get_post( $privacy_policy_page_id ); 94 95 if ( ! $privacy_policy_page instanceof WP_Post ) { 96 add_settings_error( 97 'page_for_privacy_policy', 98 'page_for_privacy_policy', 99 __( 'The currently selected Privacy Policy page does not exist. Please create or select a new page.' ), 100 'error' 101 ); 102 } else { 103 if ( 'trash' === $privacy_policy_page->post_status ) { 104 add_settings_error( 105 'page_for_privacy_policy', 106 'page_for_privacy_policy', 107 sprintf( 108 /* translators: URL to Pages Trash */ 109 __( 'The currently selected Privacy Policy page is in the trash. Please create or select a new Privacy Policy page or <a href="%s">restore the current page</a>.' ), 110 'edit.php?post_status=trash&post_type=page' 111 ), 112 'error' 113 ); 114 } else { 115 $privacy_policy_page_exists = true; 116 } 117 } 118 } 119 120 $title = __( 'Privacy Settings' ); 121 $parent_file = 'options-general.php'; 122 123 require_once( ABSPATH . 'wp-admin/admin-header.php' ); 124 125 ?> 126 <div class="wrap"> 127 <h1><?php echo $title; ?></h1> 128 <h2><?php _e( 'Privacy Policy Page' ); ?></h2> 129 <p> 130 <?php _e( 'As a website owner, you may need to follow national or international privacy laws. For example, you may need to create and display a Privacy Policy.' ); ?> 131 <?php _e( 'If you already have a Privacy Policy page, please select it below. If not, please create one.' ); ?> 132 </p> 133 <p> 134 <?php _e( 'The new page will include help and suggestions for your Privacy Policy.' ); ?> 135 <?php _e( 'However, it is your responsibility to use those resources correctly, to provide the information that your Privacy Policy requires, and to keep that information current and accurate.' ); ?> 136 </p> 137 <p> 138 <?php _e( 'After your Privacy Policy page is set, we suggest that you edit it.' ); ?> 139 <?php _e( 'We would also suggest reviewing your Privacy Policy from time to time, especially after installing or updating any themes or plugins. There may be changes or new suggested information for you to consider adding to your policy.' ); ?> 140 </p> 141 <?php 142 143 if ( $privacy_policy_page_exists ) { 144 $edit_href = add_query_arg( 145 array( 146 'post' => $privacy_policy_page_id, 147 'action' => 'edit', 148 ), 149 admin_url( 'post.php' ) 150 ); 151 152 $view_href = get_permalink( $privacy_policy_page_id ); 153 154 ?> 155 <p class="tools-privacy-edit"><strong> 156 <?php 157 158 if ( 'publish' === get_post_status( $privacy_policy_page_id ) ) { 159 printf( 160 /* translators: 1: URL to edit Privacy Policy page, 2: URL to view Privacy Policy page */ 161 __( '<a href="%1$s">Edit</a> or <a href="%2$s">view</a> your Privacy Policy page content.' ), 162 esc_url( $edit_href ), 163 esc_url( $view_href ) 164 ); 165 } else { 166 printf( 167 /* translators: 1: URL to edit Privacy Policy page, 2: URL to preview Privacy Policy page */ 168 __( '<a href="%1$s">Edit</a> or <a href="%2$s">preview</a> your Privacy Policy page content.' ), 169 esc_url( $edit_href ), 170 esc_url( $view_href ) 171 ); 172 } 173 174 ?> 175 </strong></p> 176 <?php 177 } 178 ?> 179 <p> 180 <?php 181 182 printf( 183 /* translators: 1: Privacy Policy guide URL, 2: additional link attributes, 3: accessibility text */ 184 __( 'Need help putting together your new Privacy Policy page? <a href="%1$s" %2$s>Check out our guide%3$s</a> for recommendations on what content to include, along with policies suggested by your plugins and theme.' ), 185 esc_url( admin_url( 'privacy-policy-guide.php' ) ), 186 '', 187 '' 188 ); 189 190 ?> 191 </p> 192 193 <hr> 194 <table class="form-table tools-privacy-policy-page"> 195 <tr> 196 <th scope="row"> 197 <?php 198 if ( $privacy_policy_page_exists ) { 199 _e( 'Change your Privacy Policy page' ); 200 } else { 201 _e( 'Select a Privacy Policy page' ); 202 } 203 ?> 204 </th> 205 <td> 206 <?php 207 $has_pages = (bool) get_posts( 208 array( 209 'post_type' => 'page', 210 'posts_per_page' => 1, 211 'post_status' => array( 212 'publish', 213 'draft', 214 ), 215 ) 216 ); 217 218 if ( $has_pages ) : 219 ?> 220 <form method="post" action=""> 221 <label for="page_for_privacy_policy"> 222 <?php _e( 'Select an existing page:' ); ?> 223 </label> 224 <input type="hidden" name="action" value="set-privacy-page" /> 225 <?php 226 wp_dropdown_pages( 227 array( 228 'name' => 'page_for_privacy_policy', 229 'show_option_none' => __( '— Select —' ), 230 'option_none_value' => '0', 231 'selected' => $privacy_policy_page_id, 232 'post_status' => array( 'draft', 'publish' ), 233 ) 234 ); 235 236 wp_nonce_field( 'set-privacy-page' ); 237 238 submit_button( __( 'Use This Page' ), 'primary', 'submit', false, array( 'id' => 'set-page' ) ); 239 ?> 240 </form> 241 <?php endif; ?> 242 243 <form class="wp-create-privacy-page" method="post" action=""> 244 <input type="hidden" name="action" value="create-privacy-page" /> 245 <span> 246 <?php 247 if ( $has_pages ) { 248 _e( 'Or:' );