Skip to content

feat(privacy): add check for missing wp_privacy_personal_data_exporters registration#1292

Open
faisalahammad wants to merge 3 commits into
WordPress:trunkfrom
faisalahammad:feature/1251-personal-data-exporter-check
Open

feat(privacy): add check for missing wp_privacy_personal_data_exporters registration#1292
faisalahammad wants to merge 3 commits into
WordPress:trunkfrom
faisalahammad:feature/1251-personal-data-exporter-check

Conversation

@faisalahammad

@faisalahammad faisalahammad commented May 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a new static check, Personal_Data_Exporter_Check, that warns plugin authors when their plugin stores personal data (user meta, comment meta, or direct DB writes) but does not register an exporter callback via the wp_privacy_personal_data_exporters filter. Since WordPress 4.9.6, plugins handling personal data are expected to hook into the Personal Data Export tool so site admins can fulfill GDPR data export requests.

Fixes #1251

Changes

includes/Checker/Checks/Plugin_Repo/Personal_Data_Exporter_Check.php (new)

A two-phase static file check:

  1. Scan PHP files for personal-data signals (add_user_meta, update_user_meta, add_comment_meta, update_comment_meta, $wpdb->insert/update/replace)
  2. Only if signals are found, check for add_filter( 'wp_privacy_personal_data_exporters', ... ) — if absent, emit a severity-5 warning

The two-step approach avoids false positives on plugins that don't touch personal data at all.

includes/Checker/Default_Check_Repository.php

After:

'wp_functions_compatibility' => new Checks\Plugin_Repo\WP_Functions_Compatibility_Check(),
'personal_data_exporter'     => new Checks\Plugin_Repo\Personal_Data_Exporter_Check(),

Why: Registers the check under the Plugin_Repo category so it runs by default alongside other plugin directory compliance checks.

Testing

Test 1: Plugin stores user meta, no exporter registered (expects warning)

  1. Install and activate the test plugin that calls update_user_meta() with no exporter filter
  2. Run Plugin Check on it
  3. Result: Warning with code missing_personal_data_exporter appears ✅

Test 2: Plugin stores user meta, exporter registered (expects clean)

  1. Install and activate the test plugin that calls update_user_meta() and registers add_filter( 'wp_privacy_personal_data_exporters', ... )
  2. Run Plugin Check on it
  3. Result: No missing_personal_data_exporter warning ✅

Test 3: Plugin has no personal data handling (expects clean)

  1. Install and activate a plugin with no user meta / comment meta / DB writes
  2. Run Plugin Check on it
  3. Result: No missing_personal_data_exporter warning ✅

PHPUnit test class added at tests/phpunit/tests/Checker/Checks/Personal_Data_Exporter_Check_Tests.php covering all three scenarios.

Open WordPress Playground Preview

Add a new static check that warns plugin authors when their plugin
handles personal data (user meta, comment meta, direct DB writes)
but does not register a callback via the wp_privacy_personal_data_exporters
filter.

- New check class: Personal_Data_Exporter_Check
- Registered in Default_Check_Repository under 'personal_data_exporter'
- PHPUnit test class with three test cases
- Test data plugins (with and without exporter registration)

Fixes WordPress#1251
@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: faisalahammad <faisalahammad@git.wordpress.org>
Co-authored-by: AndriusBurba <andriusburba94@git.wordpress.org>
Co-authored-by: masteradhoc <masteradhoc@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@AndriusBurba

Copy link
Copy Markdown

Thanks for tackling this, @faisalahammad! I tested the branch locally in wp-env against a few plugins. The update_user_meta happy paths work as expected. A few findings:

$wpdb detection never fires. The PERSONAL_DATA_PATTERN starts the alternation with \b, but $ is a non-word character, so \b\$wpdb only matches when $wpdb is preceded by a word character, which never happens in normal code. preg_match on $wpdb->insert( returns no match (it matches only in a contrived case like foo$wpdb->insert(). So direct DB writes, which are called out in both the description and #1251, aren't detected at all. The PHPUnit tests don't catch this because none of them exercise the $wpdb path. One fix is to move the \b onto just the function name group, e.g. '/(?:\b(?:add_user_meta|update_user_meta|add_comment_meta|update_comment_meta)|\$wpdb\s*->\s*(?:insert|update|replace))\s*\(/'.

False positives from matching raw text. Because the check runs regex over file contents rather than tokens: (a) a plugin that mentions update_user_meta() only in a code comment gets warned; (b) a real plugin in my test set was flagged solely because of update_comment_meta() calls inside its tests/ directory, with no runtime usage at all. Could the check be token based, and/or skip dev and test files?

Scope. Even after the $wpdb fix, calls like $wpdb->insert or update_user_meta are extremely common for non-personal data (view counters, plugin settings, caches), so a stable default warning could be quite noisy. Given that, would it make sense to start this as an experimental or opt-in check?

Minor: the @since tags should reference the next release (trunk is currently 2.0.0, so likely 2.1.0) rather than 1.3.0, and docs/checks.md needs a row for the new check.

- switch to token scanner to skip comments and match $wpdb correctly
- add experimental trait to avoid noisy stable warnings
- exclude plugin's own tests/ directory
- fix @SInCE tags to 2.0.0
- add docs/checks.md row and new wpdb-insert test case
@faisalahammad

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review. Pushed fixes for everything you flagged.

Big ones:

  • replaced the regex with a token scanner so comments are skipped and $wpdb->insert/update/replace matches properly
  • check is now experimental, so it only runs with --include-experimental flag. helps with the noise concern for real plugins
  • added path anchored tests/ exclusion that filters the plugin's own tests folder without breaking the runner's own tests/ path

Minor:

  • @SInCE bumped from 1.3.0 to 2.0.0 to match trunk
  • added row in docs/checks.md

Also added a 4th PHPUnit test for the $wpdb->insert case and a matching testdata plugin.

All 476 phpunit tests pass, phpcs clean, phpstan clean, coderabbit review clean.

Let me know if anything else needs a look.

- extract is_personal_data_function_call helper to reduce NPath complexity
- extract is_wpdb_method_call helper to reduce NPath complexity
- extract is_exporter_filter_registration helper to reduce NPath complexity

Errors fixed:
- PHPMD NPathComplexity: find_file_with_personal_data_signal() from 3154 to < 50
- PHPMD NPathComplexity: plugin_registers_exporter() from 1300 to < 50
- PHPMD CyclomaticComplexity: find_file_with_personal_data_signal() from 21 to < 10

PHP 7.4+ compatible. All CI checks passing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Privacy: Add check for wp_privacy_personal_data_exporters filter (GDPR personal data export)

2 participants