[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[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 ( ! str_contains( $value, ' ' ) && ( str_starts_with( $value, 'http://' ) || str_starts_with( $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 $switched_locale = switch_to_user_locale( $request->user_id ); 599 } else { 600 $switched_locale = switch_to_locale( get_locale() ); 601 } 602 603 /** This filter is documented in wp-includes/functions.php */ 604 $expiration = apply_filters( 'wp_privacy_export_expiration', 3 * DAY_IN_SECONDS ); 605 $expiration_date = date_i18n( get_option( 'date_format' ), time() + $expiration ); 606 607 $exports_url = wp_privacy_exports_url(); 608 $export_file_name = get_post_meta( $request_id, '_export_file_name', true ); 609 $export_file_url = $exports_url . $export_file_name; 610 611 $site_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); 612 $site_url = home_url(); 613 614 /** 615 * Filters the recipient of the personal data export email notification. 616 * Should be used with great caution to avoid sending the data export link to wrong emails. 617 * 618 * @since 5.3.0 619 * 620 * @param string $request_email The email address of the notification recipient. 621 * @param WP_User_Request $request The request that is initiating the notification. 622 */ 623 $request_email = apply_filters( 'wp_privacy_personal_data_email_to', $request->email, $request ); 624 625 $email_data = array( 626 'request' => $request, 627 'expiration' => $expiration, 628 'expiration_date' => $expiration_date, 629 'message_recipient' => $request_email, 630 'export_file_url' => $export_file_url, 631 'sitename' => $site_name, 632 'siteurl' => $site_url, 633 ); 634 635 /* translators: Personal data export notification email subject. %s: Site title. */ 636 $subject = sprintf( __( '[%s] Personal Data Export' ), $site_name ); 637 638 /** 639 * Filters the subject of the email sent when an export request is completed. 640 * 641 * @since 5.3.0 642 * 643 * @param string $subject The email subject. 644 * @param string $sitename The name of the site. 645 * @param array $email_data { 646 * Data relating to the account action email. 647 * 648 * @type WP_User_Request $request User request object. 649 * @type int $expiration The time in seconds until the export file expires. 650 * @type string $expiration_date The localized date and time when the export file expires. 651 * @type string $message_recipient The address that the email will be sent to. Defaults 652 * to the value of `$request->email`, but can be changed 653 * by the `wp_privacy_personal_data_email_to` filter. 654 * @type string $export_file_url The export file URL. 655 * @type string $sitename The site name sending the mail. 656 * @type string $siteurl The site URL sending the mail. 657 * } 658 */ 659 $subject = apply_filters( 'wp_privacy_personal_data_email_subject', $subject, $site_name, $email_data ); 660 661 /* translators: Do not translate EXPIRATION, LINK, SITENAME, SITEURL: those are placeholders. */ 662 $email_text = __( 663 'Howdy, 664 665 Your request for an export of personal data has been completed. You may 666 download your personal data by clicking on the link below. For privacy 667 and security, we will automatically delete the file on ###EXPIRATION###, 668 so please download it before then. 669 670 ###LINK### 671 672 Regards, 673 All at ###SITENAME### 674 ###SITEURL###' 675 ); 676 677 /** 678 * Filters the text of the email sent with a personal data export file. 679 * 680 * The following strings have a special meaning and will get replaced dynamically: 681 * ###EXPIRATION### The date when the URL will be automatically deleted. 682 * ###LINK### URL of the personal data export file for the user. 683 * ###SITENAME### The name of the site. 684 * ###SITEURL### The URL to the site. 685 * 686 * @since 4.9.6 687 * @since 5.3.0 Introduced the `$email_data` array. 688 * 689 * @param string $email_text Text in the email. 690 * @param int $request_id The request ID for this personal data export. 691 * @param array $email_data { 692 * Data relating to the account action email. 693 * 694 * @type WP_User_Request $request User request object. 695 * @type int $expiration The time in seconds until the export file expires. 696 * @type string $expiration_date The localized date and time when the export file expires. 697 * @type string $message_recipient The address that the email will be sent to. Defaults 698 * to the value of `$request->email`, but can be changed 699 * by the `wp_privacy_personal_data_email_to` filter. 700 * @type string $export_file_url The export file URL. 701 * @type string $sitename The site name sending the mail. 702 * @type string $siteurl The site URL sending the mail. 703 */ 704 $content = apply_filters( 'wp_privacy_personal_data_email_content', $email_text, $request_id, $email_data ); 705 706 $content = str_replace( '###EXPIRATION###', $expiration_date, $content ); 707 $content = str_replace( '###LINK###', sanitize_url( $export_file_url ), $content ); 708 $content = str_replace( '###EMAIL###', $request_email, $content ); 709 $content = str_replace( '###SITENAME###', $site_name, $content ); 710 $content = str_replace( '###SITEURL###', sanitize_url( $site_url ), $content ); 711 712 $headers = ''; 713 714 /** 715 * Filters the headers of the email sent with a personal data export file. 716 * 717 * @since 5.4.0 718 * 719 * @param string|array $headers The email headers. 720 * @param string $subject The email subject. 721 * @param string $content The email content. 722 * @param int $request_id The request ID. 723 * @param array $email_data { 724 * Data relating to the account action email. 725 * 726 * @type WP_User_Request $request User request object. 727 * @type int $expiration The time in seconds until the export file expires. 728 * @type string $expiration_date The localized date and time when the export file expires. 729 * @type string $message_recipient The address that the email will be sent to. Defaults 730 * to the value of `$request->email`, but can be changed 731 * by the `wp_privacy_personal_data_email_to` filter. 732 * @type string $export_file_url The export file URL. 733 * @type string $sitename The site name sending the mail. 734 * @type string $siteurl The site URL sending the mail. 735 * } 736 */ 737 $headers = apply_filters( 'wp_privacy_personal_data_email_headers', $headers, $subject, $content, $request_id, $email_data ); 738 739 $mail_success = wp_mail( $request_email, $subject, $content, $headers ); 740 741 if ( $switched_locale ) { 742 restore_previous_locale(); 743 } 744 745 if ( ! $mail_success ) { 746 return new WP_Error( 'privacy_email_error', __( 'Unable to send personal data export email.' ) ); 747 } 748 749 return true; 750 } 751 752 /** 753 * Intercept personal data exporter page Ajax responses in order to assemble the personal data export file. 754 * 755 * @since 4.9.6 756 * 757 * @see 'wp_privacy_personal_data_export_page' 758 * 759 * @param array $response The response from the personal data exporter for the given page. 760 * @param int $exporter_index The index of the personal data exporter. Begins at 1. 761 * @param string $email_address The email address of the user whose personal data this is. 762 * @param int $page The page of personal data for this exporter. Begins at 1. 763 * @param int $request_id The request ID for this personal data export. 764 * @param bool $send_as_email Whether the final results of the export should be emailed to the user. 765 * @param string $exporter_key The slug (key) of the exporter. 766 * @return array The filtered response. 767 */ 768 function wp_privacy_process_personal_data_export_page( $response, $exporter_index, $email_address, $page, $request_id, $send_as_email, $exporter_key ) { 769 /* Do some simple checks on the shape of the response from the exporter. 770 * If the exporter response is malformed, don't attempt to consume it - let it 771 * pass through to generate a warning to the user by default Ajax processing. 772 */ 773 if ( ! is_array( $response ) ) { 774 return $response; 775 } 776 777 if ( ! array_key_exists( 'done', $response ) ) { 778 return $response; 779 } 780 781 if ( ! array_key_exists( 'data', $response ) ) { 782 return $response; 783 } 784 785 if ( ! is_array( $response['data'] ) ) { 786 return $response; 787 } 788 789 // Get the request. 790 $request = wp_get_user_request( $request_id ); 791 792 if ( ! $request || 'export_personal_data' !== $request->action_name ) { 793 wp_send_json_error( __( 'Invalid request ID when merging personal data to export.' ) ); 794 } 795 796 $export_data = array(); 797 798 // First exporter, first page? Reset the report data accumulation array. 799 if ( 1 === $exporter_index && 1 === $page ) { 800 update_post_meta( $request_id, '_export_data_raw', $export_data ); 801 } else { 802 $accumulated_data = get_post_meta( $request_id, '_export_data_raw', true ); 803 804 if ( $accumulated_data ) { 805 $export_data = $accumulated_data; 806 } 807 } 808 809 // Now, merge the data from the exporter response into the data we have accumulated already. 810 $export_data = array_merge( $export_data, $response['data'] ); 811 update_post_meta( $request_id, '_export_data_raw', $export_data ); 812 813 // If we are not yet on the last page of the last exporter, return now. 814 /** This filter is documented in wp-admin/includes/ajax-actions.php */ 815 $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() ); 816 $is_last_exporter = count( $exporters ) === $exporter_index; 817 $exporter_done = $response['done']; 818 if ( ! $is_last_exporter || ! $exporter_done ) { 819 return $response; 820 } 821 822 // Last exporter, last page - let's prepare the export file. 823 824 // First we need to re-organize the raw data hierarchically in groups and items. 825 $groups = array(); 826 foreach ( (array) $export_data as $export_datum ) { 827 $group_id = $export_datum['group_id']; 828 $group_label = $export_datum['group_label']; 829 830 $group_description = ''; 831 if ( ! empty( $export_datum['group_description'] ) ) { 832 $group_description = $export_datum['group_description']; 833 } 834 835 if ( ! array_key_exists( $group_id, $groups ) ) { 836 $groups[ $group_id ] = array( 837 'group_label' => $group_label, 838 'group_description' => $group_description, 839 'items' => array(), 840 ); 841 } 842 843 $item_id = $export_datum['item_id']; 844 if ( ! array_key_exists( $item_id, $groups[ $group_id ]['items'] ) ) { 845 $groups[ $group_id ]['items'][ $item_id ] = array(); 846 } 847 848 $old_item_data = $groups[ $group_id ]['items'][ $item_id ]; 849 $merged_item_data = array_merge( $export_datum['data'], $old_item_data ); 850 $groups[ $group_id ]['items'][ $item_id ] = $merged_item_data; 851 } 852 853 // Then save the grouped data into the request. 854 delete_post_meta( $request_id, '_export_data_raw' ); 855 update_post_meta( $request_id, '_export_data_grouped', $groups ); 856 857 /** 858 * Generate the export file from the collected, grouped personal data. 859 * 860 * @since 4.9.6 861 * 862 * @param int $request_id The export request ID. 863 */ 864 do_action( 'wp_privacy_personal_data_export_file', $request_id ); 865 866 // Clear the grouped data now that it is no longer needed. 867 delete_post_meta( $request_id, '_export_data_grouped' ); 868 869 // If the destination is email, send it now. 870 if ( $send_as_email ) { 871 $mail_success = wp_privacy_send_personal_data_export_email( $request_id ); 872 if ( is_wp_error( $mail_success ) ) { 873 wp_send_json_error( $mail_success->get_error_message() ); 874 } 875 876 // Update the request to completed state when the export email is sent. 877 _wp_privacy_completed_request( $request_id ); 878 } else { 879 // Modify the response to include the URL of the export file so the browser can fetch it. 880 $exports_url = wp_privacy_exports_url(); 881 $export_file_name = get_post_meta( $request_id, '_export_file_name', true ); 882 $export_file_url = $exports_url . $export_file_name; 883 884 if ( ! empty( $export_file_url ) ) { 885 $response['url'] = $export_file_url; 886 } 887 } 888 889 return $response; 890 } 891 892 /** 893 * Mark erasure requests as completed after processing is finished. 894 * 895 * This intercepts the Ajax responses to personal data eraser page requests, and 896 * monitors the status of a request. Once all of the processing has finished, the 897 * request is marked as completed. 898 * 899 * @since 4.9.6 900 * 901 * @see 'wp_privacy_personal_data_erasure_page' 902 * 903 * @param array $response The response from the personal data eraser for 904 * the given page. 905 * @param int $eraser_index The index of the personal data eraser. Begins 906 * at 1. 907 * @param string $email_address The email address of the user whose personal 908 * data this is. 909 * @param int $page The page of personal data for this eraser. 910 * Begins at 1. 911 * @param int $request_id The request ID for this personal data erasure. 912 * @return array The filtered response. 913 */ 914 function wp_privacy_process_personal_data_erasure_page( $response, $eraser_index, $email_address, $page, $request_id ) { 915 /* 916 * If the eraser response is malformed, don't attempt to consume it; let it 917 * pass through, so that the default Ajax processing will generate a warning 918 * to the user. 919 */ 920 if ( ! is_array( $response ) ) { 921 return $response; 922 } 923 924 if ( ! array_key_exists( 'done', $response ) ) { 925 return $response; 926 } 927 928 if ( ! array_key_exists( 'items_removed', $response ) ) { 929 return $response; 930 } 931 932 if ( ! array_key_exists( 'items_retained', $response ) ) { 933 return $response; 934 } 935 936 if ( ! array_key_exists( 'messages', $response ) ) { 937 return $response; 938 } 939 940 // Get the request. 941 $request = wp_get_user_request( $request_id ); 942 943 if ( ! $request || 'remove_personal_data' !== $request->action_name ) { 944 wp_send_json_error( __( 'Invalid request ID when processing personal data to erase.' ) ); 945 } 946 947 /** This filter is documented in wp-admin/includes/ajax-actions.php */ 948 $erasers = apply_filters( 'wp_privacy_personal_data_erasers', array() ); 949 $is_last_eraser = count( $erasers ) === $eraser_index; 950 $eraser_done = $response['done']; 951 952 if ( ! $is_last_eraser || ! $eraser_done ) { 953 return $response; 954 } 955 956 _wp_privacy_completed_request( $request_id ); 957 958 /** 959 * Fires immediately after a personal data erasure request has been marked completed. 960 * 961 * @since 4.9.6 962 * 963 * @param int $request_id The privacy request post ID associated with this request. 964 */ 965 do_action( 'wp_privacy_personal_data_erased', $request_id ); 966 967 return $response; 968 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Thu Nov 21 08:20:01 2024 | Cross-referenced by PHPXref |