函数文档

wp_privacy_generate_personal_data_export_file()

💡 云策文档标注

概述

wp_privacy_generate_personal_data_export_file() 是 WordPress 中用于生成个人数据导出文件的函数,它处理导出请求,创建 JSON 和 HTML 格式的报告,并打包为 ZIP 文件。该函数包含参数验证、文件生成和错误处理机制。

关键要点

  • 函数参数:接受一个必需的整数参数 $request_id,表示导出请求的 ID。
  • 依赖检查:需要 ZipArchive 类支持,否则返回 JSON 错误。
  • 请求验证:通过 wp_get_user_request() 验证请求 ID 和操作类型是否为 'export_personal_data'。
  • 邮箱验证:使用 is_email() 检查请求中的邮箱地址是否有效。
  • 导出目录管理:使用 wp_privacy_exports_dir() 和 wp_privacy_exports_url() 创建和保护导出文件夹。
  • 数据分组处理:从 post meta 获取分组数据,并合并一个特殊的“关于”组,然后转换为 JSON 格式。
  • 文件生成:分别创建 JSON 和 HTML 报告文件,并写入格式化数据。
  • ZIP 打包:使用 ZipArchive 将报告文件打包,处理文件名和路径的元数据更新。
  • 钩子触发:在文件创建后触发 wp_privacy_personal_data_export_file_created 动作钩子。
  • 错误处理:在关键步骤中,如文件操作失败,使用 wp_send_json_error() 返回错误信息。

代码示例

// 示例调用函数生成个人数据导出文件
$request_id = 123; // 假设的导出请求 ID
wp_privacy_generate_personal_data_export_file( $request_id );

注意事项

  • 确保服务器环境支持 ZipArchive 扩展,否则函数会失败。
  • 函数内部使用 wp_send_json_error() 处理错误,适合在 Ajax 上下文中调用。
  • 导出文件存储在 wp_privacy_exports_dir() 指定的目录中,需确保目录可写。
  • 函数会清理临时生成的 JSON 和 HTML 文件,只保留最终的 ZIP 文件。
  • 自 WordPress 4.9.6 版本引入,兼容性需考虑。

📄 原文内容

Generate the personal data export file.

Parameters

$request_idintrequired
The export request ID.

Source

function wp_privacy_generate_personal_data_export_file( $request_id ) {
	if ( ! class_exists( 'ZipArchive' ) ) {
		wp_send_json_error( __( 'Unable to generate personal data export file. ZipArchive not available.' ) );
	}

	// Get the request.
	$request = wp_get_user_request( $request_id );

	if ( ! $request || 'export_personal_data' !== $request->action_name ) {
		wp_send_json_error( __( 'Invalid request ID when generating personal data export file.' ) );
	}

	$email_address = $request->email;

	if ( ! is_email( $email_address ) ) {
		wp_send_json_error( __( 'Invalid email address when generating personal data export file.' ) );
	}

	// Create the exports folder if needed.
	$exports_dir = wp_privacy_exports_dir();
	$exports_url = wp_privacy_exports_url();

	if ( ! wp_mkdir_p( $exports_dir ) ) {
		wp_send_json_error( __( 'Unable to create personal data export folder.' ) );
	}

	// Protect export folder from browsing.
	$index_pathname = $exports_dir . 'index.php';
	if ( ! file_exists( $index_pathname ) ) {
		$file = fopen( $index_pathname, 'w' );
		if ( false === $file ) {
			wp_send_json_error( __( 'Unable to protect personal data export folder from browsing.' ) );
		}
		fwrite( $file, " _x( 'About', 'personal data group label' ),
		/* translators: Description for the About section in a personal data export. */
		'group_description' => _x( 'Overview of export report.', 'personal data group description' ),
		'items'             => array(
			'about-1' => array(
				array(
					'name'  => _x( 'Report generated for', 'email address' ),
					'value' => $email_address,
				),
				array(
					'name'  => _x( 'For site', 'website name' ),
					'value' => get_bloginfo( 'name' ),
				),
				array(
					'name'  => _x( 'At URL', 'website URL' ),
					'value' => get_bloginfo( 'url' ),
				),
				array(
					'name'  => _x( 'On', 'date/time' ),
					'value' => current_time( 'mysql' ),
				),
			),
		),
	);

	// And now, all the Groups.
	$groups = get_post_meta( $request_id, '_export_data_grouped', true );
	if ( is_array( $groups ) ) {
		// Merge in the special "About" group.
		$groups       = array_merge( array( 'about' => $about_group ), $groups );
		$groups_count = count( $groups );
	} else {
		if ( false !== $groups ) {
			_doing_it_wrong(
				__FUNCTION__,
				/* translators: %s: Post meta key. */
				sprintf( __( 'The %s post meta must be an array.' ), '<code>_export_data_grouped</code>' ),
				'5.8.0'
			);
		}

		$groups       = null;
		$groups_count = 0;
	}

	// Convert the groups to JSON format.
	$groups_json = wp_json_encode( $groups );

	if ( false === $groups_json ) {
		$error_message = sprintf(
			/* translators: %s: Error message. */
			__( 'Unable to encode the personal data for export. Error: %s' ),
			json_last_error_msg()
		);

		wp_send_json_error( $error_message );
	}

	/*
	 * Handle the JSON export.
	 */
	$file = fopen( $json_report_pathname, 'w' );

	if ( false === $file ) {
		wp_send_json_error( __( 'Unable to open personal data export file (JSON report) for writing.' ) );
	}

	fwrite( $file, '{' );
	fwrite( $file, '"' . $title . '":' );
	fwrite( $file, $groups_json );
	fwrite( $file, '}' );
	fclose( $file );

	/*
	 * Handle the HTML export.
	 */
	$file = fopen( $html_report_pathname, 'w' );

	if ( false === $file ) {
		wp_send_json_error( __( 'Unable to open personal data export (HTML report) for writing.' ) );
	}

	fwrite( $file, "n" );
	fwrite( $file, "<html>n" );
	fwrite( $file, "<head>n" );
	fwrite( $file, "<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />n" );
	fwrite( $file, "<style type='text/css'>" );
	fwrite( $file, 'body { color: black; font-family: Arial, sans-serif; font-size: 11pt; margin: 15px auto; width: 860px; }' );
	fwrite( $file, 'table { background: #f0f0f0; border: 1px solid #ddd; margin-bottom: 20px; width: 100%; }' );
	fwrite( $file, 'th { padding: 5px; text-align: left; width: 20%; }' );
	fwrite( $file, 'td { padding: 5px; }' );
	fwrite( $file, 'tr:nth-child(odd) { background-color: #fafafa; }' );
	fwrite( $file, '.return-to-top { text-align: right; }' );
	fwrite( $file, '</style>' );
	fwrite( $file, '<title>' );
	fwrite( $file, esc_html( $title ) );
	fwrite( $file, '</title>' );
	fwrite( $file, "</head>n" );
	fwrite( $file, "<body>n" );
	fwrite( $file, '<h1 id="top">' . esc_html__( 'Personal Data Export' ) . '</h1>' );

	// Create TOC.
	if ( $groups_count > 1 ) {
		fwrite( $file, '<div id="table_of_contents">' );
		fwrite( $file, '<h2>' . esc_html__( 'Table of Contents' ) . '</h2>' );
		fwrite( $file, '<ul>' );
		foreach ( (array) $groups as $group_id => $group_data ) {
			$group_label       = esc_html( $group_data['group_label'] );
			$group_id_attr     = sanitize_title_with_dashes( $group_data['group_label'] . '-' . $group_id );
			$group_items_count = count( (array) $group_data['items'] );
			if ( $group_items_count > 1 ) {
				$group_label .= sprintf( ' <span class="count">(%d)</span>', $group_items_count );
			}
			fwrite( $file, '<li>' );
			fwrite( $file, '<a href="#' . esc_attr( $group_id_attr ) . '">' . $group_label . '</a>' );
			fwrite( $file, '</li>' );
		}
		fwrite( $file, '</ul>' );
		fwrite( $file, '</div>' );
	}

	// Now, iterate over every group in $groups and have the formatter render it in HTML.
	foreach ( (array) $groups as $group_id => $group_data ) {
		fwrite( $file, wp_privacy_generate_personal_data_export_group_html( $group_data, $group_id, $groups_count ) );
	}

	fwrite( $file, "</body>n" );
	fwrite( $file, "</html>n" );
	fclose( $file );

	/*
	 * Now, generate the ZIP.
	 *
	 * If an archive has already been generated, then remove it and reuse the filename,
	 * to avoid breaking any URLs that may have been previously sent via email.
	 */
	$error = false;

	// This meta value is used from version 5.5.
	$archive_filename = get_post_meta( $request_id, '_export_file_name', true );

	// This one stored an absolute path and is used for backward compatibility.
	$archive_pathname = get_post_meta( $request_id, '_export_file_path', true );

	// If a filename meta exists, use it.
	if ( ! empty( $archive_filename ) ) {
		$archive_pathname = $exports_dir . $archive_filename;
	} elseif ( ! empty( $archive_pathname ) ) {
		// If a full path meta exists, use it and create the new meta value.
		$archive_filename = basename( $archive_pathname );

		update_post_meta( $request_id, '_export_file_name', $archive_filename );

		// Remove the back-compat meta values.
		delete_post_meta( $request_id, '_export_file_url' );
		delete_post_meta( $request_id, '_export_file_path' );
	} else {
		// If there's no filename or full path stored, create a new file.
		$archive_filename = $file_basename . '.zip';
		$archive_pathname = $exports_dir . $archive_filename;

		update_post_meta( $request_id, '_export_file_name', $archive_filename );
	}

	$archive_url = $exports_url . $archive_filename;

	if ( ! empty( $archive_pathname ) && file_exists( $archive_pathname ) ) {
		wp_delete_file( $archive_pathname );
	}

	$zip = new ZipArchive();
	if ( true === $zip->open( $archive_pathname, ZipArchive::CREATE ) ) {
		if ( ! $zip->addFile( $json_report_pathname, 'export.json' ) ) {
			$error = __( 'Unable to archive the personal data export file (JSON format).' );
		}

		if ( ! $zip->addFile( $html_report_pathname, 'index.html' ) ) {
			$error = __( 'Unable to archive the personal data export file (HTML format).' );
		}

		$zip->close();

		if ( ! $error ) {
			/**
			 * Fires right after all personal data has been written to the export file.
			 *
			 * @since 4.9.6
			 * @since 5.4.0 Added the `$json_report_pathname` parameter.
			 *
			 * @param string $archive_pathname     The full path to the export file on the filesystem.
			 * @param string $archive_url          The URL of the archive file.
			 * @param string $html_report_pathname The full path to the HTML personal data report on the filesystem.
			 * @param int    $request_id           The export request ID.
			 * @param string $json_report_pathname The full path to the JSON personal data report on the filesystem.
			 */
			do_action( 'wp_privacy_personal_data_export_file_created', $archive_pathname, $archive_url, $html_report_pathname, $request_id, $json_report_pathname );
		}
	} else {
		$error = __( 'Unable to open personal data export file (archive) for writing.' );
	}

	// Remove the JSON file.
	unlink( $json_report_pathname );

	// Remove the HTML file.
	unlink( $html_report_pathname );

	if ( $error ) {
		wp_send_json_error( $error );
	}
}

Hooks

do_action( ‘wp_privacy_personal_data_export_file_created’, string $archive_pathname, string $archive_url, string $html_report_pathname, int $request_id, string $json_report_pathname )

Fires right after all personal data has been written to the export file.

Changelog

Version Description
4.9.6 Introduced.