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 whether items containing personal data were |
---|
49 | erased, whether any items containing personal data were 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 | $items_removed = false; |
---|
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 | $items_removed = true; |
---|
97 | } |
---|
98 | |
---|
99 | if ( ! empty( $longitude ) ) { |
---|
100 | delete_comment_meta( $comment->comment_ID, 'longitude' ); |
---|
101 | $items_removed = true; |
---|
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 | 'items_removed' => $items_removed, |
---|
110 | '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! |
---|