[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

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


Generated: Fri Oct 25 08:20:01 2019 Cross-referenced by PHPXref 0.7