With all the hullabaloo around the General Data Protection Regulation (GDPR), we are seeing several custom GDPR frameworks being built for WordPress, each of which has its own strengths. However, with the release of version 4.9.6, there are now powerful new tools built right into the core of WordPress to handle GDPR compliance that nobody seems to be talking about. In this article, I am going to explore with you how we can tap into WordPress’s new GDPR features in our existing plugins to let it handle the heavy lifting for us. I’ll walk you through a sample project below. You can get the code and database on GitHub here.
About the sample project
For this sample project, I am choosing to go a little outside the typical realm of WordPress and create some new database tables that will hold a to-do list. This could easily be accomplished (and perhaps should be!) with a custom post type (CPT), however, I am choosing to use custom tables to illustrate the flexibility of the new WordPress core GDPR compliance features. Additionally, this to-do list will be open to the general public and not tied to any specific WordPress user. Simply supply an email address to create and view the to-do item.
High-level project requirements:
- Create a to-do list with custom database tables
- To-do items can be created by supplying an email address and text of the to-do
- No login is required
- To-do lists will be viewable per the email address they are attached to
- Must use WordPress core GDPR features for data export and deletion
With this sample project, we will not be creating any UI elements to manage the to-do list. You can view, add, and edit items directly in your favorite database client (mine is Sequel Pro or the command line!). Note, too, that if this were a production app, it would also have a public facing UI.
The database
The custom database table will be named ‘todo_list’ and have a structure of:
- id
- status
- todo
- created
You can import a sample SQL file to use in your own sample project here.
Initial plugin set up
Our next step will be to set up the plugin. For this sample, I am going to keep it stupid simple (KISS) and will not be using plain functions. This is done intentionally to keep the focus on the what and how of working with the WordPress core GDPR functionality instead of focusing on PHP concepts. You are welcomed to use these functions however you would like in your own projects.
Our plugin is going to be called GDPR Todo List Sample, and the plugin folder that I will be referencing here is ‘wordpress-gdpr-todo-list-sample’.
Here is my plugin stub in: wordpress-gdpr-todo-list-sample.php
“`<?php
/**
* Plugin Name: GDPR Todo List Sample
* Description: Sample project using core WordPress GDPR functionality.
* Version: 1.0.0
* Author: Ben Lobaugh, WebDevStudios
* Author URI: https://webdevstudios.com
*/“`
New privacy hooks
WordPress has introduced new hooks for GDPR management called privacy hooks. Here is the full list of new hooks. We will only be focusing on the hooks in bold.
Actions
- wp_privacy_personal_data_export_file_created
- wp_privacy_personal_data_export_file
- wp_privacy_personal_data_erased
Filters
- wp_privacy_export_expiration
- wp_privacy_personal_data_email_content
- wp_privacy_personal_data_exporters
- wp_privacy_personal_data_erasers
- wp_privacy_personal_data_export_page
- wp_privacy_personal_data_erasure_page
- wp_privacy_anonymize_data
- wp_privacy_exports_dir
- wp_privacy_exports_url
- wp_privacy_export_expiration
Data exporter
First up, let’s set up the data exporter. This will add data from our to-do list to the export file when a user requests to download their data. We will be tapping into the wp_privacy_personal_data_exporters filter. The filter needs to return an array with a new entry that includes a name and a callback function as elements.
The following will set up the filter callback. The second parameter is the name of our function that will be called when an export is requested.add_filter( ‘wp_privacy_personal_data_exporters’, ‘register_todo_list_exporter’ );
Now, we need to set up the callback that will set up the exporter itself. This is accomplished in two steps. the first will set the name of the exporter and the second will be another callback function. This function will contain code that performs the actual export of data. It accepts an array of currently registered exporters as a parameter. We will add our exporter to this list:
function register_todo_list_exporter( $exporters ) {
$exporters[‘todo-list’] = array(
‘exporter_friendly_name’ => __( ‘Todo List’ ),
‘callback’ => ‘todo_list_exporter’,
);
return $exporters;
}
Next up, we need to write the function that creates the data to export. We will be doing a custom database query to retrieve the results. Please note that we are not including any SQLprotection here in order to keep the sample simple. You should always ensure you properly sanitize, escape, and otherwise clean up data before use in a real application.
The function definition
function todo_list_exporter( $email_address, $page = 1 ) { }
The first parameter will be the email address of the export request. This is how we will determine what data to retrieve from the database. The second parameter is optional and can be used for paging. In our simple example, we will not be using it.
Build the query
We will need to tap into $wpdb to retrieve data from our custom table. Be sure to retrieve only data associated with the email address provided by the exporter!
$sql = “SELECT * FROM `todo_list` WHERE email=’$email_address’”;
Create the export data
From here all we have to do is pass back an array of data for the exporter to put into the export file. This is where things get slightly complex. The exporter is going to pass back an array of arrays containing an array of data. Clear as mud? Good, let’s kick this beast.
Export item
A single element winds up looking similar to:
$export_item[] = array(
‘group_id’ => $group_id,
‘group_label’ => $group_label,
‘item_id’ => $id,
‘data’ => array()
);
To break that down some…
group_id is the id of the group of data for the exporter. It is simply a way for the exporter to ensure it keeps all similar data together. It is a freeform id field. We will use “todo-items” as our group id.
group_label is similar to the group_id except that it is the human readable version. This is what the person reading the export will see when they open it. It can also be global to this export. We will use “Todo List Items”.
item_id is the id field from the database for the individual todo enty.
data is an array of arrays of the data for the row with two elements, name and value. Name is the human readable version, value is of course the value. Here is an example:
$item_data = array(
array(
‘name’ => __( ‘Todo Item’ ),
‘value’ => $item->todo
),
array(
‘name’ => __( ‘Status’ ),
‘value’ => $item->status,
),
array(
‘name’ => __( ‘Created’ ),
‘value’ => $item->created
)
);
Putting that whole hairy mess together gives you
$export_items = array();
$group_id = ‘todo-items’;
$group_label = ‘Todo List Items’;
foreach( $list as $item ) {
$item_data = array(
array(
‘name’ => __( ‘Todo Item’ ),
‘value’ => $item->todo
),
array(
‘name’ => __( ‘Status’ ),
‘value’ => $item->status,
),
array(
‘name’ => __( ‘Created’ ),
‘value’ => $item->created
)
);
$export_items[] = array(
‘group_id’ => $group_id,
‘group_label’ => $group_label,
‘item_id’ => $item->id,
‘data’ => $item_data
);
}
Full code for the exporter
add_filter( ‘wp_privacy_personal_data_exporters’, ‘register_todo_list_exporter’ );
function register_todo_list_exporter( $exporters ) {
$exporters[‘todo-list’] = array(
‘exporter_friendly_name’ => __( ‘Todo List’ ),
‘callback’ => ‘todo_list_exporter’,
);
return $exporters;
}
function todo_list_exporter( $email_address, $page = 1 ) {
global $wpdb;
$sql = “SELECT * FROM `todo_list` WHERE email=’$email_address’”;
$list = $wpdb->get_results( $sql );
$export_items = array();
$group_id = ‘todo-items’;
$group_label = ‘Todo List Items’;
foreach( $list as $item ) {
$item_data = array(
array(
‘name’ => __( ‘Todo Item’ ),
‘value’ => $item->todo
),
array(
‘name’ => __( ‘Status’ ),
‘value’ => $item->status,
),
array(
‘name’ => __( ‘Created’ ),
‘value’ => $item->created
)
);
$export_items[] = array(
‘group_id’ => $group_id,
‘group_label’ => $group_label,
‘item_id’ => $item->id,
‘data’ => $item_data
);
}
return array(
‘data’ => $export_items,
‘done’ => true
);
}
Removing data
The second half of this post deals with removing data. We will again be using the new WordPress core privacy filters. This time the filter we will tap into is wp_privacy_personal_data_erasers.
You will notice the eraser filter set up looks very similar to the exporter. Here is the filter we will use:
add_filter( ‘wp_privacy_personal_data_erasers’, ‘register_todo_list_eraser’ );
Now, we need to set up the callback that will set up the eraser itself. This is accomplished in two steps. the first will set the name of the eraser and the second will be another callback function. This function will contain code that performs the actual deletion of data. It accepts an array of currently registered erasers as a parameter. We will add our eraser to this list.
function register_todo_list_eraser( $erasers ) {
$erasers[‘todo-list’] = array(
‘eraser_friendly_name’ => __( ‘Todo List’ ),
‘callback’ => ‘todo_list_eraser’
);
return $erasers;
}
Next up, we need to write the function that erases the data. We will be doing a custom database query to retrieve the results. Please note that we are not including any SQLprotection here in order to keep the sample simple. You should always ensure you properly sanitize, escape, and otherwise clean up data before use in a real application.
The function definition
function todo_list_eraser( $email_address, $page = 1 ) { }
Build the query
To erase the data we need to tap into $wpdb to query our custom table. Be sure to delete only data associated with the email address provided by the eraser!
$removed = $wpdb->delete( ‘todo_list’, array( ’email’ => ‘[email protected]’ ) );
Return the result
Finally, we return the result of the eraser.
return array(
‘items_removed’ => $removed,
‘items_retained’ => false,
‘messages’ => array(),
‘done’ => true,
);
The return array elements are:
items_removed: this is a boolean on whether items are removed. In this example I am passing back the result of $wpdb->delete() which could be an integer.WordPress interprets it properly for the truthiness.
items_retained: this is a boolean check as to whether any records were not removed from the database.
messages: an array of string messages that can be presented to the user if needed.
done: boolean on whether the erasure process has finished.
Wrapping it up
That is it! I hope this example has been useful to show you how you can implement code in your plugins to tap into the new privacy hooks in WordPress. It can be used for nearly anything stored in the database that can be referenced back to a user or email address.
You are now equipped to confidently go forth and tackle all the new GDPR compliance needs using only WordPress core functionality.