feat(integrations): implement profile metadata (identity, registration, engagement)#4624
feat(integrations): implement profile metadata (identity, registration, engagement)#4624chickenn00dle wants to merge 3 commits intotrunkfrom
Conversation
2e3b777 to
c11dc18
Compare
973af86 to
c2dab85
Compare
There was a problem hiding this comment.
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.
| // 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 ); |
There was a problem hiding this comment.
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.
| $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; |
There was a problem hiding this comment.
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.
| $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; | |
| } |
| 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', |
There was a problem hiding this comment.
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.
| 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 ), |
There was a problem hiding this comment.
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.
| // 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 ); |
There was a problem hiding this comment.
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.
| 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 ) : [], | |
| ] | |
| ); |
|
|
||
|
|
There was a problem hiding this comment.
There are two consecutive blank lines after the newspack_register_reader_metadata filter call. Please remove the extra whitespace to keep formatting consistent.
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 dataAccount— WordPress user IDUser_Role— first WordPress role assigned to the userverified— whether the reader has verified their email (fromnp_reader_email_verifieduser meta)Connected_Account— SSO provider used to register, if any (fromnp_reader_connected_accountuser meta)Registration (6 fields):
Registration_Date— account creation date, formatted asY-m-d H:i:sRegistration_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 parametersEngagement (10 fields):
First_Visit_Date,Last_Active— from the Reader Data store (JS millisecond timestamps converted toY-m-d H:i:s)Articles_Read,Paywall_Hits— integer counts from the Reader Data storeFavorite_Categories— category IDs from the Reader Data store, converted to a comma-separated string of category namesPayment_Page— URL of the most recent checkout page (from the latest completed WC order's_newspack_referermeta)Payment_UTM_Source,Payment_UTM_Medium,Payment_UTM_Campaign— UTM data from the latest completed WC orderTotal_Paid— lifetime total spent viaWC_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:
wp-config.php:?utm_source=test&utm_medium=email&utm_campaign=spring)First_Visit_Date,Last_Active,Articles_Read,Paywall_Hits,Favorite_Categories,Payment_Page,Payment_UTM_*, andTotal_Paid) are syncedOther information: