[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-admin/includes/ -> privacy-tools.php (source)

   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">&uarr; </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  }


Generated: Sun Feb 28 01:00:03 2021 Cross-referenced by PHPXref 0.7.1