WordPress.org

Make WordPress Core

Ticket #43546: EXPORT.md

File EXPORT.md, 7.2 KB (added by allendav, 2 years ago)

Markdown document explaining how this all works together

Line 
1# How to Connect Your Plugin to Core's New Personal Data Exporter
2
3## Background
4
5In WordPress 4.9.5, new tools were added to make compliance with laws like the
6European Union's General Data Protection Regulation, or GDPR for short. Among
7the tools added is a Personal Data Export tool which supports exporting all
8the personal data for a given user in a ZIP file.
9
10In addition to the personal data stored in things like WordPress comments,
11plugins can also hook into the exporter feature to export the personal
12data they collect, whether it be in something like postmeta or even an
13entirely new Custom Post Type (CPT).
14
15## How It Works
16
17The "key" for all the exports is the user's email address - this was chosen
18because it supports exporting personal data for both full-fledged registered
19users and also unregistered users (e.g. like a logged out commenter).
20
21However, since assembling a personal data export could be an intensive
22process and will likely contain sensitive data, we don't want to just
23generate it and email it to the requestor without confirming the request, so
24the admin-facing starts all requests by having the admin enter the username
25or email address making the request and then sends then a link to click
26to confirm their request.
27
28A list of requests and whether they have been confirmed is available to
29the administrator in the same user interface.  Once a request has been
30confirmed, the admin can generate and download or directly email the
31personal data export ZIP file for the user.
32
33Inside the ZIP file the user receives, they will find a "mini website"
34with an index HTML page containing their personal data organized in
35groups (e.g. a group for comments, etc. )
36
37## Design Internals
38
39Whether the admin downloads the personal data export ZIP file or sends
40it directly to the requestor, the way the personal data export is
41assembled is identical - and relies on hooking "exporter" callbacks to
42do the dirty work of collecting all the data for the export.
43
44When the admin clicks on the download or email link, an AJAX loop begins
45that iterates over all the exporters registered in the system, one at a time.
46In addition to exporters built into core, plugins can register their own
47exporter callbacks.
48
49The exporter callback interface is designed to be as simple as possible.
50A exporter callback receives the email address we are working with,
51and a page parameter as well. The page parameter (which starts at 1) is
52used to avoid plugins potentially causing timeouts by attempting to export
53all the personal data they've collected at once.
54
55The exporter callback replies with whatever data it has for that
56email address and page and whether it is done or not. If a exporter
57callback reports that it is not done, it will be called again (in a
58separate request) with the page parameter incremented by 1.
59
60Exporter callbacks are expected to return an array of items for the
61export. Each item contains an a group identifier for the group of which
62the item is a part (e.g. comments, posts, orders, etc.), an optional group
63label (translated), an item identifier (e.g. comment-133) and then an array of
64name, value pairs containing the data to be exported for that item.
65
66It is noteworthy that the value could be a media path, in which case the
67media file will be added to the exported ZIP file with a link in the
68"mini website" "index" HTML document to it.
69
70When all the exporters have been called to completion, core first assembles
71an "index" HTML document that serves as the heart of the export report.
72First, it walks the aggregate data and finds all the groups that core
73and plugins have identified.
74
75Then, for each group, it walks the data and finds all the items, using
76their item identifier to collect all the data for a given
77item (e.g. comment-133) from all the exporters into a single entry for the
78report.  That way, core and plugins can all contribute data for the same
79item (e.g. a plugin may add location information to comments) and in the
80final export, all the data for a given item (e.g. comment 133) will be
81presented together.
82
83All of this is rendered into the HTML document and then the HTML document
84is zipped with any media attachments before being returned to the
85admin or emailed to the user. Exports are cached on the server for 1 day and
86then deleted.
87
88## What to Do
89
90A plugin can register one or more exporters, but most plugins will only
91need one. Let's work from the example given above where a plugin adds
92location data for the commenter to comments.
93
94First, let's assume the plugin has used `add_comment_meta` to add location
95data using `$meta_key` of `location`
96
97The first thing the plugin needs to do is to create an exporter function
98that accepts an email address and a page, e.g.:
99
100```
101function my_plugin_exporter( $email_address, $page = 1 ) {
102  $number = 500; // Limit us to avoid timing out
103  $page = (int) $page;
104
105  $export_items = array();
106
107  $comments = get_comments(
108          array(
109            'author_email' => $email_address,
110            'number'       => $number,
111            'paged'        => $page,
112            'order_by'     => 'comment_ID',
113            'order'        => 'ASC',
114            )
115        );
116
117  foreach ( (array) $comments as $comment ) {
118    $location = get_comment_meta( $comment->comment_ID, 'location', true );
119
120    // Only add location data to the export if it is not empty
121    if ( ! empty( $location ) ) {
122      // Most item IDs should look like postType-postID
123      // If you don't have a post, comment or other ID to work with,
124      // use a unique value to avoid having this item's export
125      // combined in the final report with other items of the same id
126      $item_id = "comment-{$comment->comment_ID}";
127
128      // Core group IDs include 'comments', 'posts', etc.
129      // But you can add your own group IDs as needed
130      $group_id = 'comments';
131
132      // Optional group label. Core provides these for core groups.
133      // If you define your own group, the first exporter to
134      // include a label will be used as the group label in the
135      // final exported report
136      $group_label = __( 'Comments' );
137
138      // This plugin only has one item to offer, but plugins
139      // can add as many items in the item data array as they want
140      $data = array(
141        'name'  => __( 'Commenter Location' ),
142        'value' => $location
143      );
144
145      $export_items[] = array(
146            'group_id'    => $group_id,
147            'group_label' => $group_label,
148            'item_id'     => $item_id,
149            'data'        => $data,
150            );
151    }
152  }
153
154  // Tell core if we have more comments to work on still
155  $done = count( $comments ) < $number;
156
157  return array(
158    'data' => $export_items,
159    'done' => $done,
160  );
161}
162```
163
164The next thing the plugin needs to do is to register the callback by
165filtering the exporter array using the `wp_privacy_personal_data_exporters`
166filter.
167
168When registering you provide a friendly name for the export (to aid in
169debugging - this friendly name is not shown to anyone at this time)
170and the callback, e.g.
171
172```
173function register_my_plugin_exporter( $exporters ) {
174  $exporters[] = array(
175    'exporter_friendly_name' => __( 'Comment Location Plugin' ),
176    'callback'               => 'my_plugin_exporter',
177    );
178  return $exporters;
179}
180
181add_filter(
182  'wp_privacy_personal_data_exporters',
183  'register_my_plugin_exporter',
184  10
185);
186```
187
188And that's all there is to it! Your plugin will now provide data
189for the export!