Make WordPress Core


Ignore:
Timestamp:
06/10/2019 11:53:32 PM (4 years ago)
Author:
azaozz
Message:

Privacy tools:

  • Move the (remaining) privacy tools related functions from wp-admin/includes/file.php to wp-admin/includes/privacy-tools.php.
  • Move the WP_User_Request class to a separate file.

See #43895.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/includes/file.php

    r45515 r45519  
    21952195    <?php
    21962196}
    2197 
    2198 /**
    2199  * Generate a single group for the personal data export report.
    2200  *
    2201  * @since 4.9.6
    2202  *
    2203  * @param array $group_data {
    2204  *     The group data to render.
    2205  *
    2206  *     @type string $group_label  The user-facing heading for the group, e.g. 'Comments'.
    2207  *     @type array  $items        {
    2208  *         An array of group items.
    2209  *
    2210  *         @type array  $group_item_data  {
    2211  *             An array of name-value pairs for the item.
    2212  *
    2213  *             @type string $name   The user-facing name of an item name-value pair, e.g. 'IP Address'.
    2214  *             @type string $value  The user-facing value of an item data pair, e.g. '50.60.70.0'.
    2215  *         }
    2216  *     }
    2217  * }
    2218  * @return string The HTML for this group and its items.
    2219  */
    2220 function wp_privacy_generate_personal_data_export_group_html( $group_data ) {
    2221     $group_html  = '<h2>' . esc_html( $group_data['group_label'] ) . '</h2>';
    2222     $group_html .= '<div>';
    2223 
    2224     foreach ( (array) $group_data['items'] as $group_item_id => $group_item_data ) {
    2225         $group_html .= '<table>';
    2226         $group_html .= '<tbody>';
    2227 
    2228         foreach ( (array) $group_item_data as $group_item_datum ) {
    2229             $value = $group_item_datum['value'];
    2230             // If it looks like a link, make it a link.
    2231             if ( false === strpos( $value, ' ' ) && ( 0 === strpos( $value, 'http://' ) || 0 === strpos( $value, 'https://' ) ) ) {
    2232                 $value = '<a href="' . esc_url( $value ) . '">' . esc_html( $value ) . '</a>';
    2233             }
    2234 
    2235             $group_html .= '<tr>';
    2236             $group_html .= '<th>' . esc_html( $group_item_datum['name'] ) . '</th>';
    2237             $group_html .= '<td>' . wp_kses( $value, 'personal_data_export' ) . '</td>';
    2238             $group_html .= '</tr>';
    2239         }
    2240 
    2241         $group_html .= '</tbody>';
    2242         $group_html .= '</table>';
    2243     }
    2244 
    2245     $group_html .= '</div>';
    2246 
    2247     return $group_html;
    2248 }
    2249 
    2250 /**
    2251  * Generate the personal data export file.
    2252  *
    2253  * @since 4.9.6
    2254  *
    2255  * @param int $request_id The export request ID.
    2256  */
    2257 function wp_privacy_generate_personal_data_export_file( $request_id ) {
    2258     if ( ! class_exists( 'ZipArchive' ) ) {
    2259         wp_send_json_error( __( 'Unable to generate export file. ZipArchive not available.' ) );
    2260     }
    2261 
    2262     // Get the request data.
    2263     $request = wp_get_user_request_data( $request_id );
    2264 
    2265     if ( ! $request || 'export_personal_data' !== $request->action_name ) {
    2266         wp_send_json_error( __( 'Invalid request ID when generating export file.' ) );
    2267     }
    2268 
    2269     $email_address = $request->email;
    2270 
    2271     if ( ! is_email( $email_address ) ) {
    2272         wp_send_json_error( __( 'Invalid email address when generating export file.' ) );
    2273     }
    2274 
    2275     // Create the exports folder if needed.
    2276     $exports_dir = wp_privacy_exports_dir();
    2277     $exports_url = wp_privacy_exports_url();
    2278 
    2279     if ( ! wp_mkdir_p( $exports_dir ) ) {
    2280         wp_send_json_error( __( 'Unable to create export folder.' ) );
    2281     }
    2282 
    2283     // Protect export folder from browsing.
    2284     $index_pathname = $exports_dir . 'index.html';
    2285     if ( ! file_exists( $index_pathname ) ) {
    2286         $file = fopen( $index_pathname, 'w' );
    2287         if ( false === $file ) {
    2288             wp_send_json_error( __( 'Unable to protect export folder from browsing.' ) );
    2289         }
    2290         fwrite( $file, '<!-- Silence is golden. -->' );
    2291         fclose( $file );
    2292     }
    2293 
    2294     $stripped_email       = str_replace( '@', '-at-', $email_address );
    2295     $stripped_email       = sanitize_title( $stripped_email ); // slugify the email address
    2296     $obscura              = wp_generate_password( 32, false, false );
    2297     $file_basename        = 'wp-personal-data-file-' . $stripped_email . '-' . $obscura;
    2298     $html_report_filename = $file_basename . '.html';
    2299     $html_report_pathname = wp_normalize_path( $exports_dir . $html_report_filename );
    2300     $file                 = fopen( $html_report_pathname, 'w' );
    2301     if ( false === $file ) {
    2302         wp_send_json_error( __( 'Unable to open export file (HTML report) for writing.' ) );
    2303     }
    2304 
    2305     $title = sprintf(
    2306         /* translators: %s: user's email address */
    2307         __( 'Personal Data Export for %s' ),
    2308         $email_address
    2309     );
    2310 
    2311     // Open HTML.
    2312     fwrite( $file, "<!DOCTYPE html>\n" );
    2313     fwrite( $file, "<html>\n" );
    2314 
    2315     // Head.
    2316     fwrite( $file, "<head>\n" );
    2317     fwrite( $file, "<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />\n" );
    2318     fwrite( $file, "<style type='text/css'>" );
    2319     fwrite( $file, 'body { color: black; font-family: Arial, sans-serif; font-size: 11pt; margin: 15px auto; width: 860px; }' );
    2320     fwrite( $file, 'table { background: #f0f0f0; border: 1px solid #ddd; margin-bottom: 20px; width: 100%; }' );
    2321     fwrite( $file, 'th { padding: 5px; text-align: left; width: 20%; }' );
    2322     fwrite( $file, 'td { padding: 5px; }' );
    2323     fwrite( $file, 'tr:nth-child(odd) { background-color: #fafafa; }' );
    2324     fwrite( $file, '</style>' );
    2325     fwrite( $file, '<title>' );
    2326     fwrite( $file, esc_html( $title ) );
    2327     fwrite( $file, '</title>' );
    2328     fwrite( $file, "</head>\n" );
    2329 
    2330     // Body.
    2331     fwrite( $file, "<body>\n" );
    2332 
    2333     // Heading.
    2334     fwrite( $file, '<h1>' . esc_html__( 'Personal Data Export' ) . '</h1>' );
    2335 
    2336     // And now, all the Groups.
    2337     $groups = get_post_meta( $request_id, '_export_data_grouped', true );
    2338 
    2339     // First, build an "About" group on the fly for this report.
    2340     $about_group = array(
    2341         /* translators: Header for the About section in a personal data export. */
    2342         'group_label' => _x( 'About', 'personal data group label' ),
    2343         'items'       => array(
    2344             'about-1' => array(
    2345                 array(
    2346                     'name'  => _x( 'Report generated for', 'email address' ),
    2347                     'value' => $email_address,
    2348                 ),
    2349                 array(
    2350                     'name'  => _x( 'For site', 'website name' ),
    2351                     'value' => get_bloginfo( 'name' ),
    2352                 ),
    2353                 array(
    2354                     'name'  => _x( 'At URL', 'website URL' ),
    2355                     'value' => get_bloginfo( 'url' ),
    2356                 ),
    2357                 array(
    2358                     'name'  => _x( 'On', 'date/time' ),
    2359                     'value' => current_time( 'mysql' ),
    2360                 ),
    2361             ),
    2362         ),
    2363     );
    2364 
    2365     // Merge in the special about group.
    2366     $groups = array_merge( array( 'about' => $about_group ), $groups );
    2367 
    2368     // Now, iterate over every group in $groups and have the formatter render it in HTML.
    2369     foreach ( (array) $groups as $group_id => $group_data ) {
    2370         fwrite( $file, wp_privacy_generate_personal_data_export_group_html( $group_data ) );
    2371     }
    2372 
    2373     fwrite( $file, "</body>\n" );
    2374 
    2375     // Close HTML.
    2376     fwrite( $file, "</html>\n" );
    2377     fclose( $file );
    2378 
    2379     /*
    2380      * Now, generate the ZIP.
    2381      *
    2382      * If an archive has already been generated, then remove it and reuse the
    2383      * filename, to avoid breaking any URLs that may have been previously sent
    2384      * via email.
    2385      */
    2386     $error            = false;
    2387     $archive_url      = get_post_meta( $request_id, '_export_file_url', true );
    2388     $archive_pathname = get_post_meta( $request_id, '_export_file_path', true );
    2389 
    2390     if ( empty( $archive_pathname ) || empty( $archive_url ) ) {
    2391         $archive_filename = $file_basename . '.zip';
    2392         $archive_pathname = $exports_dir . $archive_filename;
    2393         $archive_url      = $exports_url . $archive_filename;
    2394 
    2395         update_post_meta( $request_id, '_export_file_url', $archive_url );
    2396         update_post_meta( $request_id, '_export_file_path', wp_normalize_path( $archive_pathname ) );
    2397     }
    2398 
    2399     if ( ! empty( $archive_pathname ) && file_exists( $archive_pathname ) ) {
    2400         wp_delete_file( $archive_pathname );
    2401     }
    2402 
    2403     $zip = new ZipArchive;
    2404     if ( true === $zip->open( $archive_pathname, ZipArchive::CREATE ) ) {
    2405         if ( ! $zip->addFile( $html_report_pathname, 'index.html' ) ) {
    2406             $error = __( 'Unable to add data to export file.' );
    2407         }
    2408 
    2409         $zip->close();
    2410 
    2411         if ( ! $error ) {
    2412             /**
    2413              * Fires right after all personal data has been written to the export file.
    2414              *
    2415              * @since 4.9.6
    2416              *
    2417              * @param string $archive_pathname     The full path to the export file on the filesystem.
    2418              * @param string $archive_url          The URL of the archive file.
    2419              * @param string $html_report_pathname The full path to the personal data report on the filesystem.
    2420              * @param int    $request_id           The export request ID.
    2421              */
    2422             do_action( 'wp_privacy_personal_data_export_file_created', $archive_pathname, $archive_url, $html_report_pathname, $request_id );
    2423         }
    2424     } else {
    2425         $error = __( 'Unable to open export file (archive) for writing.' );
    2426     }
    2427 
    2428     // And remove the HTML file.
    2429     unlink( $html_report_pathname );
    2430 
    2431     if ( $error ) {
    2432         wp_send_json_error( $error );
    2433     }
    2434 }
    2435 
    2436 /**
    2437  * Send an email to the user with a link to the personal data export file
    2438  *
    2439  * @since 4.9.6
    2440  *
    2441  * @param int $request_id The request ID for this personal data export.
    2442  * @return true|WP_Error True on success or `WP_Error` on failure.
    2443  */
    2444 function wp_privacy_send_personal_data_export_email( $request_id ) {
    2445     // Get the request data.
    2446     $request = wp_get_user_request_data( $request_id );
    2447 
    2448     if ( ! $request || 'export_personal_data' !== $request->action_name ) {
    2449         return new WP_Error( 'invalid_request', __( 'Invalid request ID when sending personal data export email.' ) );
    2450     }
    2451 
    2452     // Localize message content for user; fallback to site default for visitors.
    2453     if ( ! empty( $request->user_id ) ) {
    2454         $locale = get_user_locale( $request->user_id );
    2455     } else {
    2456         $locale = get_locale();
    2457     }
    2458 
    2459     $switched_locale = switch_to_locale( $locale );
    2460 
    2461     /** This filter is documented in wp-includes/functions.php */
    2462     $expiration      = apply_filters( 'wp_privacy_export_expiration', 3 * DAY_IN_SECONDS );
    2463     $expiration_date = date_i18n( get_option( 'date_format' ), time() + $expiration );
    2464 
    2465     /* translators: Do not translate EXPIRATION, LINK, SITENAME, SITEURL: those are placeholders. */
    2466     $email_text = __(
    2467         'Howdy,
    2468 
    2469 Your request for an export of personal data has been completed. You may
    2470 download your personal data by clicking on the link below. For privacy
    2471 and security, we will automatically delete the file on ###EXPIRATION###,
    2472 so please download it before then.
    2473 
    2474 ###LINK###
    2475 
    2476 Regards,
    2477 All at ###SITENAME###
    2478 ###SITEURL###'
    2479     );
    2480 
    2481     /**
    2482      * Filters the text of the email sent with a personal data export file.
    2483      *
    2484      * The following strings have a special meaning and will get replaced dynamically:
    2485      * ###EXPIRATION###         The date when the URL will be automatically deleted.
    2486      * ###LINK###               URL of the personal data export file for the user.
    2487      * ###SITENAME###           The name of the site.
    2488      * ###SITEURL###            The URL to the site.
    2489      *
    2490      * @since 4.9.6
    2491      *
    2492      * @param string $email_text     Text in the email.
    2493      * @param int    $request_id     The request ID for this personal data export.
    2494      */
    2495     $content = apply_filters( 'wp_privacy_personal_data_email_content', $email_text, $request_id );
    2496 
    2497     $email_address   = $request->email;
    2498     $export_file_url = get_post_meta( $request_id, '_export_file_url', true );
    2499     $site_name       = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
    2500     $site_url        = home_url();
    2501 
    2502     $content = str_replace( '###EXPIRATION###', $expiration_date, $content );
    2503     $content = str_replace( '###LINK###', esc_url_raw( $export_file_url ), $content );
    2504     $content = str_replace( '###EMAIL###', $email_address, $content );
    2505     $content = str_replace( '###SITENAME###', $site_name, $content );
    2506     $content = str_replace( '###SITEURL###', esc_url_raw( $site_url ), $content );
    2507 
    2508     $mail_success = wp_mail(
    2509         $email_address,
    2510         sprintf(
    2511             /* translators: Personal data export notification email subject. %s: Site title */
    2512             __( '[%s] Personal Data Export' ),
    2513             $site_name
    2514         ),
    2515         $content
    2516     );
    2517 
    2518     if ( $switched_locale ) {
    2519         restore_previous_locale();
    2520     }
    2521 
    2522     if ( ! $mail_success ) {
    2523         return new WP_Error( 'privacy_email_error', __( 'Unable to send personal data export email.' ) );
    2524     }
    2525 
    2526     return true;
    2527 }
    2528 
    2529 /**
    2530  * Intercept personal data exporter page Ajax responses in order to assemble the personal data export file.
    2531  * @see wp_privacy_personal_data_export_page
    2532  * @since 4.9.6
    2533  *
    2534  * @param array  $response        The response from the personal data exporter for the given page.
    2535  * @param int    $exporter_index  The index of the personal data exporter. Begins at 1.
    2536  * @param string $email_address   The email address of the user whose personal data this is.
    2537  * @param int    $page            The page of personal data for this exporter. Begins at 1.
    2538  * @param int    $request_id      The request ID for this personal data export.
    2539  * @param bool   $send_as_email   Whether the final results of the export should be emailed to the user.
    2540  * @param string $exporter_key    The slug (key) of the exporter.
    2541  * @return array The filtered response.
    2542  */
    2543 function wp_privacy_process_personal_data_export_page( $response, $exporter_index, $email_address, $page, $request_id, $send_as_email, $exporter_key ) {
    2544     /* Do some simple checks on the shape of the response from the exporter.
    2545      * If the exporter response is malformed, don't attempt to consume it - let it
    2546      * pass through to generate a warning to the user by default Ajax processing.
    2547      */
    2548     if ( ! is_array( $response ) ) {
    2549         return $response;
    2550     }
    2551 
    2552     if ( ! array_key_exists( 'done', $response ) ) {
    2553         return $response;
    2554     }
    2555 
    2556     if ( ! array_key_exists( 'data', $response ) ) {
    2557         return $response;
    2558     }
    2559 
    2560     if ( ! is_array( $response['data'] ) ) {
    2561         return $response;
    2562     }
    2563 
    2564     // Get the request data.
    2565     $request = wp_get_user_request_data( $request_id );
    2566 
    2567     if ( ! $request || 'export_personal_data' !== $request->action_name ) {
    2568         wp_send_json_error( __( 'Invalid request ID when merging exporter data.' ) );
    2569     }
    2570 
    2571     $export_data = array();
    2572 
    2573     // First exporter, first page? Reset the report data accumulation array.
    2574     if ( 1 === $exporter_index && 1 === $page ) {
    2575         update_post_meta( $request_id, '_export_data_raw', $export_data );
    2576     } else {
    2577         $export_data = get_post_meta( $request_id, '_export_data_raw', true );
    2578     }
    2579 
    2580     // Now, merge the data from the exporter response into the data we have accumulated already.
    2581     $export_data = array_merge( $export_data, $response['data'] );
    2582     update_post_meta( $request_id, '_export_data_raw', $export_data );
    2583 
    2584     // If we are not yet on the last page of the last exporter, return now.
    2585     /** This filter is documented in wp-admin/includes/ajax-actions.php */
    2586     $exporters        = apply_filters( 'wp_privacy_personal_data_exporters', array() );
    2587     $is_last_exporter = $exporter_index === count( $exporters );
    2588     $exporter_done    = $response['done'];
    2589     if ( ! $is_last_exporter || ! $exporter_done ) {
    2590         return $response;
    2591     }
    2592 
    2593     // Last exporter, last page - let's prepare the export file.
    2594 
    2595     // First we need to re-organize the raw data hierarchically in groups and items.
    2596     $groups = array();
    2597     foreach ( (array) $export_data as $export_datum ) {
    2598         $group_id    = $export_datum['group_id'];
    2599         $group_label = $export_datum['group_label'];
    2600         if ( ! array_key_exists( $group_id, $groups ) ) {
    2601             $groups[ $group_id ] = array(
    2602                 'group_label' => $group_label,
    2603                 'items'       => array(),
    2604             );
    2605         }
    2606 
    2607         $item_id = $export_datum['item_id'];
    2608         if ( ! array_key_exists( $item_id, $groups[ $group_id ]['items'] ) ) {
    2609             $groups[ $group_id ]['items'][ $item_id ] = array();
    2610         }
    2611 
    2612         $old_item_data                            = $groups[ $group_id ]['items'][ $item_id ];
    2613         $merged_item_data                         = array_merge( $export_datum['data'], $old_item_data );
    2614         $groups[ $group_id ]['items'][ $item_id ] = $merged_item_data;
    2615     }
    2616 
    2617     // Then save the grouped data into the request.
    2618     delete_post_meta( $request_id, '_export_data_raw' );
    2619     update_post_meta( $request_id, '_export_data_grouped', $groups );
    2620 
    2621     /**
    2622      * Generate the export file from the collected, grouped personal data.
    2623      *
    2624      * @since 4.9.6
    2625      *
    2626      * @param int $request_id The export request ID.
    2627      */
    2628     do_action( 'wp_privacy_personal_data_export_file', $request_id );
    2629 
    2630     // Clear the grouped data now that it is no longer needed.
    2631     delete_post_meta( $request_id, '_export_data_grouped' );
    2632 
    2633     // If the destination is email, send it now.
    2634     if ( $send_as_email ) {
    2635         $mail_success = wp_privacy_send_personal_data_export_email( $request_id );
    2636         if ( is_wp_error( $mail_success ) ) {
    2637             wp_send_json_error( $mail_success->get_error_message() );
    2638         }
    2639 
    2640         // Update the request to completed state when the export email is sent.
    2641         _wp_privacy_completed_request( $request_id );
    2642     } else {
    2643         // Modify the response to include the URL of the export file so the browser can fetch it.
    2644         $export_file_url = get_post_meta( $request_id, '_export_file_url', true );
    2645         if ( ! empty( $export_file_url ) ) {
    2646             $response['url'] = $export_file_url;
    2647         }
    2648     }
    2649 
    2650     return $response;
    2651 }
Note: See TracChangeset for help on using the changeset viewer.