Skip to content

Add WPFTP feedback export: Tools admin page + WP-CLI command - #3651

Merged
maciejpilarski merged 2 commits into
trunkfrom
feature/wpftp-feedback-export
Sep 3, 2026
Merged

Add WPFTP feedback export: Tools admin page + WP-CLI command#3651
maciejpilarski merged 2 commits into
trunkfrom
feature/wpftp-feedback-export

Conversation

@Piyopiyo-Kitsune

Copy link
Copy Markdown
Collaborator

Context

The two WordPress Facilitator Training Program courses run a survey (a Jetpack Form) asking respondents what role they're in. Being able to later match who enrolled in and completed the course against their reported role requires knowing which logged-in user submitted each survey response.

Jetpack Forms' own CSV export doesn't include the logged-in user who submitted each response, even though Jetpack captures it internally (display name, username, user ID) at submission time. There's no filter/hook exposed by Jetpack to inject that into its own export, so this adds a standalone export path in wporg-learn instead.

What's added

  • wp feedback export (WP-CLI) — full export of Jetpack Forms feedback responses: every field Jetpack's own CSV export has (ID, Date, Title, Source, the form's own fields, Consent, IP Address, Country code, Browser), plus the logged-in user, joined by the real feedback post ID (not by row position).
  • Tools → WPFTP Feedback (wp-admin) — the same export from the browser, with a form picker, status filters (spam/trash), and a date range — for exporting without shell access. Gated behind the export capability (Administrator-only by default), nonce-protected.

Both share get_rows()/build_csv() in feedback-logged-in-user-export.php; the CLI command lives in its own file (class-feedback-export-cli.php) since this plugin's convention is not to mix function and class declarations in one file.

Testing

  • Verified end-to-end in wp-env: real guest + logged-in Jetpack Forms submissions, both the CLI command and the Tools page produce an identical, correctly-joined CSV.
  • Verified the "no responses found" admin notice and a distinguishable error (vs. a silent empty result) when Jetpack Forms is deactivated.
  • Verified capability gating denies a non-admin (Editor) user, both at the menu level and the form-handler level.
  • phpcs clean against this project's phpcs.xml.dist.
  • Went through /code-review (5 findings: reuse of Jetpack's own get_all_parent_post_ids(), avoiding a redundant double-fetch of feedback data, replacing a const+reassignment workaround with proper use imports, distinguishing integration failures from genuinely-empty results via WP_Error, and comment-style) — all fixed and re-verified.
  • Ran a security review — no findings.

🤖 Generated with Claude Code

Jetpack Forms' own CSV export leaves out the logged-in user who
submitted each response, even though Jetpack captures it internally
(display name, username, user ID) at submission time. This is needed
to later match WordPress Facilitator Training Program survey
respondents' reported roles against who actually enrolled in and
completed the courses.

Adds:
- wp feedback export (WP-CLI): full export of Jetpack Forms feedback
  responses (every field Jetpack's own CSV export has) plus the
  logged-in user, joined by feedback post ID.
- Tools > WPFTP Feedback: the same export from wp-admin, with a form
  picker, status filters, and a date range, for exporting without
  shell access. Gated behind the export capability, nonce-protected.

Both share get_rows()/build_csv() in feedback-logged-in-user-export.php;
the CLI command lives in its own file per this plugin's convention of
not mixing function declarations and class declarations in one file.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an alternative export path for Jetpack Forms feedback responses in wporg-learn, ensuring each response row includes the logged-in user information Jetpack captures but does not include in its own CSV export.

Changes:

  • Adds shared export logic (get_rows() + build_csv()) to assemble Jetpack’s export columns and join logged-in user data by feedback post ID.
  • Adds a Tools → WPFTP Feedback admin page to download the CSV with form/status/date filters.
  • Adds a wp feedback export WP-CLI subcommand to generate and save the same CSV.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

File Description
wp-content/plugins/wporg-learn/wporg-learn.php Loads the new feedback export and WP-CLI command files.
wp-content/plugins/wporg-learn/inc/feedback-logged-in-user-export.php Implements shared row building + CSV creation, and adds the Tools page + admin-post handler.
wp-content/plugins/wporg-learn/inc/class-feedback-export-cli.php Registers the wp feedback export WP-CLI command using the shared export logic.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread wp-content/plugins/wporg-learn/inc/feedback-logged-in-user-export.php Outdated
Comment thread wp-content/plugins/wporg-learn/inc/class-feedback-export-cli.php Outdated
- wp_die()'s second argument is the title, not a status code — pass
  403 via $args['response'] instead so the response actually sends
  that status.
- get_rows() only checked Contact_Form_Plugin but immediately uses
  Feedback::POST_TYPE/Feedback::get(); check both classes exist.
- render_admin_page() only checked Feedback, but the page also calls
  get_form_options() which needs Contact_Form_Plugin; check both.
- Fall back to the Tools page URL when wp_get_referer() is empty,
  instead of redirecting to whatever admin-post.php happens to
  resolve to.
- Add a screen-reader label for the unlabeled "before" date input.
- Fix a grammatically garbled sentence in the CLI file's docblock.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@maciejpilarski

Copy link
Copy Markdown
Collaborator

Looking into it 👀

@maciejpilarski
maciejpilarski merged commit 14c2a19 into trunk Sep 3, 2026
1 check passed
@maciejpilarski
maciejpilarski deleted the feature/wpftp-feedback-export branch September 3, 2026 02:27
@maciejpilarski
maciejpilarski restored the feature/wpftp-feedback-export branch September 3, 2026 02:56
@maciejpilarski

Copy link
Copy Markdown
Collaborator

Update after merge: this was reverted in #3660 and re-landed with fixes in #3661.

A full review of the merged code, checked against the Jetpack Forms source, the shared Export_CSV mu-plugin utility and WordPress core, found problems that change what the export contains and let the CLI widen an export silently:

  • date_query without inclusive drops both boundary days of a Y-m-d range; a one-day range exports nothing.
  • Export_CSV::add_row() runs sanitize_text_field() on every cell and its esc_csv() inserts apostrophes before -, +, @, = mid-text, so free-text answers come out altered.
  • An unrecognised --status makes WP_Query drop the status clause (exports spam and trash), an unparseable --after becomes 1970 (exports everything), a non-numeric --post casts to 0 (all forms).
  • --dir defaulted to the current directory, so a run from the site root wrote a guessably named file with usernames and IP addresses into the web root.
  • Smaller: all-test-response results became a wp_die(); spam/trash rows had no status column; same-day exports overwrote each other; the ID column was matched by the English literal rather than Jetpack's translated key; core's export capability gated the page.

One data caveat that matters for the role-to-completion analysis: Jetpack only began capturing the submitter's account in forms package 7.14.0 (23 March 2026). Responses stored before that have no logged_in_user key at all, so their user columns are blank in exactly the way a guest's are. #3661 adds a "Logged-in user recorded" column so the two cases can be told apart.

The feature and its purpose are unchanged in #3661; @Piyopiyo-Kitsune is credited as co-author there. Thanks for building it.

maciejpilarski added a commit that referenced this pull request Sep 3, 2026
…3651)" (#3660)

This reverts commit 14c2a19 (#3651).

A review after the merge found defects that change what the export
contains and CLI arguments that widen it instead of failing:

- A Y-m-d date range drops both its boundary days.
- The shared Export_CSV utility rewrites free-text answers (paragraph
  breaks collapsed, "<" encoded, percent sequences stripped, apostrophes
  inserted before " -", " +", " @", " =").
- An unrecognised --status exports every status including spam and trash,
  an unparseable --after exports everything, and a non-numeric --post
  means all forms.
- The CLI writes the file, which holds usernames and IP addresses under a
  guessable name, into the current directory, the web root by default.

Reverting so the corrected version can land as one clean change.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
maciejpilarski added a commit that referenced this pull request Sep 3, 2026
Re-adds the Tools > WPFTP Feedback page and the `wp feedback export`
command from #3651, reverted in #3660, with the post-merge review applied:

- Date bounds are inclusive and must parse.
- The CSV is written verbatim with fputcsv and Jetpack's own esc_csv()
  guard instead of the shared Export_CSV utility, which rewrote free text.
- Status values are validated, --post must be numeric, and an all-test
  batch is an empty result rather than an error.
- New columns: Status and "Logged-in user recorded" (Jetpack only began
  capturing the submitter in forms package 7.14.0); added columns carry
  Jetpack's space prefix so they cannot collide with form fields.
- CLI: --dir is required and refused inside ABSPATH or WP_CONTENT_DIR,
  the file is created with 'x' and chmod 0600, the name carries the form
  and the time, and an empty result exits 0 with a warning.
- A dedicated export_feedback_responses capability, granted to
  administrators, replaces core's export.
- Responses are processed in batches of 200 with the Feedback cache
  cleared between them; the form picker covers every status and shows IDs.

Co-authored-by: Destiny Kanno <17694715+Piyopiyo-Kitsune@users.noreply.github.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.

3 participants