| 1 | # How to Connect Your Plugin to Core's New Personal Data Eraser
|
|---|
| 2 |
|
|---|
| 3 | ## Background
|
|---|
| 4 |
|
|---|
| 5 | In WordPress 4.9.x, new tools were added to make compliance easier with laws
|
|---|
| 6 | like the European Union's General Data Protection Regulation, or GDPR for
|
|---|
| 7 | short. Among the tools added is a Personal Data Removal tool which supports
|
|---|
| 8 | erasing/anonymizing personal data for a given user in a ZIP file.
|
|---|
| 9 |
|
|---|
| 10 | In addition to the personal data stored in things like WordPress comments,
|
|---|
| 11 | plugins can also hook into the eraser feature to erase the personal
|
|---|
| 12 | data they collect, whether it be in something like postmeta or even an
|
|---|
| 13 | entirely new Custom Post Type (CPT).
|
|---|
| 14 |
|
|---|
| 15 | ## How It Works
|
|---|
| 16 |
|
|---|
| 17 | Like the exporters, the "key" for all the erasers is the user's email address -
|
|---|
| 18 | this was chosen because it supports erasing personal data for both full-fledged
|
|---|
| 19 | registered users and also unregistered users (e.g. like a logged out commenter).
|
|---|
| 20 |
|
|---|
| 21 | However, since performing a personal data erase is a destructive process, we
|
|---|
| 22 | don't want to just do it without confirming the request, so the admin-facing
|
|---|
| 23 | UX starts all requests by having the admin enter the username or email address
|
|---|
| 24 | making the request and then sends then a link to click to confirm their
|
|---|
| 25 | request.
|
|---|
| 26 |
|
|---|
| 27 | A list of requests and whether they have been confirmed is available to
|
|---|
| 28 | the administrator in the same user interface. Once a request has been
|
|---|
| 29 | confirmed, the admin can kick off personal data erasure for the user.
|
|---|
| 30 |
|
|---|
| 31 | ## Design Internals
|
|---|
| 32 |
|
|---|
| 33 | The way the personal data export is erased is similar to how the personal data
|
|---|
| 34 | exporters - and relies on hooking "eraser" callbacks to do the dirty work
|
|---|
| 35 | of erasing the data.
|
|---|
| 36 |
|
|---|
| 37 | When the admin clicks on the remove personal data link, an AJAX loop begins
|
|---|
| 38 | that iterates over all the erasers registered in the system, one at a time.
|
|---|
| 39 | In addition to erasers built into core, plugins can register their own
|
|---|
| 40 | eraser callbacks.
|
|---|
| 41 |
|
|---|
| 42 | The eraser callback interface is designed to be as simple as possible.
|
|---|
| 43 | An eraser callback receives the email address we are working with,
|
|---|
| 44 | and a page parameter as well. The page parameter (which starts at 1) is
|
|---|
| 45 | used to avoid plugins potentially causing timeouts by attempting to erase
|
|---|
| 46 | all the personal data they've collected at once.
|
|---|
| 47 |
|
|---|
| 48 | The eraser callback replies with a count of items containing personal data that
|
|---|
| 49 | it erased, a count of items containing personal data that was retained, an
|
|---|
| 50 | array of messages to present to the admin (explaining why items that were
|
|---|
| 51 | retained were retained) and whether it is done or not. If an eraser
|
|---|
| 52 | callback reports that it is not done, it will be called again (in a
|
|---|
| 53 | separate request) with the page parameter incremented by 1.
|
|---|
| 54 |
|
|---|
| 55 | When all the exporters have been called to completion, the UX is updated to
|
|---|
| 56 | show whether or not all personal data found was erased, and any messages
|
|---|
| 57 | explaining why personal data was retained.
|
|---|
| 58 |
|
|---|
| 59 | ## What to Do
|
|---|
| 60 |
|
|---|
| 61 | A plugin can register one or more erasers, but most plugins will only
|
|---|
| 62 | need one. Let's work on a hypothetical plugin which adds commenter location
|
|---|
| 63 | data to comments.
|
|---|
| 64 |
|
|---|
| 65 | Let's assume the plugin has used `add_comment_meta` to add location
|
|---|
| 66 | data using `meta_key`s of `latitude` and `longitude`
|
|---|
| 67 |
|
|---|
| 68 | The first thing the plugin needs to do is to create an eraser function
|
|---|
| 69 | that accepts an email address and a page, e.g.:
|
|---|
| 70 |
|
|---|
| 71 | ```
|
|---|
| 72 | function my_plugin_eraser( $email_address, $page = 1 ) {
|
|---|
| 73 | $number = 500; // Limit us to avoid timing out
|
|---|
| 74 | $page = (int) $page;
|
|---|
| 75 |
|
|---|
| 76 | $export_items = array();
|
|---|
| 77 |
|
|---|
| 78 | $comments = get_comments(
|
|---|
| 79 | array(
|
|---|
| 80 | 'author_email' => $email_address,
|
|---|
| 81 | 'number' => $number,
|
|---|
| 82 | 'paged' => $page,
|
|---|
| 83 | 'order_by' => 'comment_ID',
|
|---|
| 84 | 'order' => 'ASC',
|
|---|
| 85 | )
|
|---|
| 86 | );
|
|---|
| 87 |
|
|---|
| 88 | $num_items_removed = 0;
|
|---|
| 89 |
|
|---|
| 90 | foreach ( (array) $comments as $comment ) {
|
|---|
| 91 | $latitude = get_comment_meta( $comment->comment_ID, 'latitude', true );
|
|---|
| 92 | $longitude = get_comment_meta( $comment->comment_ID, 'longitude', true );
|
|---|
| 93 |
|
|---|
| 94 | if ( ! empty( $latitude ) ) {
|
|---|
| 95 | delete_comment_meta( $comment->comment_ID, 'latitude' );
|
|---|
| 96 | $num_items_removed++;
|
|---|
| 97 | }
|
|---|
| 98 |
|
|---|
| 99 | if ( ! empty( $longitude ) ) {
|
|---|
| 100 | delete_comment_meta( $comment->comment_ID, 'longitude' );
|
|---|
| 101 | $num_items_removed++;
|
|---|
| 102 | }
|
|---|
| 103 | }
|
|---|
| 104 |
|
|---|
| 105 | // Tell core if we have more comments to work on still
|
|---|
| 106 | $done = count( $comments ) < $number;
|
|---|
| 107 |
|
|---|
| 108 | return array(
|
|---|
| 109 | 'num_items_removed' => $num_items_removed,
|
|---|
| 110 | 'num_items_retained' => 0, // always 0 in this example
|
|---|
| 111 | 'messages' => array(), // no messages in this example
|
|---|
| 112 | 'done' => $done,
|
|---|
| 113 | );
|
|---|
| 114 | }
|
|---|
| 115 | ```
|
|---|
| 116 |
|
|---|
| 117 | The next thing the plugin needs to do is to register the callback by
|
|---|
| 118 | filtering the eraser array using the `wp_privacy_personal_data_erasers`
|
|---|
| 119 | filter.
|
|---|
| 120 |
|
|---|
| 121 | When registering you provide a friendly name for the eraser (to aid in
|
|---|
| 122 | debugging - this friendly name is not shown to anyone at this time)
|
|---|
| 123 | and the callback, e.g.
|
|---|
| 124 |
|
|---|
| 125 | ```
|
|---|
| 126 | function register_my_plugin_eraser( $erasers ) {
|
|---|
| 127 | $erasers[] = array(
|
|---|
| 128 | 'eraser_friendly_name' => __( 'Comment Location Plugin' ),
|
|---|
| 129 | 'callback' => 'my_plugin_eraser',
|
|---|
| 130 | );
|
|---|
| 131 | return $erasers;
|
|---|
| 132 | }
|
|---|
| 133 |
|
|---|
| 134 | add_filter(
|
|---|
| 135 | 'wp_privacy_personal_data_erasers',
|
|---|
| 136 | 'register_my_plugin_eraser',
|
|---|
| 137 | 10
|
|---|
| 138 | );
|
|---|
| 139 | ```
|
|---|
| 140 |
|
|---|
| 141 | And that's all there is to it! Your plugin will now clean up its personal
|
|---|
| 142 | data!
|
|---|