Personal Data Exporters and Erasers

This is more like an advanced tutorial, I mean all we are going to do here is to add and customise the code. So, if you are just looking for more basic information about the WordPress personal data tools, I recommend to watch this video first:

[youtube_embed]https://www.youtube.com/embed/rYy1rEiJZsQ[/youtube_embed]

Register your Personal Data Exporter

Let’s talk a little bit about Tools > Export Personal Data. It is build to help you in a situation when somebody who used your website for a while requested all his personal data collected by you.

As I said in the video before, everything becomes very simple with the new Personal Data Exporter tool introduced in WordPress 4.9.6.

But what if you have your custom plugin or just the code in your theme – doesn’t matter, which operates your users personal data? This personal data won’t be added to the exported file automatically. What to do in this case? Example:

How to export additional personal data added by plugins, themes or with some custom code?
This is just an example of Orders custom post type where customer names and emails are stored in custom fields.

Is there a way to export this additional personal data? Manually? Surely no.

Below is the solution – I’m going to share with you how to extend WordPress default exporter and eraser tools for that test Orders custom post type.


add_filter( 'wp_privacy_personal_data_exporters', 'misha_register_exporter', 10);

function misha_register_exporter( $exporters_array ) {
	$exporters_array['misha_exporter'] = array(
		'exporter_friendly_name' => 'Misha exporter', // isn't shown anywhere
		'callback' => 'misha_exporter_function', // name of the callback function which is below
	);
	return $exporters_array;
}

/*
 * Callback function which processes the export
 */
function misha_exporter_function( $email_address, $iteration = 1 ) {

	$iteration = (int) $iteration;

	$export_items = array();

	if( $orders = get_posts( array(
		'post_type' => 'my_order',
		'posts_per_page' => 100, // how much to process each time
		'paged' => $iteration,
		'meta_key' => 'customer_email',
		'meta_value' => $email_address
	) ) ) {

		foreach ( (array) $orders as $order ){

			// here you can specify the fields, that exist in any way
			$data = array(
				array(
					'name' => 'Order ID',
					'value' => $order->ID
				),
				array(
					'name' => 'Full Name',
					'value' => get_post_meta( $order->ID, 'customer_name', true )
				),
				array(
					'name' => 'Email',
					'value' => $email_address
				),
				array(
					'name' => 'Amount',
					'value' => get_post_meta( $order->ID, 'order_amount', true )
				)
			);

			// let's say that it is the additional field, which is not always populated
			if( $country = get_post_meta( $order->ID, 'customer_country', true ) ) {
				$data[] = array(
					'name' => 'Country',
					'value' => $country
				);
			}


			$export_items[] = array(
 				'group_id' => 'orders',
				'group_label' => 'Orders',
				'item_id' => 'order-'.$order->ID,
 				'data' => $data
			);
		}
	}

	// Tell core if we have more orders to work on still
	$done = count( $orders ) < 100;
	return array(
		'data' => $export_items,
		'done' => $done,
	);
}

Some notes for the above code:

Please note also, that the report will be created only when you click a “Download Personal Data” button or email the data.

Once the export file is created, another action hook will be fired:


add_action( 'wp_privacy_personal_data_export_file_created', 'misha_export_file_created', 20, 4 );

function misha_export_file_created( $archive_pathname, $archive_url, $html_report_pathname, $request_id ) {

	// do stuff

}

Read below about wp_get_user_request_data( $request_id ); as well.

Exported files directory. How to change it?

By default WordPress stores all the files with the exported personal data in {UPLOADS DIR}/wp-personal-data-exports.

If you want to change your export directory, just run the above function for both wp_privacy_exports_dir and wp_privacy_exports_url filter hooks.

Exported files expiration period

WordPress has a scheduled action wp_privacy_delete_old_export_files now which runs every hour and it can not be changed.

There is a function connected to the hook, which checks the privacy exports directory for the files and removes anything older than 3 days.

3 days period can be changed with wp_privacy_export_expiration filter hook. For example below I change it to 1 day.


add_filter('wp_privacy_export_expiration', 'misha_custom_exp_period' );
function misha_custom_exp_period( $seconds ) {
	return 86400; // One day
}

Remember, that since WordPress 3.5.0 you can use time constants like DAY_IN_SECONDS, HOUR_IN_SECONDS etc.

Register your Personal Data Erasers

In most cases you do not have to remove personal data, it would be enough just to anonymise it. Following my previous example with exporting orders, I created a personal data eraser for the WordPress tool below.


add_filter( 'wp_privacy_personal_data_erasers', 'misha_register_my_eraser', 10 );

function misha_register_my_eraser( $erasers ) {
	$erasers['misha_eraser'] = array(
		'eraser_friendly_name' => 'Misha eraser', // anything
		'callback' => 'misha_eraser_function', // callback
	);
	return $erasers;
}

/*
 * This callback function processes erasure requests
 */
function misha_eraser_function( $email_address, $iteration = 1 ) {

	$iteration = (int) $iteration;
	$items_removed = false;
	
	if( $orders = get_posts(
		array(
			'post_type' => 'my_order',
			'posts_per_page' => 100, // how much order do you want to process for a single term
			'paged' => $iteration,
			'meta_key' => 'customer_email',
			'meta_value' => $email_address
		)
	) ) {

		foreach ( (array) $orders as $order ){

			// you can remove orders completely if you do not need them
			//wp_delete_post( $order->ID, true ); // parameter true means permanent deletion without trash

			// you can delete or anonymise the fields
			delete_post_meta($order->ID, 'customer_email');
			update_post_meta($order->ID, 'customer_name','Anonymous');
	
			// if something was changed or removed in this iteration, set to "true"
			$items_removed = true;

		}
	}

	$done = count( $orders ) < $iteration;
	return array(
		'items_removed' => $items_removed,
		'items_retained' => false,
		'messages' => array(''), // you can add any custom message to be shown in /wp-admin/
		'done' => $done,
	);

}

One more hook…

Once all the erasure operations have been completed, wp_privacy_personal_data_erased action hook will be fired. By the way the user notifications are also connected to this hook. Example:


add_action( 'wp_privacy_personal_data_erased', 'misha_do_smth_after_data_erasure' );
function misha_do_smth_after_data_erasure( $request_id ) {
	// do something
}

By the way you can use wp_get_user_request_data( $request_id ); to get all the request data as an object. Example:


$request_data = wp_get_user_request_data( $request_id );
// print_r( $request_data);
echo $request->email;

As always, if you have any question or suggestion, welcome to comments 👇

Misha Rudrastyh

Misha Rudrastyh

Hey guys and welcome to my website. For more than 10 years I've been doing my best to share with you some superb WordPress guides and tips for free.

Need some developer help? Contact me

Follow me on Twitter