Make WordPress Core

Ticket #43551: 43551.2.diff

File 43551.2.diff, 9.1 KB (added by allendav, 5 years ago)

Updated to handle 1-base; updated to handle nested/structured personal data; improved HTML report format

  • src/wp-admin/includes/admin-filters.php

     
    128128add_action( 'upgrader_process_complete', 'wp_version_check', 10, 0 );
    129129add_action( 'upgrader_process_complete', 'wp_update_plugins', 10, 0 );
    130130add_action( 'upgrader_process_complete', 'wp_update_themes', 10, 0 );
     131
     132// Privacy hooks
     133add_action( 'admin_init', 'wp_privacy_delete_old_export_files' );
     134add_filter( 'wp_privacy_personal_data_export_page', 'wp_privacy_process_personal_data_export_page', 10, 4 );
  • src/wp-admin/includes/file.php

     
    19341934        </div>
    19351935        <?php
    19361936}
     1937
     1938// TODO phpDocs
     1939function wp_privacy_generate_personal_data_export_group_html( $group_data ) {
     1940        $group_html = '';
     1941
     1942        $group_html .= '<h2>' . esc_html( $group_data['group_label'] ) . '</h2>';
     1943        $group_html .= '<div>';
     1944
     1945        foreach ( (array) $group_data['items'] as $group_item_id => $group_item_data ) {
     1946                $group_html .= '<table>';
     1947                $group_html .= '<tbody>';
     1948
     1949                foreach ( (array) $group_item_data as $group_item_datum ) {
     1950                        $group_html .= '<tr>';
     1951                        $group_html .= '<th>' . esc_html( $group_item_datum['name'] ) . '</th>';
     1952                        // TODO entities!
     1953                        $group_html .= '<td>' . esc_html( $group_item_datum['value'] ) . '</td>';
     1954                        // TODO support attachment links
     1955                        $group_html .= '</tr>';
     1956                }
     1957
     1958                $group_html .= '</tbody>';
     1959                $group_html .= '</table>';
     1960        }
     1961
     1962        $group_html .= '</div>';
     1963
     1964        return $group_html;
     1965}
     1966
     1967// TODO phpDocs
     1968function wp_privacy_generate_personal_data_export( $exports_dir, $email_address, $personal_data ) {
     1969
     1970        // TODO Instead of HTML, return a ZIP containing the HTML and the attachments
     1971        $timestamp = current_time( 'timestamp' );
     1972        $index_filename = 'wp-personal-data-export-' . md5( $email_address ) . '-' . md5( $timestamp ) . '.html';
     1973
     1974        $index_path = trailingslashit( $exports_dir ) . $index_filename;
     1975        $file = fopen( $index_path, 'w' );
     1976        // TODO catch fopen error
     1977
     1978        $title = sprintf(
     1979                __( 'Personal Data Export for %s' ),
     1980                $email_address
     1981        );
     1982
     1983        // Open HTML
     1984        fwrite( $file, "<!DOCTYPE html>\n" );
     1985        fwrite( $file, "<html>\n" );
     1986
     1987        // Head
     1988        fwrite( $file, "<head>\n" );
     1989        fwrite( $file, "<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />\n" );
     1990        fwrite( $file, "<style type='text/css'>" );
     1991        fwrite( $file, "body { color: black; font-family: Arial, sans-serif; font-size: 11pt; margin: 15px auto; width: 860px; }" );
     1992        fwrite( $file, "table { background: #f0f0f0; border: 1px solid #ddd; margin-bottom: 20px; width: 100%; }" );
     1993        fwrite( $file, "th { padding: 5px; text-align: left; width: 20%; }" );
     1994        fwrite( $file, "td { padding: 5px; }" );
     1995        fwrite( $file, "tr:nth-child(odd) { background-color: #fafafa; }" );
     1996        fwrite( $file, "</style>" );
     1997        fwrite( $file, "<title>" );
     1998        fwrite( $file, esc_html( $title ) );
     1999        fwrite( $file, "</title>" );
     2000        fwrite( $file, "</head>\n" );
     2001
     2002        // Body
     2003        fwrite( $file, "<body>\n" );
     2004
     2005        // Heading
     2006        fwrite( $file, "<h1>" . esc_html__( 'Personal Data Export' ) . "</h1>" );
     2007
     2008        // And now, all the Groups
     2009        $groups = array();
     2010
     2011        // First, build a "About" group on the fly for this report
     2012        $groups[] = array(
     2013                'group_id'    => 'about',
     2014                'group_label' => __( 'About' ),
     2015                'items'       => array(
     2016                        'about-1' => array(
     2017                                array(
     2018                                        'name'  => __( 'Report generated for' ),
     2019                                        'value' => $email_address,
     2020                                ),
     2021                                array(
     2022                                        'name'  => __( 'For site' ),
     2023                                        'value' => get_bloginfo( 'name' ),
     2024                                ),
     2025                                array(
     2026                                        'name'  => __( 'At URL' ),
     2027                                        'value' => get_bloginfo( 'url' ),
     2028                                ),
     2029                                array(
     2030                                        'name'  => __( 'On' ),
     2031                                        'value' => current_time( 'mysql' ),
     2032                                ),
     2033                        ),
     2034                )
     2035        );
     2036
     2037        // Next, iterate over every item in $personal_data
     2038        // Extract all the unique group_ids, group_labels and item_ids
     2039        // Initialize/append the data for each item under its item_id
     2040        foreach ( (array) $personal_data as $personal_datum ) {
     2041                $group_id    = $personal_datum['group_id'];
     2042                $group_label = $personal_datum['group_label'];
     2043                if ( ! array_key_exists( $group_id, $groups ) ) {
     2044                        $groups[] = array(
     2045                                'group_id'    => $group_id,
     2046                                'group_label' => $group_label,
     2047                                'items'       => array(),
     2048                        );
     2049                }
     2050
     2051                $item_id = $personal_datum['item_id'];
     2052                if ( ! array_key_exists( $item_id, $groups[ $group_id ]['items'] ) ) {
     2053                        $groups[ $group_id ]['items'][ $item_id ] = array();
     2054                }
     2055
     2056                $old_item_data = $groups[ $group_id ]['items'][ $item_id ];
     2057                $merged_item_data = array_merge( $personal_datum['data'], $old_item_data );
     2058                $groups[ $group_id ]['items'][ $item_id ] = $merged_item_data;
     2059        }
     2060
     2061        // Now, iterate over every group in $groups and have the formatter render it in HTML
     2062        foreach ( (array) $groups as $group ) {
     2063                fwrite( $file, wp_privacy_generate_personal_data_export_group_html( $group ) );
     2064        }
     2065
     2066        fwrite( $file, "</body>\n" );
     2067
     2068        // Close HTML
     2069        fwrite( $file, "</html>\n" );
     2070        fclose( $file );
     2071
     2072        // Now, generate the ZIP
     2073        $archive_filename = 'wp-personal-data-export-' . md5( $email_address ) . '-' . md5( $timestamp ) . '.zip';
     2074        $archive_path = trailingslashit( $exports_dir ) . $archive_filename;
     2075
     2076        $zip = new ZipArchive;
     2077        // TODO test for no ZipArchive to work with
     2078
     2079        if ( TRUE === $zip->open( $archive_path, ZipArchive::CREATE ) ) {
     2080                $zip->addFile( $index_path, 'index.html' );
     2081                // TODO - add things referenced in wp-content/uploads
     2082                $zip->close();
     2083        } else {
     2084                error_log( "unable to open zip for creation" );
     2085                // TODO handle error here
     2086        }
     2087
     2088        // And remove the HTML file
     2089        unlink( $index_path );
     2090
     2091        return(
     2092                array(
     2093                        'timestamp' => $timestamp,
     2094                        'filename' => $archive_filename,
     2095                )
     2096        );
     2097}
     2098
     2099// TODO phpDocs
     2100function wp_privacy_process_personal_data_export_page( $response, $exporter_index, $email_address, $page ) {
     2101
     2102        // Housekeeping
     2103        $upload_dir  = wp_upload_dir();
     2104        $exports_dir = $upload_dir['basedir'] . '/exports';
     2105        $exports_url = $upload_dir['baseurl'] . '/exports';
     2106
     2107        // Create the exports folder if needed
     2108        $result = wp_mkdir_p( $exports_dir );
     2109        if ( is_wp_error( $result ) ) {
     2110                return $result;
     2111        }
     2112
     2113        // TODO Make sure the exports folder is protected (htaccess, index)
     2114
     2115        // Generate a export file option key from the email address
     2116        $export_file_option = '_wp_privacy_export_file_' . md5( $email_address );
     2117
     2118        // See if we have an export URL cached for this user already
     2119        $export_file_details = get_site_option( $export_file_option, '', false );
     2120
     2121        // And if we do, short circuit the export and send the path back instead
     2122        if ( ! empty( $export_file_details ) ) {
     2123                $pieces = explode( ':', $export_file_details ); // path:timestamp
     2124                $export_file_path = trailingslashit( $exports_dir ) . $pieces[0];
     2125
     2126                if ( file_exists( $export_file_path ) ) {
     2127                        return(
     2128                                array(
     2129                                        'data' => array(),
     2130                                        'done' => true,
     2131                                        'url'  => $pieces[0],
     2132                                )
     2133                        );
     2134                }
     2135
     2136                // No file? Delete the option and continue building the export
     2137                delete_site_option( $export_file_option );
     2138        }
     2139
     2140        // Generate a (temporary) data storage option key from the email address
     2141        $export_data_option = '_wp_privacy_export_data_' . md5( $email_address );
     2142
     2143        // First page of first exporter? Prepare/clear the option where we will assemble all the responses
     2144        if ( 1 === $exporter_index && 1 === $page ) {
     2145                update_site_option( $export_data_option, array() );
     2146        }
     2147
     2148        // Grab whatever data has already been collected on previous exporters and/or pages
     2149        $export_data = get_site_option( $export_data_option, array() );
     2150
     2151        // Does the response include data? If so, add this response's data to all the data we have received so far
     2152        // TODO - enforce the shape?
     2153        // TODO - enforce data as a numeric (not associative) array
     2154        $exporter_page_data = $response['data'];
     2155        if ( ! empty( $exporter_page_data ) && is_array( $exporter_page_data ) ) {
     2156                $export_data = array_merge( $export_data, $exporter_page_data );
     2157                update_site_option( $export_data_option, $export_data );
     2158        }
     2159
     2160        // Are we on the last exporter, and did that export say it was done? If not, return now
     2161        // so we can continue to collect data for export
     2162        $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() );
     2163        $is_last_exporter = $exporter_index === count( $exporters );
     2164        $exporter_done = $response['done'];
     2165        if ( ! $is_last_exporter || ! $exporter_done ) {
     2166                return $response;
     2167        }
     2168
     2169        // TODO de-repetitive-ize the data
     2170
     2171        // Generate the export
     2172        $result = wp_privacy_generate_personal_data_export( $exports_dir, $email_address, $export_data );
     2173        if ( is_wp_error( $result ) ) {
     2174                return $result;
     2175        }
     2176
     2177        // Get the timestamp and filename from the export
     2178        $timestamp = $result['timestamp'];
     2179        $filename = $result['filename'];
     2180
     2181        // Build the URL for the file
     2182        $export_url = trailingslashit( $exports_url ) . $filename;
     2183
     2184        // Modify the response to include the URL of the export file
     2185        $response['url'] = $export_url;
     2186
     2187        // TODO Save the export file in an option for safekeeping
     2188
     2189        return $response;
     2190}
     2191
     2192function wp_privacy_delete_old_export_files() {
     2193
     2194        // TODO delete old export files and their options
     2195
     2196}