[ Index ] |
PHP Cross Reference of WordPress |
[Summary view] [Print] [Text view]
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 true|WP_Error Returns true if sending the email was successful, 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 personal data 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 for personal data 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 Request ID on success, or a WP_Error on failure. 45 */ 46 function _wp_privacy_completed_request( $request_id ) { 47 // Get the request. 48 $request_id = absint( $request_id ); 49 $request = wp_get_user_request( $request_id ); 50 51 if ( ! $request ) { 52 return new WP_Error( 'privacy_request_error', __( 'Invalid personal data request.' ) ); 53 } 54 55 update_post_meta( $request_id, '_wp_user_request_completed_timestamp', time() ); 56 57 $result = wp_update_post( 58 array( 59 'ID' => $request_id, 60 'post_status' => 'request-completed', 61 ) 62 ); 63 64 return $result; 65 } 66 67 /** 68 * Handle list table actions. 69 * 70 * @since 4.9.6 71 * @access private 72 */ 73 function _wp_personal_data_handle_actions() { 74 if ( isset( $_POST['privacy_action_email_retry'] ) ) { 75 check_admin_referer( 'bulk-privacy_requests' ); 76 77 $request_id = absint( current( array_keys( (array) wp_unslash( $_POST['privacy_action_email_retry'] ) ) ) ); 78 $result = _wp_privacy_resend_request( $request_id ); 79 80 if ( is_wp_error( $result ) ) { 81 add_settings_error( 82 'privacy_action_email_retry', 83 'privacy_action_email_retry', 84 $result->get_error_message(), 85 'error' 86 ); 87 } else { 88 add_settings_error( 89 'privacy_action_email_retry', 90 'privacy_action_email_retry', 91 __( 'Confirmation request sent again successfully.' ), 92 'success' 93 ); 94 } 95 } elseif ( isset( $_POST['action'] ) ) { 96 $action = ! empty( $_POST['action'] ) ? sanitize_key( wp_unslash( $_POST['action'] ) ) : ''; 97 98 switch ( $action ) { 99 case 'add_export_personal_data_request': 100 case 'add_remove_personal_data_request': 101 check_admin_referer( 'personal-data-request' ); 102 103 if ( ! isset( $_POST['type_of_action'], $_POST['username_or_email_for_privacy_request'] ) ) { 104 add_settings_error( 105 'action_type', 106 'action_type', 107 __( 'Invalid personal data action.' ), 108 'error' 109 ); 110 } 111 $action_type = sanitize_text_field( wp_unslash( $_POST['type_of_action'] ) ); 112 $username_or_email_address = sanitize_text_field( wp_unslash( $_POST['username_or_email_for_privacy_request'] ) ); 113 $email_address = ''; 114 $status = 'pending'; 115 116 if ( ! isset( $_POST['send_confirmation_email'] ) ) { 117 $status = 'confirmed'; 118 } 119 120 if ( ! in_array( $action_type, _wp_privacy_action_request_types(), true ) ) { 121 add_settings_error( 122 'action_type', 123 'action_type', 124 __( 'Invalid personal data action.' ), 125 'error' 126 ); 127 } 128 129 if ( ! is_email( $username_or_email_address ) ) { 130 $user = get_user_by( 'login', $username_or_email_address ); 131 if ( ! $user instanceof WP_User ) { 132 add_settings_error( 133 'username_or_email_for_privacy_request', 134 'username_or_email_for_privacy_request', 135 __( 'Unable to add this request. A valid email address or username must be supplied.' ), 136 'error' 137 ); 138 } else { 139 $email_address = $user->user_email; 140 } 141 } else { 142 $email_address = $username_or_email_address; 143 } 144 145 if ( empty( $email_address ) ) { 146 break; 147 } 148 149 $request_id = wp_create_user_request( $email_address, $action_type, array(), $status ); 150 $message = ''; 151 152 if ( is_wp_error( $request_id ) ) { 153 $message = $request_id->get_error_message(); 154 } elseif ( ! $request_id ) { 155 $message = __( 'Unable to initiate confirmation request.' ); 156 } 157 158 if ( $message ) { 159 add_settings_error( 160 'username_or_email_for_privacy_request', 161 'username_or_email_for_privacy_request', 162 $message, 163 'error' 164 ); 165 break; 166 } 167 168 if ( 'pending' === $status ) { 169 wp_send_user_request( $request_id ); 170 171 $message = __( 'Confirmation request initiated successfully.' ); 172 } elseif ( 'confirmed' === $status ) { 173 $message = __( 'Request added successfully.' ); 174 } 175 176 if ( $message ) { 177 add_settings_error( 178 'username_or_email_for_privacy_request', 179 'username_or_email_for_privacy_request', 180 $message, 181 'success' 182 ); 183 break; 184 } 185 } 186 } 187 } 188 189 /** 190 * Cleans up failed and expired requests before displaying the list table. 191 * 192 * @since 4.9.6 193 * @access private 194 */ 195 function _wp_personal_data_cleanup_requests() { 196 /** This filter is documented in wp-includes/user.php */ 197 $expires = (int) apply_filters( 'user_request_key_expiration', DAY_IN_SECONDS ); 198 199 $requests_query = new WP_Query( 200 array( 201 'post_type' => 'user_request', 202 'posts_per_page' => -1, 203 'post_status' => 'request-pending', 204 'fields' => 'ids', 205 'date_query' => array( 206 array( 207 'column' => 'post_modified_gmt', 208 'before' => $expires . ' seconds ago', 209 ), 210 ), 211 ) 212 ); 213 214 $request_ids = $requests_query->posts; 215 216 foreach ( $request_ids as $request_id ) { 217 wp_update_post( 218 array( 219 'ID' => $request_id, 220 'post_status' => 'request-failed', 221 'post_password' => '', 222 ) 223 ); 224 } 225 } 226 227 /** 228 * Generate a single group for the personal data export report. 229 * 230 * @since 4.9.6 231 * @since 5.4.0 Added the `$group_id` and `$groups_count` parameters. 232 * 233 * @param array $group_data { 234 * The group data to render. 235 * 236 * @type string $group_label The user-facing heading for the group, e.g. 'Comments'. 237 * @type array $items { 238 * An array of group items. 239 * 240 * @type array $group_item_data { 241 * An array of name-value pairs for the item. 242 * 243 * @type string $name The user-facing name of an item name-value pair, e.g. 'IP Address'. 244 * @type string $value The user-facing value of an item data pair, e.g. '50.60.70.0'. 245 * } 246 * } 247 * } 248 * @param string $group_id The group identifier. 249 * @param int $groups_count The number of all groups 250 * @return string The HTML for this group and its items. 251 */ 252 function wp_privacy_generate_personal_data_export_group_html( $group_data, $group_id = '', $groups_count = 1 ) { 253 $group_id_attr = sanitize_title_with_dashes( $group_data['group_label'] . '-' . $group_id ); 254 255 $group_html = '<h2 id="' . esc_attr( $group_id_attr ) . '">'; 256 $group_html .= esc_html( $group_data['group_label'] ); 257 258 $items_count = count( (array) $group_data['items'] ); 259 if ( $items_count > 1 ) { 260 $group_html .= sprintf( ' <span class="count">(%d)</span>', $items_count ); 261 } 262 263 $group_html .= '</h2>'; 264 265 if ( ! empty( $group_data['group_description'] ) ) { 266 $group_html .= '<p>' . esc_html( $group_data['group_description'] ) . '</p>'; 267 } 268 269 $group_html .= '<div>'; 270 271 foreach ( (array) $group_data['items'] as $group_item_id => $group_item_data ) { 272 $group_html .= '<table>'; 273 $group_html .= '<tbody>'; 274 275 foreach ( (array) $group_item_data as $group_item_datum ) { 276 $value = $group_item_datum['value']; 277 // If it looks like a link, make it a link. 278 if ( false === strpos( $value, ' ' ) && ( 0 === strpos( $value, 'http://' ) || 0 === strpos( $value, 'https://' ) ) ) { 279 $value = '<a href="' . esc_url( $value ) . '">' . esc_html( $value ) . '</a>'; 280 } 281 282 $group_html .= '<tr>'; 283 $group_html .= '<th>' . esc_html( $group_item_datum['name'] ) . '</th>'; 284 $group_html .= '<td>' . wp_kses( $value, 'personal_data_export' ) . '</td>'; 285 $group_html .= '</tr>'; 286 } 287 288 $group_html .= '</tbody>'; 289 $group_html .= '</table>'; 290 } 291 292 if ( $groups_count > 1 ) { 293 $group_html .= '<div class="return-to-top">'; 294 $group_html .= '<a href="#top"><span aria-hidden="true">↑ </span> ' . esc_html__( 'Go to top' ) . '</a>'; 295 $group_html .= '</div>'; 296 } 297 298 $group_html .= '</div>'; 299 300 return $group_html; 301 } 302 303 /** 304 * Generate the personal data export file. 305 * 306 * @since 4.9.6 307 * 308 * @param int $request_id The export request ID. 309 */ 310 function wp_privacy_generate_personal_data_export_file( $request_id ) { 311 if ( ! class_exists( 'ZipArchive' ) ) { 312 wp_send_json_error( __( 'Unable to generate personal data export file. ZipArchive not available.' ) ); 313 } 314 315 // Get the request. 316 $request = wp_get_user_request( $request_id ); 317 318 if ( ! $request || 'export_personal_data' !== $request->action_name ) { 319 wp_send_json_error( __( 'Invalid request ID when generating personal data export file.' ) ); 320 } 321 322 $email_address = $request->email; 323 324 if ( ! is_email( $email_address ) ) { 325 wp_send_json_error( __( 'Invalid email address when generating personal data export file.' ) ); 326 } 327 328 // Create the exports folder if needed. 329 $exports_dir = wp_privacy_exports_dir(); 330 $exports_url = wp_privacy_exports_url(); 331 332 if ( ! wp_mkdir_p( $exports_dir ) ) { 333 wp_send_json_error( __( 'Unable to create personal data export folder.' ) ); 334 } 335 336 // Protect export folder from browsing. 337 $index_pathname = $exports_dir . 'index.php'; 338 if ( ! file_exists( $index_pathname ) ) { 339 $file = fopen( $index_pathname, 'w' ); 340 if ( false === $file ) { 341 wp_send_json_error( __( 'Unable to protect personal data export folder from browsing.' ) ); 342 } 343 fwrite( $file, "<?php\n// Silence is golden.\n" ); 344 fclose( $file ); 345 } 346 347 $obscura = wp_generate_password( 32, false, false ); 348 $file_basename = 'wp-personal-data-file-' . $obscura; 349 $html_report_filename = wp_unique_filename( $exports_dir, $file_basename . '.html' ); 350 $html_report_pathname = wp_normalize_path( $exports_dir . $html_report_filename ); 351 $json_report_filename = $file_basename . '.json'; 352 $json_report_pathname = wp_normalize_path( $exports_dir . $json_report_filename ); 353 354 /* 355 * Gather general data needed. 356 */ 357 358 // Title. 359 $title = sprintf( 360 /* translators: %s: User's email address. */ 361 __( 'Personal Data Export for %s' ), 362 $email_address 363 ); 364 365 // First, build an "About" group on the fly for this report. 366 $about_group = array( 367 /* translators: Header for the About section in a personal data export. */ 368 'group_label' => _x( 'About', 'personal data group label' ), 369 /* translators: Description for the About section in a personal data export. */ 370 'group_description' => _x( 'Overview of export report.', 'personal data group description' ), 371 'items' => array( 372 'about-1' => array( 373 array( 374 'name' => _x( 'Report generated for', 'email address' ), 375 'value' => $email_address, 376 ), 377 array( 378 'name' => _x( 'For site', 'website name' ), 379 'value' => get_bloginfo( 'name' ), 380 ), 381 array( 382 'name' => _x( 'At URL', 'website URL' ), 383 'value' => get_bloginfo( 'url' ), 384 ), 385 array( 386 'name' => _x( 'On', 'date/time' ), 387 'value' => current_time( 'mysql' ), 388 ), 389 ), 390 ), 391 ); 392 393 // And now, all the Groups. 394 $groups = get_post_meta( $request_id, '_export_data_grouped', true ); 395 if ( is_array( $groups ) ) { 396 // Merge in the special "About" group. 397 $groups = array_merge( array( 'about' => $about_group ), $groups ); 398 $groups_count = count( $groups ); 399 } else { 400 if ( false !== $groups ) { 401 _doing_it_wrong( 402 __FUNCTION__, 403 /* translators: %s: Post meta key. */ 404 sprintf( __( 'The %s post meta must be an array.' ), '<code>_export_data_grouped</code>' ), 405 '5.8.0' 406 ); 407 } 408 409 $groups = null; 410 $groups_count = 0; 411 } 412 413 // Convert the groups to JSON format. 414 $groups_json = wp_json_encode( $groups ); 415 416 if ( false === $groups_json ) { 417 $error_message = sprintf( 418 /* translators: %s: Error message. */ 419 __( 'Unable to encode the personal data for export. Error: %s' ), 420 json_last_error_msg() 421 ); 422 423 wp_send_json_error( $error_message ); 424 } 425 426 /* 427 * Handle the JSON export. 428 */ 429 $file = fopen( $json_report_pathname, 'w' ); 430 431 if ( false === $file ) { 432 wp_send_json_error( __( 'Unable to open personal data export file (JSON report) for writing.' ) ); 433 } 434 435 fwrite( $file, '{' ); 436 fwrite( $file, '"' . $title . '":' ); 437 fwrite( $file, $groups_json ); 438 fwrite( $file, '}' ); 439 fclose( $file ); 440 441 /* 442 * Handle the HTML export. 443 */ 444 $file = fopen( $html_report_pathname, 'w' ); 445 446 if ( false === $file ) { 447 wp_send_json_error( __( 'Unable to open personal data export (HTML report) for writing.' ) ); 448 } 449 450 fwrite( $file, "<!DOCTYPE html>\n" ); 451 fwrite( $file, "<html>\n" ); 452 fwrite( $file, "<head>\n" ); 453 fwrite( $file, "<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />\n" ); 454 fwrite( $file, "<style type='text/css'>" ); 455 fwrite( $file, 'body { color: black; font-family: Arial, sans-serif; font-size: 11pt; margin: 15px auto; width: 860px; }' ); 456 fwrite( $file, 'table { background: #f0f0f0; border: 1px solid #ddd; margin-bottom: 20px; width: 100%; }' ); 457 fwrite( $file, 'th { padding: 5px; text-align: left; width: 20%; }' ); 458 fwrite( $file, 'td { padding: 5px; }' ); 459 fwrite( $file, 'tr:nth-child(odd) { background-color: #fafafa; }' ); 460 fwrite( $file, '.return-to-top { text-align: right; }' ); 461 fwrite( $file, '</style>' ); 462 fwrite( $file, '<title>' ); 463 fwrite( $file, esc_html( $title ) ); 464 fwrite( $file, '</title>' ); 465 fwrite( $file, "</head>\n" ); 466 fwrite( $file, "<body>\n" ); 467 fwrite( $file, '<h1 id="top">' . esc_html__( 'Personal Data Export' ) . '</h1>' ); 468 469 // Create TOC. 470 if ( $groups_count > 1 ) { 471 fwrite( $file, '<div id="table_of_contents">' ); 472 fwrite( $file, '<h2>' . esc_html__( 'Table of Contents' ) . '</h2>' ); 473 fwrite( $file, '<ul>' ); 474 foreach ( (array) $groups as $group_id => $group_data ) { 475 $group_label = esc_html( $group_data['group_label'] ); 476 $group_id_attr = sanitize_title_with_dashes( $group_data['group_label'] . '-' . $group_id ); 477 $group_items_count = count( (array) $group_data['items'] ); 478 if ( $group_items_count > 1 ) { 479 $group_label .= sprintf( ' <span class="count">(%d)</span>', $group_items_count ); 480 } 481 fwrite( $file, '<li>' ); 482 fwrite( $file, '<a href="#' . esc_attr( $group_id_attr ) . '">' . $group_label . '</a>' ); 483 fwrite( $file, '</li>' ); 484 } 485 fwrite( $file, '</ul>' ); 486 fwrite( $file, '</div>' ); 487 } 488 489 // Now, iterate over every group in $groups and have the formatter render it in HTML. 490 foreach ( (array) $groups as $group_id => $group_data ) { 491 fwrite( $file, wp_privacy_generate_personal_data_export_group_html( $group_data, $group_id, $groups_count ) ); 492 } 493 494 fwrite( $file, "</body>\n" ); 495 fwrite( $file, "</html>\n" ); 496 fclose( $file ); 497 498 /* 499 * Now, generate the ZIP. 500 * 501 * If an archive has already been generated, then remove it and reuse the filename, 502 * to avoid breaking any URLs that may have been previously sent via email. 503 */ 504 $error = false; 505 506 // This meta value is used from version 5.5. 507 $archive_filename = get_post_meta( $request_id, '_export_file_name', true ); 508 509 // This one stored an absolute path and is used for backward compatibility. 510 $archive_pathname = get_post_meta( $request_id, '_export_file_path', true ); 511 512 // If a filename meta exists, use it. 513 if ( ! empty( $archive_filename ) ) { 514 $archive_pathname = $exports_dir . $archive_filename; 515 } elseif ( ! empty( $archive_pathname ) ) { 516 // If a full path meta exists, use it and create the new meta value. 517 $archive_filename = basename( $archive_pathname ); 518 519 update_post_meta( $request_id, '_export_file_name', $archive_filename ); 520 521 // Remove the back-compat meta values. 522 delete_post_meta( $request_id, '_export_file_url' ); 523 delete_post_meta( $request_id, '_export_file_path' ); 524 } else { 525 // If there's no filename or full path stored, create a new file. 526 $archive_filename = $file_basename . '.zip'; 527 $archive_pathname = $exports_dir . $archive_filename; 528 529 update_post_meta( $request_id, '_export_file_name', $archive_filename ); 530 } 531 532 $archive_url = $exports_url . $archive_filename; 533 534 if ( ! empty( $archive_pathname ) && file_exists( $archive_pathname ) ) { 535 wp_delete_file( $archive_pathname ); 536 } 537 538 $zip = new ZipArchive; 539 if ( true === $zip->open( $archive_pathname, ZipArchive::CREATE ) ) { 540 if ( ! $zip->addFile( $json_report_pathname, 'export.json' ) ) { 541 $error = __( 'Unable to archive the personal data export file (JSON format).' ); 542 } 543 544 if ( ! $zip->addFile( $html_report_pathname, 'index.html' ) ) { 545 $error = __( 'Unable to archive the personal data export file (HTML format).' ); 546 } 547 548 $zip->close(); 549 550 if ( ! $error ) { 551 /** 552 * Fires right after all personal data has been written to the export file. 553 * 554 * @since 4.9.6 555 * @since 5.4.0 Added the `$json_report_pathname` parameter. 556 * 557 * @param string $archive_pathname The full path to the export file on the filesystem. 558 * @param string $archive_url The URL of the archive file. 559 * @param string $html_report_pathname The full path to the HTML personal data report on the filesystem. 560 * @param int $request_id The export request ID. 561 * @param string $json_report_pathname The full path to the JSON personal data report on the filesystem. 562 */ 563 do_action( 'wp_privacy_personal_data_export_file_created', $archive_pathname, $archive_url, $html_report_pathname, $request_id, $json_report_pathname ); 564 } 565 } else { 566 $error = __( 'Unable to open personal data export file (archive) for writing.' ); 567 } 568 569 // Remove the JSON file. 570 unlink( $json_report_pathname ); 571 572 // Remove the HTML file. 573 unlink( $html_report_pathname ); 574 575 if ( $error ) { 576 wp_send_json_error( $error ); 577 } 578 } 579 580 /** 581 * Send an email to the user with a link to the personal data export file 582 * 583 * @since 4.9.6 584 * 585 * @param int $request_id The request ID for this personal data export. 586 * @return true|WP_Error True on success or `WP_Error` on failure. 587 */ 588 function wp_privacy_send_personal_data_export_email( $request_id ) { 589 // Get the request. 590 $request = wp_get_user_request( $request_id ); 591 592 if ( ! $request || 'export_personal_data' !== $request->action_name ) { 593 return new WP_Error( 'invalid_request', __( 'Invalid request ID when sending personal data export email.' ) ); 594 } 595 596 // Localize message content for user; fallback to site default for visitors. 597 if ( ! empty( $request->user_id ) ) { 598 $locale = get_user_locale( $request->user_id ); 599 } else { 600 $locale = get_locale(); 601 } 602 603 $switched_locale = switch_to_locale( $locale ); 604 605 /** This filter is documented in wp-includes/functions.php */ 606 $expiration = apply_filters( 'wp_privacy_export_expiration', 3 * DAY_IN_SECONDS ); 607 $expiration_date = date_i18n( get_option( 'date_format' ), time() + $expiration ); 608 609 $exports_url = wp_privacy_exports_url(); 610 $export_file_name = get_post_meta( $request_id, '_export_file_name', true ); 611 $export_file_url = $exports_url . $export_file_name; 612 613 $site_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); 614 $site_url = home_url(); 615 616 /** 617 * Filters the recipient of the personal data export email notification. 618 * Should be used with great caution to avoid sending the data export link to wrong emails. 619 * 620 * @since 5.3.0 621 * 622 * @param string $request_email The email address of the notification recipient. 623 * @param WP_User_Request $request The request that is initiating the notification. 624 */ 625 $request_email = apply_filters( 'wp_privacy_personal_data_email_to', $request->email, $request ); 626 627 $email_data = array( 628 'request' => $request, 629 'expiration' => $expiration, 630 'expiration_date' => $expiration_date, 631 'message_recipient' => $request_email, 632 'export_file_url' => $export_file_url, 633 'sitename' => $site_name, 634 'siteurl' => $site_url, 635 ); 636 637 /* translators: Personal data export notification email subject. %s: Site title. */ 638 $subject = sprintf( __( '[%s] Personal Data Export' ), $site_name ); 639 640 /** 641 * Filters the subject of the email sent when an export request is completed. 642 * 643 * @since 5.3.0 644 * 645 * @param string $subject The email subject. 646 * @param string $sitename The name of the site. 647 * @param array $email_data { 648 * Data relating to the account action email. 649 * 650 * @type WP_User_Request $request User request object. 651 * @type int $expiration The time in seconds until the export file expires. 652 * @type string $expiration_date The localized date and time when the export file expires. 653 * @type string $message_recipient The address that the email will be sent to. Defaults 654 * to the value of `$request->email`, but can be changed 655 * by the `wp_privacy_personal_data_email_to` filter. 656 * @type string $export_file_url The export file URL. 657 * @type string $sitename The site name sending the mail. 658 * @type string $siteurl The site URL sending the mail. 659 * } 660 */ 661 $subject = apply_filters( 'wp_privacy_personal_data_email_subject', $subject, $site_name, $email_data ); 662 663 /* translators: Do not translate EXPIRATION, LINK, SITENAME, SITEURL: those are placeholders. */ 664 $email_text = __( 665 'Howdy, 666 667 Your request for an export of personal data has been completed. You may 668 download your personal data by clicking on the link below. For privacy 669 and security, we will automatically delete the file on ###EXPIRATION###, 670 so please download it before then. 671 672 ###LINK### 673 674 Regards, 675 All at ###SITENAME### 676 ###SITEURL###' 677 ); 678 679 /** 680 * Filters the text of the email sent with a personal data export file. 681 * 682 * The following strings have a special meaning and will get replaced dynamically: 683 * ###EXPIRATION### The date when the URL will be automatically deleted. 684 * ###LINK### URL of the personal data export file for the user. 685 * ###SITENAME### The name of the site. 686 * ###SITEURL### The URL to the site. 687 * 688 * @since 4.9.6 689 * @since 5.3.0 Introduced the `$email_data` array. 690 * 691 * @param string $email_text Text in the email. 692 * @param int $request_id The request ID for this personal data export. 693 * @param array $email_data { 694 * Data relating to the account action email. 695 * 696 * @type WP_User_Request $request User request object. 697 * @type int $expiration The time in seconds until the export file expires. 698 * @type string $expiration_date The localized date and time when the export file expires. 699 * @type string $message_recipient The address that the email will be sent to. Defaults 700 * to the value of `$request->email`, but can be changed 701 * by the `wp_privacy_personal_data_email_to` filter. 702 * @type string $export_file_url The export file URL. 703 * @type string $sitename The site name sending the mail. 704 * @type string $siteurl The site URL sending the mail. 705 */ 706 $content = apply_filters( 'wp_privacy_personal_data_email_content', $email_text, $request_id, $email_data ); 707 708 $content = str_replace( '###EXPIRATION###', $expiration_date, $content ); 709 $content = str_replace( '###LINK###', esc_url_raw( $export_file_url ), $content ); 710 $content = str_replace( '###EMAIL###', $request_email, $content ); 711 $content = str_replace( '###SITENAME###', $site_name, $content ); 712 $content = str_replace( '###SITEURL###', esc_url_raw( $site_url ), $content ); 713 714 $headers = ''; 715 716 /** 717 * Filters the headers of the email sent with a personal data export file. 718 * 719 * @since 5.4.0 720 * 721 * @param string|array $headers The email headers. 722 * @param string $subject The email subject. 723 * @param string $content The email content. 724 * @param int $request_id The request ID. 725 * @param array $email_data { 726 * Data relating to the account action email. 727 * 728 * @type WP_User_Request $request User request object. 729 * @type int $expiration The time in seconds until the export file expires. 730 * @type string $expiration_date The localized date and time when the export file expires. 731 * @type string $message_recipient The address that the email will be sent to. Defaults 732 * to the value of `$request->email`, but can be changed 733 * by the `wp_privacy_personal_data_email_to` filter. 734 * @type string $export_file_url The export file URL. 735 * @type string $sitename The site name sending the mail. 736 * @type string $siteurl The site URL sending the mail. 737 * } 738 */ 739 $headers = apply_filters( 'wp_privacy_personal_data_email_headers', $headers, $subject, $content, $request_id, $email_data ); 740 741 $mail_success = wp_mail( $request_email, $subject, $content, $headers ); 742 743 if ( $switched_locale ) { 744 restore_previous_locale(); 745 } 746 747 if ( ! $mail_success ) { 748 return new WP_Error( 'privacy_email_error', __( 'Unable to send personal data export email.' ) ); 749 } 750 751 return true; 752 } 753 754 /** 755 * Intercept personal data exporter page Ajax responses in order to assemble the personal data export file. 756 * 757 * @since 4.9.6 758 * 759 * @see 'wp_privacy_personal_data_export_page' 760 * 761 * @param array $response The response from the personal data exporter for the given page. 762 * @param int $exporter_index The index of the personal data exporter. Begins at 1. 763 * @param string $email_address The email address of the user whose personal data this is. 764 * @param int $page The page of personal data for this exporter. Begins at 1. 765 * @param int $request_id The request ID for this personal data export. 766 * @param bool $send_as_email Whether the final results of the export should be emailed to the user. 767 * @param string $exporter_key The slug (key) of the exporter. 768 * @return array The filtered response. 769 */ 770 function wp_privacy_process_personal_data_export_page( $response, $exporter_index, $email_address, $page, $request_id, $send_as_email, $exporter_key ) { 771 /* Do some simple checks on the shape of the response from the exporter. 772 * If the exporter response is malformed, don't attempt to consume it - let it 773 * pass through to generate a warning to the user by default Ajax processing. 774 */ 775 if ( ! is_array( $response ) ) { 776 return $response; 777 } 778 779 if ( ! array_key_exists( 'done', $response ) ) { 780 return $response; 781 } 782 783 if ( ! array_key_exists( 'data', $response ) ) { 784 return $response; 785 } 786 787 if ( ! is_array( $response['data'] ) ) { 788 return $response; 789 } 790 791 // Get the request. 792 $request = wp_get_user_request( $request_id ); 793 794 if ( ! $request || 'export_personal_data' !== $request->action_name ) { 795 wp_send_json_error( __( 'Invalid request ID when merging personal data to export.' ) ); 796 } 797 798 $export_data = array(); 799 800 // First exporter, first page? Reset the report data accumulation array. 801 if ( 1 === $exporter_index && 1 === $page ) { 802 update_post_meta( $request_id, '_export_data_raw', $export_data ); 803 } else { 804 $accumulated_data = get_post_meta( $request_id, '_export_data_raw', true ); 805 806 if ( $accumulated_data ) { 807 $export_data = $accumulated_data; 808 } 809 } 810 811 // Now, merge the data from the exporter response into the data we have accumulated already. 812 $export_data = array_merge( $export_data, $response['data'] ); 813 update_post_meta( $request_id, '_export_data_raw', $export_data ); 814 815 // If we are not yet on the last page of the last exporter, return now. 816 /** This filter is documented in wp-admin/includes/ajax-actions.php */ 817 $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() ); 818 $is_last_exporter = count( $exporters ) === $exporter_index; 819 $exporter_done = $response['done']; 820 if ( ! $is_last_exporter || ! $exporter_done ) { 821 return $response; 822 } 823 824 // Last exporter, last page - let's prepare the export file. 825 826 // First we need to re-organize the raw data hierarchically in groups and items. 827 $groups = array(); 828 foreach ( (array) $export_data as $export_datum ) { 829 $group_id = $export_datum['group_id']; 830 $group_label = $export_datum['group_label']; 831 832 $group_description = ''; 833 if ( ! empty( $export_datum['group_description'] ) ) { 834 $group_description = $export_datum['group_description']; 835 } 836 837 if ( ! array_key_exists( $group_id, $groups ) ) { 838 $groups[ $group_id ] = array( 839 'group_label' => $group_label, 840 'group_description' => $group_description, 841 'items' => array(), 842 ); 843 } 844 845 $item_id = $export_datum['item_id']; 846 if ( ! array_key_exists( $item_id, $groups[ $group_id ]['items'] ) ) { 847 $groups[ $group_id ]['items'][ $item_id ] = array(); 848 } 849 850 $old_item_data = $groups[ $group_id ]['items'][ $item_id ]; 851 $merged_item_data = array_merge( $export_datum['data'], $old_item_data ); 852 $groups[ $group_id ]['items'][ $item_id ] = $merged_item_data; 853 } 854 855 // Then save the grouped data into the request. 856 delete_post_meta( $request_id, '_export_data_raw' ); 857 update_post_meta( $request_id, '_export_data_grouped', $groups ); 858 859 /** 860 * Generate the export file from the collected, grouped personal data. 861 * 862 * @since 4.9.6 863 * 864 * @param int $request_id The export request ID. 865 */ 866 do_action( 'wp_privacy_personal_data_export_file', $request_id ); 867 868 // Clear the grouped data now that it is no longer needed. 869 delete_post_meta( $request_id, '_export_data_grouped' ); 870 871 // If the destination is email, send it now. 872 if ( $send_as_email ) { 873 $mail_success = wp_privacy_send_personal_data_export_email( $request_id ); 874 if ( is_wp_error( $mail_success ) ) { 875 wp_send_json_error( $mail_success->get_error_message() ); 876 } 877 878 // Update the request to completed state when the export email is sent. 879 _wp_privacy_completed_request( $request_id ); 880 } else { 881 // Modify the response to include the URL of the export file so the browser can fetch it. 882 $exports_url = wp_privacy_exports_url(); 883 $export_file_name = get_post_meta( $request_id, '_export_file_name', true ); 884 $export_file_url = $exports_url . $export_file_name; 885 886 if ( ! empty( $export_file_url ) ) { 887 $response['url'] = $export_file_url; 888 } 889 } 890 891 return $response; 892 } 893 894 /** 895 * Mark erasure requests as completed after processing is finished. 896 * 897 * This intercepts the Ajax responses to personal data eraser page requests, and 898 * monitors the status of a request. Once all of the processing has finished, the 899 * request is marked as completed. 900 * 901 * @since 4.9.6 902 * 903 * @see 'wp_privacy_personal_data_erasure_page' 904 * 905 * @param array $response The response from the personal data eraser for 906 * the given page. 907 * @param int $eraser_index The index of the personal data eraser. Begins 908 * at 1. 909 * @param string $email_address The email address of the user whose personal 910 * data this is. 911 * @param int $page The page of personal data for this eraser. 912 * Begins at 1. 913 * @param int $request_id The request ID for this personal data erasure. 914 * @return array The filtered response. 915 */ 916 function wp_privacy_process_personal_data_erasure_page( $response, $eraser_index, $email_address, $page, $request_id ) { 917 /* 918 * If the eraser response is malformed, don't attempt to consume it; let it 919 * pass through, so that the default Ajax processing will generate a warning 920 * to the user. 921 */ 922 if ( ! is_array( $response ) ) { 923 return $response; 924 } 925 926 if ( ! array_key_exists( 'done', $response ) ) { 927 return $response; 928 } 929 930 if ( ! array_key_exists( 'items_removed', $response ) ) { 931 return $response; 932 } 933 934 if ( ! array_key_exists( 'items_retained', $response ) ) { 935 return $response; 936 } 937 938 if ( ! array_key_exists( 'messages', $response ) ) { 939 return $response; 940 } 941 942 // Get the request. 943 $request = wp_get_user_request( $request_id ); 944 945 if ( ! $request || 'remove_personal_data' !== $request->action_name ) { 946 wp_send_json_error( __( 'Invalid request ID when processing personal data to erase.' ) ); 947 } 948 949 /** This filter is documented in wp-admin/includes/ajax-actions.php */ 950 $erasers = apply_filters( 'wp_privacy_personal_data_erasers', array() ); 951 $is_last_eraser = count( $erasers ) === $eraser_index; 952 $eraser_done = $response['done']; 953 954 if ( ! $is_last_eraser || ! $eraser_done ) { 955 return $response; 956 } 957 958 _wp_privacy_completed_request( $request_id ); 959 960 /** 961 * Fires immediately after a personal data erasure request has been marked completed. 962 * 963 * @since 4.9.6 964 * 965 * @param int $request_id The privacy request post ID associated with this request. 966 */ 967 do_action( 'wp_privacy_personal_data_erased', $request_id ); 968 969 return $response; 970 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Tue Dec 24 01:00:02 2024 | Cross-referenced by PHPXref 0.7.1 |