Skip to content

feat(integrations): implement profile metadata (identity, registration, engagement)#4624

Open
chickenn00dle wants to merge 3 commits intotrunkfrom
feat/add-profile-metadata
Open

feat(integrations): implement profile metadata (identity, registration, engagement)#4624
chickenn00dle wants to merge 3 commits intotrunkfrom
feat/add-profile-metadata

Conversation

@chickenn00dle
Copy link
Copy Markdown
Contributor

@chickenn00dle chickenn00dle commented Apr 2, 2026

All Submissions:

Changes proposed in this Pull Request:

Implements get_metadata() for the three profile contact metadata classes that were previously returning empty arrays: Identity, Registration, and Engagement.

Identity (7 fields):

  • first_name, last_name, email — from WordPress user data
  • Account — WordPress user ID
  • User_Role — first WordPress role assigned to the user
  • verified — whether the reader has verified their email (from np_reader_email_verified user meta)
  • Connected_Account — SSO provider used to register, if any (from np_reader_connected_account user meta)

Registration (6 fields):

  • Registration_Date — account creation date, formatted as Y-m-d H:i:s
  • Registration_Page — URL where the reader registered (from user meta)
  • Registration_Strategy — how the reader registered: newsletter, checkout, registration-wall, etc. (from user meta)
  • Registration_UTM_Source, Registration_UTM_Medium, Registration_UTM_Campaign — parsed from the registration page URL query parameters

Engagement (10 fields):

  • First_Visit_Date, Last_Active — from the Reader Data store (JS millisecond timestamps converted to Y-m-d H:i:s)
  • Articles_Read, Paywall_Hits — integer counts from the Reader Data store
  • Favorite_Categories — category IDs from the Reader Data store, converted to a comma-separated string of category names
  • Payment_Page — URL of the most recent checkout page (from the latest completed WC order's _newspack_referer meta)
  • Payment_UTM_Source, Payment_UTM_Medium, Payment_UTM_Campaign — UTM data from the latest completed WC order
  • Total_Paid — lifetime total spent via WC_Customer::get_total_spent()

Closes https://linear.app/a8c/issue/NPPD-1389/implement-profile-fields-identity-engagement-registration

How to test the changes in this Pull Request:

  1. Enable the new metadata system in wp-config.php:
    define( 'NEWSPACK_LOG_LEVEL', 3 );
    define( 'NEWSPACK_SYNC_METADATA_VERSION', '1.0' );
    define( 'NEWSPACK_INTEGRATIONS_SETTINGS_ENABLED', true );
  2. Navigate to Audience > Integrations > ESP and confirm the Identity, Registration, and Engagement fields appear in the outgoing metadata section
  3. Check all fields and save, confirm they persist on reload
  4. Register a new reader on a page with UTM parameters (e.g. ?utm_source=test&utm_medium=email&utm_campaign=spring)
  5. Check the logs — verify registration metadata includes the date, page, strategy, and UTM values
  6. As that reader, browse several articles and trigger a paywall hit
  7. Make a WooCommerce subscription or donation purchase
  8. Verify that engagement fields (First_Visit_Date, Last_Active, Articles_Read, Paywall_Hits, Favorite_Categories, Payment_Page, Payment_UTM_*, and Total_Paid) are synced
  9. Run the unit tests:
    n test-php --group=Identity_Metadata,Registration_Metadata,Engagement_Metadata

Other information:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your changes, as applicable?
  • Have you successfully ran tests with your changes locally?

@chickenn00dle chickenn00dle force-pushed the feat/add-profile-metadata branch from 2e3b777 to c11dc18 Compare April 2, 2026 19:56
@chickenn00dle chickenn00dle force-pushed the feat/add-profile-metadata branch from 973af86 to c2dab85 Compare April 2, 2026 20:42
@chickenn00dle chickenn00dle marked this pull request as ready for review April 2, 2026 21:40
@chickenn00dle chickenn00dle requested a review from a team as a code owner April 2, 2026 21:40
Copilot AI review requested due to automatic review settings April 2, 2026 21:40
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Implements real metadata export for Reader Activation “profile” contact metadata by populating Identity, Registration, and Engagement metadata classes, and adds unit tests to validate the new behaviors.

Changes:

  • Implemented get_metadata() for Identity, Registration, and Engagement contact metadata classes (including new registration UTM persistence).
  • Updated sync metadata class list to use the new metadata sections and improved integration-specific sync logging.
  • Added unit tests covering Identity/Registration/Engagement metadata output.

Reviewed changes

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

Show a summary per file
File Description
tests/unit-tests/class-test-registration-metadata.php Adds unit coverage for Registration metadata fields (date, page, strategy, UTM).
tests/unit-tests/class-test-identity-metadata.php Adds unit coverage for Identity fields (name/email/account/role/verified/connected account).
tests/unit-tests/class-test-engagement-metadata.php Adds unit coverage for Engagement fields (timestamps, paywall hits, favorite categories, payment fields, total paid).
src/blocks/reader-registration/index.php Adjusts referer URL handling for reCAPTCHA and stored registration metadata.
includes/reader-activation/sync/contact-metadata/class-registration.php Implements Registration metadata (including UTM meta lookup).
includes/reader-activation/sync/contact-metadata/class-identity.php Implements Identity metadata from WP user fields and user meta.
includes/reader-activation/sync/contact-metadata/class-engagement.php Implements Engagement metadata from Reader Data and WooCommerce order/customer data.
includes/reader-activation/sync/class-metadata.php Switches non-legacy metadata class list to Identity/Registration/Engagement + others.
includes/reader-activation/sync/class-contact-sync.php Moves debug logging into integration loop and logs integration-prepared payloads.
includes/reader-activation/sync/class-contact-metadata.php Updates date formatting docblock to match current formatter behavior.
includes/reader-activation/class-reader-activation.php Captures/saves registration UTM params; minor formatting change.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +2110 to +2114
// Capture UTM params before reconstructing the URL (which drops query params).
$registration_utm = [];
if ( ! empty( $current_page_url['query'] ) ) {
$query_params = [];
\wp_parse_str( $current_page_url['query'], $query_params );
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

process_auth_form() assumes wp_parse_url( wp_get_raw_referer() ) returns an array, but it can be false. Accessing $current_page_url['query'] in that case will trigger PHP warnings and skip UTM capture. Consider guarding with is_array( $current_page_url ) (or normalizing to an empty array) before reading query/path.

Copilot uses AI. Check for mistakes.
Comment on lines +517 to +518
$current_page_url = ! empty( $parsed_referer['path'] ) ? \esc_url( $raw_referer ) : $raw_referer;
$recaptcha_url = ! empty( $parsed_referer['path'] ) ? \esc_url( \home_url( $parsed_referer['path'] ) ) : $current_page_url;
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

wp_parse_url( $raw_referer ) can return false. Indexing $parsed_referer['path'] without checking is_array( $parsed_referer ) can produce PHP warnings when there is no/invalid referer. Add an is_array guard (and ideally normalize $current_page_url/$recaptcha_url to empty strings) before using path.

Suggested change
$current_page_url = ! empty( $parsed_referer['path'] ) ? \esc_url( $raw_referer ) : $raw_referer;
$recaptcha_url = ! empty( $parsed_referer['path'] ) ? \esc_url( \home_url( $parsed_referer['path'] ) ) : $current_page_url;
$referer_path = is_array( $parsed_referer ) && ! empty( $parsed_referer['path'] ) ? $parsed_referer['path'] : '';
$current_page_url = '';
$recaptcha_url = '';
if ( ! empty( $referer_path ) ) {
$current_page_url = \esc_url( $raw_referer );
$recaptcha_url = \esc_url( \home_url( $referer_path ) );
} else {
$current_page_url = $raw_referer ? $raw_referer : '';
$recaptcha_url = $current_page_url;
}

Copilot uses AI. Check for mistakes.
Comment on lines 50 to 56
public static function get_fields() {
return [
'First_Visit_Date' => 'First Visit Date',
'Last_Active' => 'Last Active',
'Articles_Read' => 'Articles Read',
'Paywall_Hits' => 'Paywall Hits',
'Favorite_Categories' => 'Favorite Categories',
'Payment_Page' => 'Payment Page',
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

The PR description and JS reader-data store include an articles_read metric, but Engagement metadata does not expose an Articles_Read field in get_fields(). This prevents configuring/syncing that value. Add the field mapping and keep the label consistent with other Engagement fields.

Copilot uses AI. Check for mistakes.
Comment on lines +76 to +81
return [
'First_Visit_Date' => $this->format_reader_data_timestamp( 'first_visit_date' ),
'Last_Active' => $this->format_reader_data_timestamp( 'last_active' ),
'Paywall_Hits' => $this->get_reader_data_int( 'paywall_hits' ),
'Favorite_Categories' => $this->get_favorite_categories(),
'Payment_Page' => $this->get_payment_page( $order ),
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

Engagement get_metadata() does not include an Articles_Read value even though the reader data store sets articles_read (and the PR description lists it). Add Articles_Read to the returned metadata, sourced from the reader data store, and extend unit tests to cover it.

Copilot uses AI. Check for mistakes.
// Added logging here to more easily monitor integration sync data. Can be removed once integrations are released.
if ( 'legacy' !== Metadata::get_version() ) {
Logger::log( sprintf( 'Syncing contact %s for integration %s with context "%s".', $integration_contact['email'] ?? 'unknown', $integration_id, $context ) );
Logger::log( $integration_contact );
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

This logging now runs once per active integration and logs the full prepared contact payload (including metadata/PII) each time. That can significantly increase log volume and risk leaking personal data into logs. Consider logging only a summarized/redacted subset, logging once per sync, and/or gating payload logging behind a higher/explicit log level.

Suggested change
Logger::log( $integration_contact );
Logger::log(
[
'integration' => $integration_id,
'context' => $context,
'email_present' => ! empty( $integration_contact['email'] ),
'field_count' => \is_array( $integration_contact ) ? count( $integration_contact ) : 0,
'fields' => \is_array( $integration_contact ) ? array_keys( $integration_contact ) : [],
]
);

Copilot uses AI. Check for mistakes.
Comment on lines +2440 to +2441


Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

There are two consecutive blank lines after the newspack_register_reader_metadata filter call. Please remove the extra whitespace to keep formatting consistent.

Suggested change

Copilot uses AI. Check for mistakes.
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.

2 participants