[ 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 ( 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 // And now, all the Groups. 366 $groups = get_post_meta( $request_id, '_export_data_grouped', true ); 367 368 // First, build an "About" group on the fly for this report. 369 $about_group = array( 370 /* translators: Header for the About section in a personal data export. */ 371 'group_label' => _x( 'About', 'personal data group label' ), 372 /* translators: Description for the About section in a personal data export. */ 373 'group_description' => _x( 'Overview of export report.', 'personal data group description' ), 374 'items' => array( 375 'about-1' => array( 376 array( 377 'name' => _x( 'Report generated for', 'email address' ), 378 'value' => $email_address, 379 ), 380 array( 381 'name' => _x( 'For site', 'website name' ), 382 'value' => get_bloginfo( 'name' ), 383 ), 384 array( 385 'name' => _x( 'At URL', 'website URL' ), 386 'value' => get_bloginfo( 'url' ), 387 ), 388 array( 389 'name' => _x( 'On', 'date/time' ), 390 'value' => current_time( 'mysql' ), 391 ), 392 ), 393 ), 394 ); 395 396 // Merge in the special about group. 397 $groups = array_merge( array( 'about' => $about_group ), $groups ); 398 399 $groups_count = count( $groups ); 400 401 // Convert the groups to JSON format. 402 $groups_json = wp_json_encode( $groups ); 403 404 /* 405 * Handle the JSON export. 406 */ 407 $file = fopen( $json_report_pathname, 'w' ); 408 409 if ( false === $file ) { 410 wp_send_json_error( __( 'Unable to open personal data export file (JSON report) for writing.' ) ); 411 } 412 413 fwrite( $file, '{' ); 414 fwrite( $file, '"' . $title . '":' ); 415 fwrite( $file, $groups_json ); 416 fwrite( $file, '}' ); 417 fclose( $file ); 418 419 /* 420 * Handle the HTML export. 421 */ 422 $file = fopen( $html_report_pathname, 'w' ); 423 424 if ( false === $file ) { 425 wp_send_json_error( __( 'Unable to open personal data export (HTML report) for writing.' ) ); 426 } 427 428 fwrite( $file, "<!DOCTYPE html>\n" ); 429 fwrite( $file, "<html>\n" ); 430 fwrite( $file, "<head>\n" ); 431 fwrite( $file, "<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />\n" ); 432 fwrite( $file, "<style type='text/css'>" ); 433 fwrite( $file, 'body { color: black; font-family: Arial, sans-serif; font-size: 11pt; margin: 15px auto; width: 860px; }' ); 434 fwrite( $file, 'table { background: #f0f0f0; border: 1px solid #ddd; margin-bottom: 20px; width: 100%; }' ); 435 fwrite( $file, 'th { padding: 5px; text-align: left; width: 20%; }' ); 436 fwrite( $file, 'td { padding: 5px; }' ); 437 fwrite( $file, 'tr:nth-child(odd) { background-color: #fafafa; }' ); 438 fwrite( $file, '.return-to-top { text-align: right; }' ); 439 fwrite( $file, '</style>' ); 440 fwrite( $file, '<title>' ); 441 fwrite( $file, esc_html( $title ) ); 442 fwrite( $file, '</title>' ); 443 fwrite( $file, "</head>\n" ); 444 fwrite( $file, "<body>\n" ); 445 fwrite( $file, '<h1 id="top">' . esc_html__( 'Personal Data Export' ) . '</h1>' ); 446 447 // Create TOC. 448 if ( $groups_count > 1 ) { 449 fwrite( $file, '<div id="table_of_contents">' ); 450 fwrite( $file, '<h2>' . esc_html__( 'Table of Contents' ) . '</h2>' ); 451 fwrite( $file, '<ul>' ); 452 foreach ( (array) $groups as $group_id => $group_data ) { 453 $group_label = esc_html( $group_data['group_label'] ); 454 $group_id_attr = sanitize_title_with_dashes( $group_data['group_label'] . '-' . $group_id ); 455 $group_items_count = count( (array) $group_data['items'] ); 456 if ( $group_items_count > 1 ) { 457 $group_label .= sprintf( ' <span class="count">(%d)</span>', $group_items_count ); 458 } 459 fwrite( $file, '<li>' ); 460 fwrite( $file, '<a href="#' . esc_attr( $group_id_attr ) . '">' . $group_label . '</a>' ); 461 fwrite( $file, '</li>' ); 462 } 463 fwrite( $file, '</ul>' ); 464 fwrite( $file, '</div>' ); 465 } 466 467 // Now, iterate over every group in $groups and have the formatter render it in HTML. 468 foreach ( (array) $groups as $group_id => $group_data ) { 469 fwrite( $file, wp_privacy_generate_personal_data_export_group_html( $group_data, $group_id, $groups_count ) ); 470 } 471 472 fwrite( $file, "</body>\n" ); 473 fwrite( $file, "</html>\n" ); 474 fclose( $file ); 475 476 /* 477 * Now, generate the ZIP. 478 * 479 * If an archive has already been generated, then remove it and reuse the filename, 480 * to avoid breaking any URLs that may have been previously sent via email. 481 */ 482 $error = false; 483 484 // This meta value is used from version 5.5. 485 $archive_filename = get_post_meta( $request_id, '_export_file_name', true ); 486 487 // This one stored an absolute path and is used for backward compatibility. 488 $archive_pathname = get_post_meta( $request_id, '_export_file_path', true ); 489 490 // If a filename meta exists, use it. 491 if ( ! empty( $archive_filename ) ) { 492 $archive_pathname = $exports_dir . $archive_filename; 493 } elseif ( ! empty( $archive_pathname ) ) { 494 // If a full path meta exists, use it and create the new meta value. 495 $archive_filename = basename( $archive_pathname ); 496 497 update_post_meta( $request_id, '_export_file_name', $archive_filename ); 498 499 // Remove the back-compat meta values. 500 delete_post_meta( $request_id, '_export_file_url' ); 501 delete_post_meta( $request_id, '_export_file_path' ); 502 } else { 503 // If there's no filename or full path stored, create a new file. 504 $archive_filename = $file_basename . '.zip'; 505 $archive_pathname = $exports_dir . $archive_filename; 506 507 update_post_meta( $request_id, '_export_file_name', $archive_filename ); 508 } 509 510 $archive_url = $exports_url . $archive_filename; 511 512 if ( ! empty( $archive_pathname ) && file_exists( $archive_pathname ) ) { 513 wp_delete_file( $archive_pathname ); 514 } 515 516 $zip = new ZipArchive; 517 if ( true === $zip->open( $archive_pathname, ZipArchive::CREATE ) ) { 518 if ( ! $zip->addFile( $json_report_pathname, 'export.json' ) ) { 519 $error = __( 'Unable to archive the personal data export file (JSON format).' ); 520 } 521 522 if ( ! $zip->addFile( $html_report_pathname, 'index.html' ) ) { 523 $error = __( 'Unable to archive the personal data export file (HTML format).' ); 524 } 525 526 $zip->close(); 527 528 if ( ! $error ) { 529 /** 530 * Fires right after all personal data has been written to the export file. 531 * 532 * @since 4.9.6 533 * @since 5.4.0 Added the `$json_report_pathname` parameter. 534 * 535 * @param string $archive_pathname The full path to the export file on the filesystem. 536 * @param string $archive_url The URL of the archive file. 537 * @param string $html_report_pathname The full path to the HTML personal data report on the filesystem. 538 * @param int $request_id The export request ID. 539 * @param string $json_report_pathname The full path to the JSON personal data report on the filesystem. 540 */ 541 do_action( 'wp_privacy_personal_data_export_file_created', $archive_pathname, $archive_url, $html_report_pathname, $request_id, $json_report_pathname ); 542 } 543 } else { 544 $error = __( 'Unable to open personal data export file (archive) for writing.' ); 545 } 546 547 // Remove the JSON file. 548 unlink( $json_report_pathname ); 549 550 // Remove the HTML file. 551 unlink( $html_report_pathname ); 552 553 if ( $error ) { 554 wp_send_json_error( $error ); 555 } 556 } 557 558 /** 559 * Send an email to the user with a link to the personal data export file 560 * 561 * @since 4.9.6 562 * 563 * @param int $request_id The request ID for this personal data export. 564 * @return true|WP_Error True on success or `WP_Error` on failure. 565 */ 566 function wp_privacy_send_personal_data_export_email( $request_id ) { 567 // Get the request. 568 $request = wp_get_user_request( $request_id ); 569 570 if ( ! $request || 'export_personal_data' !== $request->action_name ) { 571 return new WP_Error( 'invalid_request', __( 'Invalid request ID when sending personal data export email.' ) ); 572 } 573 574 // Localize message content for user; fallback to site default for visitors. 575 if ( ! empty( $request->user_id ) ) { 576 $locale = get_user_locale( $request->user_id ); 577 } else { 578 $locale = get_locale(); 579 } 580 581 $switched_locale = switch_to_locale( $locale ); 582 583 /** This filter is documented in wp-includes/functions.php */ 584 $expiration = apply_filters( 'wp_privacy_export_expiration', 3 * DAY_IN_SECONDS ); 585 $expiration_date = date_i18n( get_option( 'date_format' ), time() + $expiration ); 586 587 $exports_url = wp_privacy_exports_url(); 588 $export_file_name = get_post_meta( $request_id, '_export_file_name', true ); 589 $export_file_url = $exports_url . $export_file_name; 590 591 $site_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); 592 $site_url = home_url(); 593 594 /** 595 * Filters the recipient of the personal data export email notification. 596 * Should be used with great caution to avoid sending the data export link to wrong emails. 597 * 598 * @since 5.3.0 599 * 600 * @param string $request_email The email address of the notification recipient. 601 * @param WP_User_Request $request The request that is initiating the notification. 602 */ 603 $request_email = apply_filters( 'wp_privacy_personal_data_email_to', $request->email, $request ); 604 605 $email_data = array( 606 'request' => $request, 607 'expiration' => $expiration, 608 'expiration_date' => $expiration_date, 609 'message_recipient' => $request_email, 610 'export_file_url' => $export_file_url, 611 'sitename' => $site_name, 612 'siteurl' => $site_url, 613 ); 614 615 /* translators: Personal data export notification email subject. %s: Site title. */ 616 $subject = sprintf( __( '[%s] Personal Data Export' ), $site_name ); 617 618 /** 619 * Filters the subject of the email sent when an export request is completed. 620 * 621 * @since 5.3.0 622 * 623 * @param string $subject The email subject. 624 * @param string $sitename The name of the site. 625 * @param array $email_data { 626 * Data relating to the account action email. 627 * 628 * @type WP_User_Request $request User request object. 629 * @type int $expiration The time in seconds until the export file expires. 630 * @type string $expiration_date The localized date and time when the export file expires. 631 * @type string $message_recipient The address that the email will be sent to. Defaults 632 * to the value of `$request->email`, but can be changed 633 * by the `wp_privacy_personal_data_email_to` filter. 634 * @type string $export_file_url The export file URL. 635 * @type string $sitename The site name sending the mail. 636 * @type string $siteurl The site URL sending the mail. 637 * } 638 */ 639 $subject = apply_filters( 'wp_privacy_personal_data_email_subject', $subject, $site_name, $email_data ); 640 641 /* translators: Do not translate EXPIRATION, LINK, SITENAME, SITEURL: those are placeholders. */ 642 $email_text = __( 643 'Howdy, 644 645 Your request for an export of personal data has been completed. You may 646 download your personal data by clicking on the link below. For privacy 647 and security, we will automatically delete the file on ###EXPIRATION###, 648 so please download it before then. 649 650 ###LINK### 651 652 Regards, 653 All at ###SITENAME### 654 ###SITEURL###' 655 ); 656 657 /** 658 * Filters the text of the email sent with a personal data export file. 659 * 660 * The following strings have a special meaning and will get replaced dynamically: 661 * ###EXPIRATION### The date when the URL will be automatically deleted. 662 * ###LINK### URL of the personal data export file for the user. 663 * ###SITENAME### The name of the site. 664 * ###SITEURL### The URL to the site. 665 * 666 * @since 4.9.6 667 * @since 5.3.0 Introduced the `$email_data` array. 668 * 669 * @param string $email_text Text in the email. 670 * @param int $request_id The request ID for this personal data export. 671 * @param array $email_data { 672 * Data relating to the account action email. 673 * 674 * @type WP_User_Request $request User request object. 675 * @type int $expiration The time in seconds until the export file expires. 676 * @type string $expiration_date The localized date and time when the export file expires. 677 * @type string $message_recipient The address that the email will be sent to. Defaults 678 * to the value of `$request->email`, but can be changed 679 * by the `wp_privacy_personal_data_email_to` filter. 680 * @type string $export_file_url The export file URL. 681 * @type string $sitename The site name sending the mail. 682 * @type string $siteurl The site URL sending the mail. 683 */ 684 $content = apply_filters( 'wp_privacy_personal_data_email_content', $email_text, $request_id, $email_data ); 685 686 $content = str_replace( '###EXPIRATION###', $expiration_date, $content ); 687 $content = str_replace( '###LINK###', esc_url_raw( $export_file_url ), $content ); 688 $content = str_replace( '###EMAIL###', $request_email, $content ); 689 $content = str_replace( '###SITENAME###', $site_name, $content ); 690 $content = str_replace( '###SITEURL###', esc_url_raw( $site_url ), $content ); 691 692 $headers = ''; 693 694 /** 695 * Filters the headers of the email sent with a personal data export file. 696 * 697 * @since 5.4.0 698 * 699 * @param string|array $headers The email headers. 700 * @param string $subject The email subject. 701 * @param string $content The email content. 702 * @param int $request_id The request ID. 703 * @param array $email_data { 704 * Data relating to the account action email. 705 * 706 * @type WP_User_Request $request User request object. 707 * @type int $expiration The time in seconds until the export file expires. 708 * @type string $expiration_date The localized date and time when the export file expires. 709 * @type string $message_recipient The address that the email will be sent to. Defaults 710 * to the value of `$request->email`, but can be changed 711 * by the `wp_privacy_personal_data_email_to` filter. 712 * @type string $export_file_url The export file URL. 713 * @type string $sitename The site name sending the mail. 714 * @type string $siteurl The site URL sending the mail. 715 * } 716 */ 717 $headers = apply_filters( 'wp_privacy_personal_data_email_headers', $headers, $subject, $content, $request_id, $email_data ); 718 719 $mail_success = wp_mail( $request_email, $subject, $content, $headers ); 720 721 if ( $switched_locale ) { 722 restore_previous_locale(); 723 } 724 725 if ( ! $mail_success ) { 726 return new WP_Error( 'privacy_email_error', __( 'Unable to send personal data export email.' ) ); 727 } 728 729 return true; 730 } 731 732 /** 733 * Intercept personal data exporter page Ajax responses in order to assemble the personal data export file. 734 * 735 * @since 4.9.6 736 * 737 * @see 'wp_privacy_personal_data_export_page' 738 * 739 * @param array $response The response from the personal data exporter for the given page. 740 * @param int $exporter_index The index of the personal data exporter. Begins at 1. 741 * @param string $email_address The email address of the user whose personal data this is. 742 * @param int $page The page of personal data for this exporter. Begins at 1. 743 * @param int $request_id The request ID for this personal data export. 744 * @param bool $send_as_email Whether the final results of the export should be emailed to the user. 745 * @param string $exporter_key The slug (key) of the exporter. 746 * @return array The filtered response. 747 */ 748 function wp_privacy_process_personal_data_export_page( $response, $exporter_index, $email_address, $page, $request_id, $send_as_email, $exporter_key ) { 749 /* Do some simple checks on the shape of the response from the exporter. 750 * If the exporter response is malformed, don't attempt to consume it - let it 751 * pass through to generate a warning to the user by default Ajax processing. 752 */ 753 if ( ! is_array( $response ) ) { 754 return $response; 755 } 756 757 if ( ! array_key_exists( 'done', $response ) ) { 758 return $response; 759 } 760 761 if ( ! array_key_exists( 'data', $response ) ) { 762 return $response; 763 } 764 765 if ( ! is_array( $response['data'] ) ) { 766 return $response; 767 } 768 769 // Get the request. 770 $request = wp_get_user_request( $request_id ); 771 772 if ( ! $request || 'export_personal_data' !== $request->action_name ) { 773 wp_send_json_error( __( 'Invalid request ID when merging personal data to export.' ) ); 774 } 775 776 $export_data = array(); 777 778 // First exporter, first page? Reset the report data accumulation array. 779 if ( 1 === $exporter_index && 1 === $page ) { 780 update_post_meta( $request_id, '_export_data_raw', $export_data ); 781 } else { 782 $accumulated_data = get_post_meta( $request_id, '_export_data_raw', true ); 783 784 if ( $accumulated_data ) { 785 $export_data = $accumulated_data; 786 } 787 } 788 789 // Now, merge the data from the exporter response into the data we have accumulated already. 790 $export_data = array_merge( $export_data, $response['data'] ); 791 update_post_meta( $request_id, '_export_data_raw', $export_data ); 792 793 // If we are not yet on the last page of the last exporter, return now. 794 /** This filter is documented in wp-admin/includes/ajax-actions.php */ 795 $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() ); 796 $is_last_exporter = count( $exporters ) === $exporter_index; 797 $exporter_done = $response['done']; 798 if ( ! $is_last_exporter || ! $exporter_done ) { 799 return $response; 800 } 801 802 // Last exporter, last page - let's prepare the export file. 803 804 // First we need to re-organize the raw data hierarchically in groups and items. 805 $groups = array(); 806 foreach ( (array) $export_data as $export_datum ) { 807 $group_id = $export_datum['group_id']; 808 $group_label = $export_datum['group_label']; 809 810 $group_description = ''; 811 if ( ! empty( $export_datum['group_description'] ) ) { 812 $group_description = $export_datum['group_description']; 813 } 814 815 if ( ! array_key_exists( $group_id, $groups ) ) { 816 $groups[ $group_id ] = array( 817 'group_label' => $group_label, 818 'group_description' => $group_description, 819 'items' => array(), 820 ); 821 } 822 823 $item_id = $export_datum['item_id']; 824 if ( ! array_key_exists( $item_id, $groups[ $group_id ]['items'] ) ) { 825 $groups[ $group_id ]['items'][ $item_id ] = array(); 826 } 827 828 $old_item_data = $groups[ $group_id ]['items'][ $item_id ]; 829 $merged_item_data = array_merge( $export_datum['data'], $old_item_data ); 830 $groups[ $group_id ]['items'][ $item_id ] = $merged_item_data; 831 } 832 833 // Then save the grouped data into the request. 834 delete_post_meta( $request_id, '_export_data_raw' ); 835 update_post_meta( $request_id, '_export_data_grouped', $groups ); 836 837 /** 838 * Generate the export file from the collected, grouped personal data. 839 * 840 * @since 4.9.6 841 * 842 * @param int $request_id The export request ID. 843 */ 844 do_action( 'wp_privacy_personal_data_export_file', $request_id ); 845 846 // Clear the grouped data now that it is no longer needed. 847 delete_post_meta( $request_id, '_export_data_grouped' ); 848 849 // If the destination is email, send it now. 850 if ( $send_as_email ) { 851 $mail_success = wp_privacy_send_personal_data_export_email( $request_id ); 852 if ( is_wp_error( $mail_success ) ) { 853 wp_send_json_error( $mail_success->get_error_message() ); 854 } 855 856 // Update the request to completed state when the export email is sent. 857 _wp_privacy_completed_request( $request_id ); 858 } else { 859 // Modify the response to include the URL of the export file so the browser can fetch it. 860 $exports_url = wp_privacy_exports_url(); 861 $export_file_name = get_post_meta( $request_id, '_export_file_name', true ); 862 $export_file_url = $exports_url . $export_file_name; 863 864 if ( ! empty( $export_file_url ) ) { 865 $response['url'] = $export_file_url; 866 } 867 } 868 869 return $response; 870 } 871 872 /** 873 * Mark erasure requests as completed after processing is finished. 874 * 875 * This intercepts the Ajax responses to personal data eraser page requests, and 876 * monitors the status of a request. Once all of the processing has finished, the 877 * request is marked as completed. 878 * 879 * @since 4.9.6 880 * 881 * @see 'wp_privacy_personal_data_erasure_page' 882 * 883 * @param array $response The response from the personal data eraser for 884 * the given page. 885 * @param int $eraser_index The index of the personal data eraser. Begins 886 * at 1. 887 * @param string $email_address The email address of the user whose personal 888 * data this is. 889 * @param int $page The page of personal data for this eraser. 890 * Begins at 1. 891 * @param int $request_id The request ID for this personal data erasure. 892 * @return array The filtered response. 893 */ 894 function wp_privacy_process_personal_data_erasure_page( $response, $eraser_index, $email_address, $page, $request_id ) { 895 /* 896 * If the eraser response is malformed, don't attempt to consume it; let it 897 * pass through, so that the default Ajax processing will generate a warning 898 * to the user. 899 */ 900 if ( ! is_array( $response ) ) { 901 return $response; 902 } 903 904 if ( ! array_key_exists( 'done', $response ) ) { 905 return $response; 906 } 907 908 if ( ! array_key_exists( 'items_removed', $response ) ) { 909 return $response; 910 } 911 912 if ( ! array_key_exists( 'items_retained', $response ) ) { 913 return $response; 914 } 915 916 if ( ! array_key_exists( 'messages', $response ) ) { 917 return $response; 918 } 919 920 // Get the request. 921 $request = wp_get_user_request( $request_id ); 922 923 if ( ! $request || 'remove_personal_data' !== $request->action_name ) { 924 wp_send_json_error( __( 'Invalid request ID when processing personal data to erase.' ) ); 925 } 926 927 /** This filter is documented in wp-admin/includes/ajax-actions.php */ 928 $erasers = apply_filters( 'wp_privacy_personal_data_erasers', array() ); 929 $is_last_eraser = count( $erasers ) === $eraser_index; 930 $eraser_done = $response['done']; 931 932 if ( ! $is_last_eraser || ! $eraser_done ) { 933 return $response; 934 } 935 936 _wp_privacy_completed_request( $request_id ); 937 938 /** 939 * Fires immediately after a personal data erasure request has been marked completed. 940 * 941 * @since 4.9.6 942 * 943 * @param int $request_id The privacy request post ID associated with this request. 944 */ 945 do_action( 'wp_privacy_personal_data_erased', $request_id ); 946 947 return $response; 948 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Fri Mar 5 08:20:02 2021 | Cross-referenced by PHPXref |