| | 1937 | |
| | 1938 | // TODO phpDocs |
| | 1939 | function wp_privacy_generate_personal_data_export( $exports_dir, $email_address, $personal_data ) { |
| | 1940 | |
| | 1941 | // TODO Instead of HTML, return a ZIP containing the HTML and the attachments |
| | 1942 | $timestamp = current_time( 'timestamp' ); |
| | 1943 | $index_filename = 'wp-personal-data-export-' . md5( $email_address ) . '-' . md5( $timestamp ) . '.html'; |
| | 1944 | |
| | 1945 | $index_path = trailingslashit( $exports_dir ) . $index_filename; |
| | 1946 | $file = fopen( $index_path, 'w' ); |
| | 1947 | // TODO catch fopen error |
| | 1948 | |
| | 1949 | fwrite( $file, "<!DOCTYPE html>\n" ); |
| | 1950 | fwrite( $file, "<html>\n" ); |
| | 1951 | fwrite( $file, "<head>\n" ); |
| | 1952 | |
| | 1953 | // TODO title using email address |
| | 1954 | |
| | 1955 | fwrite( $file, "</head>\n" ); |
| | 1956 | fwrite( $file, "<body>\n" ); |
| | 1957 | |
| | 1958 | // TODO heading using email address and possibly gravatar |
| | 1959 | // TODO heading should also include date/time generated and for what site |
| | 1960 | |
| | 1961 | fwrite( $file, "<table>\n" ); |
| | 1962 | fwrite( $file, "<tbody>\n" ); |
| | 1963 | |
| | 1964 | foreach( (array) $personal_data as $personal_datum ) { |
| | 1965 | fwrite( $file, "<tr>\n" ); |
| | 1966 | fwrite( $file, "<td>" ); |
| | 1967 | fwrite( $file, esc_html( $personal_datum['name'] ) ); |
| | 1968 | fwrite( $file, "</td>\n" ); |
| | 1969 | fwrite( $file, "<td>" ); |
| | 1970 | // TODO kses the value |
| | 1971 | // TODO linkify filepaths from wp-content/uploads |
| | 1972 | fwrite( $file, nl2br( $personal_datum['value'] ) ); |
| | 1973 | fwrite( $file, "</td>" ); |
| | 1974 | fwrite( $file, "</tr>\n" ); |
| | 1975 | } |
| | 1976 | |
| | 1977 | fwrite( $file, "</tbody>\n" ); |
| | 1978 | fwrite( $file, "</table>\n" ); |
| | 1979 | fwrite( $file, "</body>\n" ); |
| | 1980 | fwrite( $file, "</html>\n" ); |
| | 1981 | fclose( $file ); |
| | 1982 | |
| | 1983 | // Now, generate the ZIP |
| | 1984 | $archive_filename = 'wp-personal-data-export-' . md5( $email_address ) . '-' . md5( $timestamp ) . '.zip'; |
| | 1985 | $archive_path = trailingslashit( $exports_dir ) . $archive_filename; |
| | 1986 | |
| | 1987 | $zip = new ZipArchive; |
| | 1988 | // TODO test for no ZipArchive to work with |
| | 1989 | |
| | 1990 | if ( TRUE === $zip->open( $archive_path, ZipArchive::CREATE ) ) { |
| | 1991 | $zip->addFile( $index_path, 'index.html' ); |
| | 1992 | // TODO - add things referenced in wp-content/uploads |
| | 1993 | $zip->close(); |
| | 1994 | } else { |
| | 1995 | error_log( "unable to open zip for creation" ); |
| | 1996 | // TODO handle error here |
| | 1997 | } |
| | 1998 | |
| | 1999 | // And remove the HTML file |
| | 2000 | unlink( $index_path ); |
| | 2001 | |
| | 2002 | return( |
| | 2003 | array( |
| | 2004 | 'timestamp' => $timestamp, |
| | 2005 | 'filename' => $archive_filename, |
| | 2006 | ) |
| | 2007 | ); |
| | 2008 | } |
| | 2009 | |
| | 2010 | // TODO phpDocs |
| | 2011 | function wp_privacy_process_personal_data_export_page( $response, $exporter_index, $email_address, $page ) { |
| | 2012 | |
| | 2013 | // Housekeeping |
| | 2014 | $upload_dir = wp_upload_dir(); |
| | 2015 | $exports_dir = $upload_dir['basedir'] . '/exports'; |
| | 2016 | $exports_url = $upload_dir['baseurl'] . '/exports'; |
| | 2017 | |
| | 2018 | // Create the exports folder if needed |
| | 2019 | $result = wp_mkdir_p( $exports_dir ); |
| | 2020 | if ( is_wp_error( $result ) ) { |
| | 2021 | return $result; |
| | 2022 | } |
| | 2023 | |
| | 2024 | // TODO Make sure the exports folder is protected (htaccess, index) |
| | 2025 | |
| | 2026 | // Generate a export file option key from the email address |
| | 2027 | $export_file_option = '_wp_privacy_export_file_' . md5( $email_address ); |
| | 2028 | |
| | 2029 | // See if we have an export URL cached for this user already |
| | 2030 | $export_file_details = get_site_option( $export_file_option, '', false ); |
| | 2031 | |
| | 2032 | // And if we do, short circuit the export and send the path back instead |
| | 2033 | if ( ! empty( $export_file_details ) ) { |
| | 2034 | $pieces = explode( ':', $export_file_details ); // path:timestamp |
| | 2035 | $export_file_path = trailingslashit( $exports_dir ) . $pieces[0]; |
| | 2036 | |
| | 2037 | if ( file_exists( $export_file_path ) ) { |
| | 2038 | return( |
| | 2039 | array( |
| | 2040 | 'data' => array(), |
| | 2041 | 'done' => true, |
| | 2042 | 'url' => $pieces[0], |
| | 2043 | ) |
| | 2044 | ); |
| | 2045 | } |
| | 2046 | |
| | 2047 | // No file? Delete the option and continue building the export |
| | 2048 | delete_site_option( $export_file_option ); |
| | 2049 | } |
| | 2050 | |
| | 2051 | // Generate a (temporary) data storage option key from the email address |
| | 2052 | $export_data_option = '_wp_privacy_export_data_' . md5( $email_address ); |
| | 2053 | |
| | 2054 | // First page of first exporter? Prepare/clear the option where we will assemble all the responses |
| | 2055 | if ( 0 === $exporter_index && 0 === $page ) { |
| | 2056 | update_site_option( $export_data_option, array() ); |
| | 2057 | } |
| | 2058 | |
| | 2059 | // Grab whatever data has already been collected on previous exporters and/or pages |
| | 2060 | $export_data = get_site_option( $export_data_option, array() ); |
| | 2061 | |
| | 2062 | // Does the response include data? If so, add this response's data to all the data we have received so far |
| | 2063 | // TODO - make 43438 enforce the data/done/url shape? |
| | 2064 | // TODO - enforce data as a numeric (not associative) array |
| | 2065 | $exporter_page_data = $response['data']; |
| | 2066 | if ( ! empty( $exporter_page_data ) && is_array( $exporter_page_data ) ) { |
| | 2067 | $export_data = array_merge( $export_data, $exporter_page_data ); |
| | 2068 | update_site_option( $export_data_option, $export_data ); |
| | 2069 | } |
| | 2070 | |
| | 2071 | // Are we on the last exporter, and did that export say it was done? If not, return now |
| | 2072 | // so we can continue to collect data for export |
| | 2073 | $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() ); |
| | 2074 | $is_last_exporter = $exporter_index === count( $exporters ) - 1; |
| | 2075 | $exporter_done = $response['done']; |
| | 2076 | if ( ! $is_last_exporter || ! $exporter_done ) { |
| | 2077 | return $response; |
| | 2078 | } |
| | 2079 | |
| | 2080 | // TODO de-repetitive-ize the data |
| | 2081 | |
| | 2082 | // Generate the export |
| | 2083 | $result = wp_privacy_generate_personal_data_export( $exports_dir, $email_address, $export_data ); |
| | 2084 | if ( is_wp_error( $result ) ) { |
| | 2085 | return $result; |
| | 2086 | } |
| | 2087 | |
| | 2088 | // Get the timestamp and filename from the export |
| | 2089 | $timestamp = $result['timestamp']; |
| | 2090 | $filename = $result['filename']; |
| | 2091 | |
| | 2092 | // Build the URL for the file |
| | 2093 | $export_url = trailingslashit( $exports_url ) . $filename; |
| | 2094 | |
| | 2095 | // Modify the response to include the URL of the export file |
| | 2096 | $response['url'] = $export_url; |
| | 2097 | |
| | 2098 | // TODO Save the export file in an option for safekeeping |
| | 2099 | |
| | 2100 | return $response; |
| | 2101 | } |
| | 2102 | |
| | 2103 | function wp_privacy_delete_old_export_files() { |
| | 2104 | |
| | 2105 | // TODO delete old export files and their options |
| | 2106 | |
| | 2107 | } |