Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -729,10 +729,12 @@ public function prepare_contact( $contact ) {
$prepared = [];

foreach ( $contact['metadata'] as $key => $value ) {
// If the key is already prefixed, keep it as-is if its field is enabled.
// If the key is already prefixed, keep it only when its field is both
// enabled and currently available — guarding against stale enabled-field
// names left over from a prior feature-flag-on period.
if ( 0 === strpos( $key, $prefix ) ) {
$field_name = substr( $key, strlen( $prefix ) );
if ( in_array( $field_name, $enabled_fields, true ) ) {
if ( in_array( $field_name, $enabled_fields, true ) && in_array( $field_name, $keys_map, true ) ) {
$prepared[ $key ] = $value;
}
continue;
Expand Down
3 changes: 2 additions & 1 deletion includes/reader-activation/sync/class-metadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class Metadata {
/**
* Get the metadata classes to be used for syncing contact metadata to the ESP.
*
* These are the metadata classes that will be used in case get_version is not legacy.
* These are the metadata classes that will be used to build the full set of contact metadata fields.
*
* @return array List of metadata classes.
*/
Expand All @@ -47,6 +47,7 @@ protected static function get_metadata_classes() {
$classes = [
'Legacy_Basic',
'Legacy_Payment',
'Content_Gate',
Comment thread
dkoo marked this conversation as resolved.
];
} else {
$classes = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
namespace Newspack\Reader_Activation\Sync\Contact_Metadata;

use Newspack\Reader_Activation\Sync\Contact_Metadata;
use Newspack\Reader_Activation\Sync\Legacy_Metadata;
use Newspack\Reader_Activation\Sync\Metadata;
use Newspack\Access_Rules;
use Newspack\Content_Gate as Content_Gate_CPT;
use Newspack\Group_Subscription;
Expand Down Expand Up @@ -41,7 +43,7 @@ public static function reset_cache() {
* @return boolean
*/
public static function is_available() {
return true;
return Content_Gate_CPT::is_newspack_feature_enabled();
}

/**
Expand All @@ -50,7 +52,7 @@ public static function is_available() {
* @return string
*/
public static function get_section_name() {
return __( 'Content Access', 'newspack' );
return __( 'Content Access', 'newspack-plugin' );
}

/**
Expand Down Expand Up @@ -78,26 +80,36 @@ public function get_metadata() {

$custom_access_gates = self::get_custom_access_gates();

// No custom access gates configured — nothing to evaluate.
if ( empty( $custom_access_gates ) ) {
return [
$metadata = [
'Content_Access' => '',
'Content_Access_Source' => '',
'Content_Access_Group' => '',
];
} else {
$evaluations = [];
foreach ( $custom_access_gates as $gate ) {
$evaluations[] = User_Gate_Access::evaluate_gate_for_user( $gate, $this->user->ID );
}

$user_id = $this->user->ID;
$metadata = [
'Content_Access' => self::has_content_access( $evaluations ) ? 'Yes' : 'No',
'Content_Access_Source' => implode( ', ', self::collect_labels( $evaluations, $user_id, [ self::class, 'get_source_labels' ] ) ),
'Content_Access_Group' => implode( ', ', self::collect_labels( $evaluations, $user_id, [ self::class, 'get_group_labels' ] ) ),
];
}

$evaluations = [];
foreach ( $custom_access_gates as $gate ) {
$evaluations[] = User_Gate_Access::evaluate_gate_for_user( $gate, $this->user->ID );
// In legacy mode the main sync path does not run a normalize step on
// the merged contact, so each metadata class must return keys in the
// prefixed shape (matching Legacy_Basic / Legacy_Payment). Without this,
// raw Content_Access keys are silently dropped at the ESP push.
if ( 'legacy' === Metadata::get_version() ) {
$normalized = Legacy_Metadata::normalize_contact_data( [ 'metadata' => $metadata ] );
Comment thread
dkoo marked this conversation as resolved.
return $normalized['metadata'] ?? [];
}

$user_id = $this->user->ID;
return [
'Content_Access' => self::has_content_access( $evaluations ) ? 'Yes' : 'No',
'Content_Access_Source' => implode( ', ', self::collect_labels( $evaluations, $user_id, [ self::class, 'get_source_labels' ] ) ),
'Content_Access_Group' => implode( ', ', self::collect_labels( $evaluations, $user_id, [ self::class, 'get_group_labels' ] ) ),
];
return $metadata;
}

/**
Expand Down
19 changes: 18 additions & 1 deletion tests/unit-tests/content-gate/class-content-gate-metadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Newspack\Group_Subscription_Settings;
use Newspack\Institution;
use Newspack\Reader_Activation;
use Newspack\Reader_Activation\Sync\Metadata;
use Newspack\Reader_Activation\Sync\Contact_Metadata\Content_Gate as Content_Gate_Metadata;

/**
Expand Down Expand Up @@ -41,6 +42,13 @@ class Newspack_Test_Content_Gate_Metadata extends WP_UnitTestCase {
*/
private $institution_ids = [];

/**
* Schema version restored in tear_down().
*
* @var string
*/
private $original_version;

/**
* Set up the WC mocks once for the class.
*/
Expand All @@ -51,6 +59,11 @@ public static function set_up_before_class() {

/**
* Set up before each test.
*
* Forces the v1 schema so get_metadata() returns the raw key shape these
* tests assert against — legacy mode now normalizes to prefixed keys
* (matching the other Legacy_* classes), which is exercised in
* Test_Content_Gate_Legacy.
*/
public function set_up() {
parent::set_up();
Expand All @@ -63,7 +76,9 @@ public function set_up() {
$subscriptions_database = [];
$products_database = [];

self::$user_id = $this->factory->user->create(
$this->original_version = Metadata::$version;
Metadata::$version = '1.0';
self::$user_id = $this->factory->user->create(
[
'role' => 'subscriber',
'user_email' => 'reader@example.com',
Expand Down Expand Up @@ -93,6 +108,8 @@ public function tear_down() {

Group_Subscription::reset_cache();
Institution::reset_matching_cache();
Metadata::$version = $this->original_version;
Content_Gate_Metadata::reset_cache();
parent::tear_down();
}

Expand Down
30 changes: 30 additions & 0 deletions tests/unit-tests/integrations/class-test-prepare-contact.php
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,36 @@ public function test_preserves_email_and_name() {
$this->assertSame( 'Test User', $result['name'] );
}

/**
* Test that an already-prefixed key whose field is enabled but no longer
* present in the live keys map (e.g. because a feature flag turned off the
* corresponding metadata class after the field was saved) is filtered out.
*/
public function test_already_prefixed_stale_enabled_field_filtered() {
$this->set_metadata_version( '1.0' );

// Write the enabled-fields option directly, bypassing the
// update_enabled_outgoing_fields() intersect filter, to simulate a stale
// saved field name that is no longer in the live keys map.
\update_option( 'newspack_integration_outgoing_fields_prepare-test', [ 'Stale Field' ] );

$keys_map = Metadata::get_keys();
$this->assertNotContains( 'Stale Field', $keys_map, 'Sanity: stale field must not be in the live keys map.' );

$contact = [
'email' => 'test@example.com',
'metadata' => [ 'NP_Stale Field' => 'leftover_value' ],
];

$result = $this->integration->prepare_contact( $contact );

$this->assertArrayNotHasKey(
'NP_Stale Field',
$result['metadata'],
'Stale prefixed key must be dropped when its field is no longer available.'
);
}

/**
* Test mixed raw and already-prefixed keys in the same contact.
*/
Expand Down
Loading
Loading