diff --git a/includes/class-newspack-newsletters-contacts.php b/includes/class-newspack-newsletters-contacts.php
new file mode 100644
index 000000000..c87a526c5
--- /dev/null
+++ b/includes/class-newspack-newsletters-contacts.php
@@ -0,0 +1,259 @@
+service . '.' );
+ } else {
+ Newspack_Newsletters_Logger::log( 'Adding contact without lists. Provider is ' . $provider->service . '.' );
+ }
+
+ $existing_contact = Newspack_Newsletters_Subscription::get_contact_data( $contact['email'], true );
+ $contact['existing_contact_data'] = \is_wp_error( $existing_contact ) ? false : $existing_contact;
+ $is_updating = \is_wp_error( $existing_contact ) ? false : true;
+
+ /**
+ * Filters the contact before passing on to the API.
+ *
+ * @param array $contact {
+ * Contact information.
+ *
+ * @type string $email Contact email address.
+ * @type string $name Contact name. Optional.
+ * @type string $existing_contact_data Existing contact data, if updating a contact. The hook will be also called when
+ * @type string[] $metadata Contact additional metadata. Optional.
+ * }
+ * @param string[]|false $selected_list_ids Array of list IDs the contact will be subscribed to, or false.
+ * @param string $provider The provider name.
+ */
+ $contact = apply_filters( 'newspack_newsletters_contact_data', $contact, $lists, $provider->service );
+
+ if ( isset( $contact['metadata'] ) ) {
+ Newspack_Newsletters_Logger::log( 'Adding contact with metadata key(s): ' . implode( ', ', array_keys( $contact['metadata'] ) ) . '.' );
+ }
+
+ if ( ! isset( $contact['metadata'] ) ) {
+ $contact['metadata'] = [];
+ }
+ $contact['metadata']['origin_newspack'] = '1';
+
+ /**
+ * Filters the contact selected lists before passing on to the API.
+ *
+ * @param string[]|false $lists Array of list IDs the contact will be subscribed to, or false.
+ * @param array $contact {
+ * Contact information.
+ *
+ * @type string $email Contact email address.
+ * @type string $name Contact name. Optional.
+ * @type string[] $metadata Contact additional metadata. Optional.
+ * }
+ * @param string $provider The provider name.
+ */
+ $lists = apply_filters( 'newspack_newsletters_contact_lists', $lists, $contact, $provider->service );
+
+ return self::add_to_esp( $contact, $lists, $is_updating );
+ }
+
+ /**
+ * Permanently delete a user subscription.
+ *
+ * @param int $user_id User ID.
+ *
+ * @return bool|WP_Error Whether the contact was deleted or error.
+ */
+ public static function delete( $user_id ) {
+ $user = get_user_by( 'id', $user_id );
+ if ( ! $user ) {
+ return new WP_Error( 'newspack_newsletters_invalid_user', __( 'Invalid user.' ) );
+ }
+ /** Only delete if email ownership is verified. */
+ if ( ! Newspack_Newsletters_Subscription::is_email_verified( $user_id ) ) {
+ return new \WP_Error( 'newspack_newsletters_email_not_verified', __( 'Email ownership is not verified.' ) );
+ }
+ $provider = Newspack_Newsletters::get_service_provider();
+ if ( empty( $provider ) ) {
+ return new WP_Error( 'newspack_newsletters_invalid_provider', __( 'Provider is not set.' ) );
+ }
+ if ( ! method_exists( $provider, 'delete_contact' ) ) {
+ return new WP_Error( 'newspack_newsletters_invalid_provider_method', __( 'Provider does not support deleting user subscriptions.' ) );
+ }
+ return $provider->delete_contact( $user->user_email );
+ }
+
+ /**
+ * Update a contact lists subscription.
+ *
+ * This method will remove the contact from all subscription lists and add
+ * them to the specified lists.
+ *
+ * @param string $email Contact email address.
+ * @param string[] $lists Array of list IDs to subscribe the contact to.
+ *
+ * @return bool|WP_Error Whether the contact was updated or error.
+ */
+ public static function update_lists( $email, $lists = [] ) {
+ if ( ! Newspack_Newsletters_Subscription::has_subscription_management() ) {
+ return new WP_Error( 'newspack_newsletters_not_supported', __( 'Not supported for this provider', 'newspack-newsletters' ) );
+ }
+ $provider = Newspack_Newsletters::get_service_provider();
+
+ Newspack_Newsletters_Logger::log( 'Updating lists of a contact. List selection: ' . implode( ', ', $lists ) . '. Provider is ' . $provider->service . '.' );
+
+ /** Determine lists to add/remove from existing list config. */
+ $lists_config = Newspack_Newsletters_Subscription::get_lists_config();
+ $lists_to_add = array_intersect( array_keys( $lists_config ), $lists );
+ $lists_to_remove = array_diff( array_keys( $lists_config ), $lists );
+
+ /** Clean up lists to add/remove from contact's existing data. */
+ $current_lists = Newspack_Newsletters_Subscription::get_contact_lists( $email );
+ $lists_to_add = array_diff( $lists_to_add, $current_lists );
+ $lists_to_remove = array_intersect( $current_lists, $lists_to_remove );
+
+ if ( empty( $lists_to_add ) && empty( $lists_to_remove ) ) {
+ return false;
+ }
+
+ $result = $provider->update_contact_lists_handling_local( $email, $lists_to_add, $lists_to_remove );
+
+ /**
+ * Fires after a contact's lists are updated.
+ *
+ * @param string $provider The provider name.
+ * @param string $email Contact email address.
+ * @param string[] $lists_to_add Array of list IDs to subscribe the contact to.
+ * @param string[] $lists_to_remove Array of list IDs to remove the contact from.
+ * @param bool|WP_Error $result True if the contact was updated or error if failed.
+ */
+ do_action( 'newspack_newsletters_update_contact_lists', $provider->service, $email, $lists_to_add, $lists_to_remove, $result );
+
+ return $result;
+ }
+
+ /**
+ * Internal method to add a contact to lists. Should be called by the
+ * `add_contact` method or `handle_async_subscribe` for the async strategy.
+ *
+ * @param array $contact Contact information.
+ * @param array $lists Array of list IDs to subscribe the contact to.
+ * @param bool $is_updating Whether the contact is being updated. If false, the contact is being created.
+ *
+ * @return array|WP_Error Contact data if it was added, or error otherwise.
+ */
+ private static function add_to_esp( $contact, $lists = [], $is_updating = false ) {
+ $provider = Newspack_Newsletters::get_service_provider();
+ $errors = new WP_Error();
+ $result = [];
+
+ try {
+ if ( method_exists( $provider, 'add_contact_with_groups_and_tags' ) ) {
+ $result = $provider->add_contact_with_groups_and_tags( $contact, $lists );
+ } elseif ( empty( $lists ) ) {
+ $result = $provider->add_contact( $contact );
+ } else {
+ foreach ( $lists as $list_id ) {
+ $result = $provider->add_contact( $contact, $list_id );
+ }
+ }
+ } catch ( \Exception $e ) {
+ $errors->add( 'newspack_newsletters_subscription_add_contact', $e->getMessage() );
+ }
+
+ if ( is_wp_error( $result ) ) {
+ $errors->add( $result->get_error_code(), $result->get_error_message() );
+ }
+
+ // Handle local lists feature.
+ foreach ( $lists as $list_id ) {
+ try {
+ $provider->add_contact_handling_local_list( $contact, $list_id );
+ } catch ( \Exception $e ) {
+ $errors->add( 'newspack_newsletters_subscription_handling_local_list', $e->getMessage() );
+ }
+ }
+
+ /**
+ * Fires after a contact is added.
+ *
+ * @param string $provider The provider name.
+ * @param array $contact {
+ * Contact information.
+ *
+ * @type string $email Contact email address.
+ * @type string $name Contact name. Optional.
+ * @type string[] $metadata Contact additional metadata. Optional.
+ * }
+ * @param string[]|false $lists Array of list IDs to subscribe the contact to.
+ * @param array|WP_Error $result Array with data if the contact was added or error if failed.
+ * @param bool $is_updating Whether the contact is being updated. If false, the contact is being created.
+ */
+ do_action( 'newspack_newsletters_add_contact', $provider->service, $contact, $lists, $result, $is_updating );
+
+ if ( $errors->has_errors() ) {
+ return $errors;
+ }
+
+ return $result;
+ }
+}
diff --git a/includes/class-newspack-newsletters-subscription.php b/includes/class-newspack-newsletters-subscription.php
index d34163b7f..8720e095c 100644
--- a/includes/class-newspack-newsletters-subscription.php
+++ b/includes/class-newspack-newsletters-subscription.php
@@ -34,7 +34,6 @@ class Newspack_Newsletters_Subscription {
public static function init() {
add_action( 'rest_api_init', [ __CLASS__, 'register_api_endpoints' ] );
add_action( 'newspack_registered_reader', [ __CLASS__, 'newspack_registered_reader' ], 10, 5 );
- add_action( 'delete_user', [ __CLASS__, 'delete_user' ], 10, 3 );
/** User email verification for subscription management. */
add_action( 'resetpass_form', [ __CLASS__, 'set_current_user_email_verified' ] );
@@ -357,125 +356,48 @@ public static function get_contact_data( $email_address, $return_details = false
* @return array|WP_Error|true Contact data if it was added, or error otherwise. True if async.
*/
public static function add_contact( $contact, $lists = false, $async = false ) {
- if ( ! is_array( $lists ) && false !== $lists ) {
- $lists = [ $lists ];
- }
-
- /**
- * Trigger an action before contact adding.
- *
- * @param string[]|false $lists Array of list IDs the contact will be subscribed to, or false.
- * @param array $contact {
- * Contact information.
- *
- * @type string $email Contact email address.
- * @type string $name Contact name. Optional.
- * @type string[] $metadata Contact additional metadata. Optional.
- * }
- */
- do_action( 'newspack_newsletters_pre_add_contact', $lists, $contact );
-
- $provider = Newspack_Newsletters::get_service_provider();
- if ( empty( $provider ) ) {
- return new WP_Error( 'newspack_newsletters_invalid_provider', __( 'Provider is not set.' ) );
- }
-
- if ( false !== $lists ) {
- Newspack_Newsletters_Logger::log( 'Adding contact to list(s): ' . implode( ', ', $lists ) . '. Provider is ' . $provider->service . '.' );
- } else {
- Newspack_Newsletters_Logger::log( 'Adding contact without lists. Provider is ' . $provider->service . '.' );
- }
-
- $existing_contact = self::get_contact_data( $contact['email'], true );
- $contact['existing_contact_data'] = \is_wp_error( $existing_contact ) ? false : $existing_contact;
- $is_updating = \is_wp_error( $existing_contact ) ? false : true;
-
- /**
- * Filters the contact before passing on to the API.
- *
- * @param array $contact {
- * Contact information.
- *
- * @type string $email Contact email address.
- * @type string $name Contact name. Optional.
- * @type string $existing_contact_data Existing contact data, if updating a contact. The hook will be also called when
- * @type string[] $metadata Contact additional metadata. Optional.
- * }
- * @param string[]|false $selected_list_ids Array of list IDs the contact will be subscribed to, or false.
- * @param string $provider The provider name.
- */
- $contact = apply_filters( 'newspack_newsletters_contact_data', $contact, $lists, $provider->service );
-
- if ( isset( $contact['metadata'] ) ) {
- Newspack_Newsletters_Logger::log( 'Adding contact with metadata key(s): ' . implode( ', ', array_keys( $contact['metadata'] ) ) . '.' );
- }
-
- if ( ! isset( $contact['metadata'] ) ) {
- $contact['metadata'] = [];
- }
- $contact['metadata']['origin_newspack'] = '1';
-
- /**
- * Filters the contact selected lists before passing on to the API.
- *
- * @param string[]|false $lists Array of list IDs the contact will be subscribed to, or false.
- * @param array $contact {
- * Contact information.
- *
- * @type string $email Contact email address.
- * @type string $name Contact name. Optional.
- * @type string[] $metadata Contact additional metadata. Optional.
- * }
- * @param string $provider The provider name.
- */
- $lists = apply_filters( 'newspack_newsletters_contact_lists', $lists, $contact, $provider->service );
-
- if ( true !== $async ) {
- return self::add_contact_to_provider( $contact, $lists, $is_updating );
- } else {
- $intent_id = self::add_subscription_intent( $contact, $lists, $is_updating );
- $nonce = wp_create_nonce( self::ASYNC_ACTION );
- $url = admin_url( 'admin-ajax.php?action=' . self::ASYNC_ACTION . '&nonce=' . $nonce );
- $args = [
- 'timeout' => 0.01,
- 'blocking' => false,
- 'cookies' => $_COOKIE, // phpcs:ignore
- 'sslverify' => apply_filters( 'https_local_ssl_verify', false ),
- 'body' => [
- 'action_name' => self::ASYNC_ACTION,
- 'intent_id' => $intent_id,
- ],
- ];
- wp_remote_post( $url, $args );
- return true;
- }
+ _deprecated_function( __METHOD__, '2.21', 'Newspack_Newsletters_Contacts::upsert' );
+ return Newspack_Newsletters_Contacts::upsert( $contact, $lists, $async );
}
/**
- * Register a subscription intent.
+ * Register a subscription intent and dispatches a async request to process it.
*
* @param array $contact Contact information.
* @param array $lists Array of list IDs to subscribe the contact to.
- * @param bool $is_updating Whether the contact is being updated. If false, the contact is being created.
*
* @return int|WP_Error Subscription intent ID or error.
*/
- private static function add_subscription_intent( $contact, $lists, $is_updating ) {
+ public static function add_subscription_intent( $contact, $lists ) {
$intent_id = \wp_insert_post(
[
'post_type' => self::SUBSCRIPTION_INTENT_CPT,
'post_status' => 'publish',
'meta_input' => [
- 'contact' => $contact,
- 'lists' => $lists,
- 'is_updating' => $is_updating,
- 'errors' => [],
+ 'contact' => $contact,
+ 'lists' => $lists,
+ 'errors' => [],
],
]
);
if ( is_wp_error( $intent_id ) ) {
Newspack_Newsletters_Logger::log( 'Error adding subscription intent: ' . $intent_id->get_error_message() );
}
+
+ $nonce = wp_create_nonce( self::ASYNC_ACTION );
+ $url = admin_url( 'admin-ajax.php?action=' . self::ASYNC_ACTION . '&nonce=' . $nonce );
+ $args = [
+ 'timeout' => 0.01,
+ 'blocking' => false,
+ 'cookies' => $_COOKIE, // phpcs:ignore
+ 'sslverify' => apply_filters( 'https_local_ssl_verify', false ),
+ 'body' => [
+ 'action_name' => self::ASYNC_ACTION,
+ 'intent_id' => $intent_id,
+ ],
+ ];
+ wp_remote_post( $url, $args );
+
return $intent_id;
}
@@ -496,11 +418,10 @@ private static function get_subscription_intent( $intent_id_or_post ) {
return false;
}
return [
- 'id' => $intent->ID,
- 'contact' => get_post_meta( $intent->ID, 'contact', true ),
- 'lists' => get_post_meta( $intent->ID, 'lists', true ),
- 'is_updating' => get_post_meta( $intent->ID, 'is_updating', true ),
- 'errors' => get_post_meta( $intent->ID, 'errors', true ),
+ 'id' => $intent->ID,
+ 'contact' => get_post_meta( $intent->ID, 'contact', true ),
+ 'lists' => get_post_meta( $intent->ID, 'lists', true ),
+ 'errors' => get_post_meta( $intent->ID, 'errors', true ),
];
}
@@ -552,8 +473,7 @@ public static function process_subscription_intents( $intent_id = null ) {
$contact = $intent['contact'];
$email = $contact['email'];
$lists = $intent['lists'];
- $is_updating = $intent['is_updating'];
- $result = self::add_contact_to_provider( $contact, $lists, $is_updating );
+ $result = Newspack_Newsletters_Contacts::upsert( $contact, $lists, false );
$user = get_user_by( 'email', $email );
if ( \is_wp_error( $result ) ) {
@@ -618,78 +538,6 @@ public static function handle_async_subscribe() {
\wp_die( 'OK', '', 200 );
}
- /**
- * Internal method to add a contact to lists. Should be called by the
- * `add_contact` method or `handle_async_subscribe` for the async strategy.
- *
- * @param array $contact Contact information.
- * @param array $lists Array of list IDs to subscribe the contact to.
- * @param bool $is_updating Whether the contact is being updated. If false, the contact is being created.
- *
- * @return array|WP_Error Contact data if it was added, or error otherwise.
- */
- private static function add_contact_to_provider( $contact, $lists = [], $is_updating = false ) {
- $provider = Newspack_Newsletters::get_service_provider();
- $errors = new WP_Error();
- $result = [];
-
- try {
- if ( method_exists( $provider, 'add_contact_with_groups_and_tags' ) ) {
- $result = $provider->add_contact_with_groups_and_tags( $contact, $lists );
- } elseif ( empty( $lists ) ) {
- $result = $provider->add_contact( $contact );
- } else {
- foreach ( $lists as $list_id ) {
- $result = $provider->add_contact( $contact, $list_id );
- }
- }
- } catch ( \Exception $e ) {
- $errors->add( 'newspack_newsletters_subscription_add_contact', $e->getMessage() );
- }
-
- if ( is_wp_error( $result ) ) {
- $errors->add( $result->get_error_code(), $result->get_error_message() );
- }
-
- // Handle local lists feature.
- foreach ( $lists as $list_id ) {
- try {
- $provider->add_contact_handling_local_list( $contact, $list_id );
- } catch ( \Exception $e ) {
- $errors->add( 'newspack_newsletters_subscription_handling_local_list', $e->getMessage() );
- }
- }
-
- /**
- * Fires after a contact is added.
- *
- * @param string $provider The provider name.
- * @param array $contact {
- * Contact information.
- *
- * @type string $email Contact email address.
- * @type string $name Contact name. Optional.
- * @type string[] $metadata Contact additional metadata. Optional.
- * }
- * @param string[]|false $lists Array of list IDs to subscribe the contact to.
- * @param array|WP_Error $result Array with data if the contact was added or error if failed.
- * @param bool $is_updating Whether the contact is being updated. If false, the contact is being created.
- */
- do_action( 'newspack_newsletters_add_contact', $provider->service, $contact, $lists, $result, $is_updating );
-
- // Remove any existing subscription error message.
- if ( ! $errors->has_errors() ) {
- $user = get_user_by( 'email', $contact['email'] );
- if ( $user ) {
- delete_user_meta( $user->ID, 'newspack_newsletters_subscription_error' );
- }
- } else {
- return $errors;
- }
-
- return $result;
- }
-
/**
* Permanently delete a user subscription.
*
@@ -698,22 +546,8 @@ private static function add_contact_to_provider( $contact, $lists = [], $is_upda
* @return bool|WP_Error Whether the contact was deleted or error.
*/
public static function delete_user_subscription( $user_id ) {
- $user = get_user_by( 'id', $user_id );
- if ( ! $user ) {
- return new WP_Error( 'newspack_newsletters_invalid_user', __( 'Invalid user.' ) );
- }
- /** Only delete if email ownership is verified. */
- if ( ! self::is_email_verified( $user_id ) ) {
- return new \WP_Error( 'newspack_newsletters_email_not_verified', __( 'Email ownership is not verified.' ) );
- }
- $provider = Newspack_Newsletters::get_service_provider();
- if ( empty( $provider ) ) {
- return new WP_Error( 'newspack_newsletters_invalid_provider', __( 'Provider is not set.' ) );
- }
- if ( ! method_exists( $provider, 'delete_contact' ) ) {
- return new WP_Error( 'newspack_newsletters_invalid_provider_method', __( 'Provider does not support deleting user subscriptions.' ) );
- }
- return $provider->delete_contact( $user->user_email );
+ _deprecated_function( __METHOD__, '2.21', 'Newspack_Newsletters_Contacts::delete' );
+ return Newspack_Newsletters_Contacts::delete( $user_id );
}
/**
@@ -756,31 +590,6 @@ public static function newspack_registered_reader( $email, $authenticate, $user_
}
}
- /**
- * Delete a contact from ESP when a reader is deleted.
- *
- * @param int $user_id ID of the user to delete.
- * @param int|null $reassign ID of the user to reassign posts and links to.
- * Default null, for no reassignment.
- * @param WP_User $user WP_User object of the user to delete.
- *
- * @return bool|WP_Error Whether the contact was deleted or error.
- */
- public static function delete_user( $user_id, $reassign, $user ) {
- if ( ! class_exists( '\Newspack\Reader_Activation' ) || ! \Newspack\Reader_Activation::is_user_reader( $user ) ) {
- return;
- }
- $sync = \Newspack\Reader_Activation::get_setting( 'sync_esp' );
- if ( ! $sync ) {
- return;
- }
- $sync_delete = \Newspack\Reader_Activation::get_setting( 'sync_esp_delete' );
- if ( ! $sync_delete ) {
- return;
- }
- return self::delete_user_subscription( $user_id );
- }
-
/**
* Update a contact lists subscription.
*
@@ -793,41 +602,8 @@ public static function delete_user( $user_id, $reassign, $user ) {
* @return bool|WP_Error Whether the contact was updated or error.
*/
private static function update_contact_lists( $email, $lists = [] ) {
- if ( ! self::has_subscription_management() ) {
- return new WP_Error( 'newspack_newsletters_not_supported', __( 'Not supported for this provider', 'newspack-newsletters' ) );
- }
- $provider = Newspack_Newsletters::get_service_provider();
-
- Newspack_Newsletters_Logger::log( 'Updating lists of a contact. List selection: ' . implode( ', ', $lists ) . '. Provider is ' . $provider->service . '.' );
-
- /** Determine lists to add/remove from existing list config. */
- $lists_config = self::get_lists_config();
- $lists_to_add = array_intersect( array_keys( $lists_config ), $lists );
- $lists_to_remove = array_diff( array_keys( $lists_config ), $lists );
-
- /** Clean up lists to add/remove from contact's existing data. */
- $current_lists = self::get_contact_lists( $email );
- $lists_to_add = array_diff( $lists_to_add, $current_lists );
- $lists_to_remove = array_intersect( $current_lists, $lists_to_remove );
-
- if ( empty( $lists_to_add ) && empty( $lists_to_remove ) ) {
- return false;
- }
-
- $result = $provider->update_contact_lists_handling_local( $email, $lists_to_add, $lists_to_remove );
-
- /**
- * Fires after a contact's lists are updated.
- *
- * @param string $provider The provider name.
- * @param string $email Contact email address.
- * @param string[] $lists_to_add Array of list IDs to subscribe the contact to.
- * @param string[] $lists_to_remove Array of list IDs to remove the contact from.
- * @param bool|WP_Error $result True if the contact was updated or error if failed.
- */
- do_action( 'newspack_newsletters_update_contact_lists', $provider->service, $email, $lists_to_add, $lists_to_remove, $result );
-
- return $result;
+ _deprecated_function( __METHOD__, '2.21', 'Newspack_Newsletters_Contacts::update_lists' );
+ return Newspack_Newsletters_Contacts::update_lists( $email, $lists );
}
/**
@@ -1225,7 +1001,7 @@ public static function process_subscription_update() {
} else {
$email = get_userdata( get_current_user_id() )->user_email;
$lists = isset( $_POST['lists'] ) ? array_map( 'sanitize_text_field', $_POST['lists'] ) : [];
- $result = self::update_contact_lists( $email, $lists );
+ $result = Newspack_Newsletters_Contacts::update_lists( $email, $lists );
if ( is_wp_error( $result ) ) {
wc_add_notice( $result->get_error_message(), 'error' );
} elseif ( false === $result ) {
diff --git a/includes/service-providers/active_campaign/class-newspack-newsletters-active-campaign.php b/includes/service-providers/active_campaign/class-newspack-newsletters-active-campaign.php
index 517c6057e..6c229702b 100644
--- a/includes/service-providers/active_campaign/class-newspack-newsletters-active-campaign.php
+++ b/includes/service-providers/active_campaign/class-newspack-newsletters-active-campaign.php
@@ -1157,9 +1157,9 @@ public function update_contact_lists( $email, $lists_to_add = [], $lists_to_remo
$existing_contact = $this->get_contact_data( $email );
if ( is_wp_error( $existing_contact ) ) {
/** Create contact */
- // Call Newspack_Newsletters_Subscription's method (not the provider's directly),
+ // Call Newspack_Newsletters_Contacts's method (not the provider's directly),
// so the appropriate hooks are called.
- $contact_data = Newspack_Newsletters_Subscription::add_contact( [ 'email' => $email ] );
+ $contact_data = Newspack_Newsletters_Contacts::upsert( [ 'email' => $email ] );
if ( is_wp_error( $contact_data ) ) {
return $contact_data;
}
diff --git a/includes/service-providers/constant_contact/class-newspack-newsletters-constant-contact.php b/includes/service-providers/constant_contact/class-newspack-newsletters-constant-contact.php
index 41c771317..66aba7cbc 100644
--- a/includes/service-providers/constant_contact/class-newspack-newsletters-constant-contact.php
+++ b/includes/service-providers/constant_contact/class-newspack-newsletters-constant-contact.php
@@ -940,9 +940,9 @@ public function update_contact_lists( $email, $lists_to_add = [], $lists_to_remo
$contact_data = $this->get_contact_data( $email );
if ( is_wp_error( $contact_data ) ) {
/** Create contact */
- // Call Newspack_Newsletters_Subscription's method (not the provider's directly),
+ // Call Newspack_Newsletters_Contacts's method (not the provider's directly),
// so the appropriate hooks are called.
- $contact_data = Newspack_Newsletters_Subscription::add_contact( [ 'email' => $email ] );
+ $contact_data = Newspack_Newsletters_Contacts::upsert( [ 'email' => $email ] );
if ( is_wp_error( $contact_data ) ) {
return $contact_data;
}
diff --git a/includes/service-providers/mailchimp/class-newspack-newsletters-mailchimp.php b/includes/service-providers/mailchimp/class-newspack-newsletters-mailchimp.php
index e76ffd9f4..5eb1f099c 100644
--- a/includes/service-providers/mailchimp/class-newspack-newsletters-mailchimp.php
+++ b/includes/service-providers/mailchimp/class-newspack-newsletters-mailchimp.php
@@ -1214,6 +1214,129 @@ public function add_contact_with_groups_and_tags( $contact, $lists ) {
return $results;
}
+ /**
+ * Get merge field type.
+ *
+ * @param mixed $value Value to check.
+ *
+ * @return string Merge field type.
+ */
+ private function get_merge_field_type( $value ) {
+ if ( is_numeric( $value ) ) {
+ return 'number';
+ }
+ if ( is_bool( $value ) ) {
+ return 'boolean';
+ }
+ return 'text';
+ }
+
+ /**
+ * Given a contact metadata array, build the `merge_fields` array to be sent to Mailchimp
+ * by sarching for existing merge fields and creating new ones as needed.
+ *
+ * @param string $audience_id Audience ID.
+ * @param array $data The contact metadata.
+ *
+ * @return array Merge fields.
+ */
+ private function prepare_merge_fields( $audience_id, $data ) {
+ $merge_fields = [];
+
+ // Strip arrays.
+ $data = array_filter(
+ $data,
+ function( $value ) {
+ return ! is_array( $value );
+ }
+ );
+
+ // Get and match existing merge fields.
+ try {
+ $existing_fields = Newspack_Newsletters_Mailchimp_Cached_Data::get_merge_fields( $audience_id );
+ } catch ( \Exception $e ) {
+ Newspack_Newsletters_Logger::log(
+ sprintf(
+ // Translators: %1$s is the error message.
+ __( 'Error getting merge fields: %1$s', 'newspack-newsletters' ),
+ $existing_fields->get_error_message()
+ )
+ );
+ return [];
+ }
+ if ( empty( $existing_fields ) ) {
+ $existing_fields = [];
+ }
+
+ usort(
+ $existing_fields,
+ function( $a, $b ) {
+ return $a['merge_id'] - $b['merge_id'];
+ }
+ );
+
+ $list_merge_fields = [];
+
+ // Handle duplicate fields.
+ foreach ( $existing_fields as $field ) {
+ if ( ! isset( $list_merge_fields[ $field['name'] ] ) ) {
+ $list_merge_fields[ $field['name'] ] = $field['tag'];
+ } else {
+ Newspack_Newsletters_Logger::log(
+ sprintf(
+ // Translators: %1$s is the merge field name, %2$s is the field's unique tag.
+ __( 'Warning: Duplicate merge field %1$s found with tag %2$s.', 'newspack-newsletters' ),
+ $field['name'],
+ $field['tag']
+ )
+ );
+ }
+ }
+
+ foreach ( $data as $field_name => $field_value ) {
+ // If field already exists, add it to the payload.
+ if ( isset( $list_merge_fields[ $field_name ] ) ) {
+ $merge_fields[ $list_merge_fields[ $field_name ] ] = $data[ $field_name ];
+ unset( $data[ $field_name ] );
+ }
+ }
+
+ // Create remaining fields.
+ $remaining_fields = array_keys( $data );
+ $mc = new Mailchimp( $this->api_key() );
+ foreach ( $remaining_fields as $field_name ) {
+ $created_field = $mc->post(
+ "lists/$audience_id/merge-fields",
+ [
+ 'name' => $field_name,
+ 'type' => $this->get_merge_field_type( $data[ $field_name ] ),
+ ]
+ );
+ // Skip field if it failed to create.
+ if ( empty( $created_field['merge_id'] ) ) {
+ Newspack_Newsletters_Logger::log(
+ sprintf(
+ // Translators: %1$s is the merge field key, %2$s is the error message.
+ __( 'Failed to create merge field %1$s. Error response: %2$s', 'newspack-newsletters' ),
+ $field_name,
+ $created_field['detail'] ?? 'Unknown error'
+ )
+ );
+ continue;
+ }
+ Newspack_Newsletters_Logger::log(
+ sprintf(
+ // Translators: %1$s is the merge field key, %2$s is the error message.
+ __( 'Created merge field %1$s.', 'newspack-newsletters' ),
+ $field_name
+ )
+ );
+ $merge_fields[ $created_field['tag'] ] = $data[ $field_name ];
+ }
+
+ return $merge_fields;
+ }
+
/**
* Add contact to a list.
*
@@ -1252,69 +1375,9 @@ public function add_contact( $contact, $list_id = false, $sublists = [] ) {
$update_payload = [ 'email_address' => $email_address ];
if ( isset( $contact['metadata'] ) && is_array( $contact['metadata'] ) && ! empty( $contact['metadata'] ) ) {
- $update_payload['merge_fields'] = [];
-
- $merge_fields_res = $mc->get( "lists/$list_id/merge-fields", [ 'count' => 1000 ] );
- if ( ! isset( $merge_fields_res['merge_fields'] ) ) {
- return new \WP_Error(
- 'newspack_newsletters_mailchimp_add_contact_failed',
- sprintf(
- // Translators: %1$s is the error message.
- __( 'Error getting merge fields: %1$s', 'newspack-newsletters' ),
- $merge_fields_res['detail'] ?? __( 'Unable to fetch merge fields.' )
- )
- );
- }
- $existing_merge_fields = $merge_fields_res['merge_fields'];
- usort(
- $existing_merge_fields,
- function ( $a, $b ) {
- return $a['merge_id'] - $b['merge_id'];
- }
- );
-
- $list_merge_fields = [];
-
- // Handle duplicate fields.
- foreach ( $existing_merge_fields as $key => $field ) {
- if ( ! isset( $list_merge_fields[ $field['name'] ] ) ) {
- $list_merge_fields[ $field['name'] ] = $field['tag'];
- } else {
- Newspack_Newsletters_Logger::log(
- sprintf(
- // Translators: %1$s is the merge field name, %2$s is the unique tag.
- __( 'Warning: Duplicate merge field %1$s found with tag %2$s.', 'newspack-newsletters' ),
- $field['name'],
- $field['tag']
- )
- );
- }
- }
-
- foreach ( $contact['metadata'] as $key => $value ) {
- if ( isset( $list_merge_fields[ $key ] ) ) {
- $update_payload['merge_fields'][ $list_merge_fields[ $key ] ] = (string) $value;
- } else {
- $created_merge_field = $mc->post(
- "lists/$list_id/merge-fields",
- [
- 'name' => $key,
- 'type' => 'text',
- ]
- );
- if ( isset( $created_merge_field['status'] ) && '4' === substr( $created_merge_field['status'], 0, 1 ) ) {
- Newspack_Newsletters_Logger::log(
- sprintf(
- // Translators: %1$s is the merge field key, %2$s is the error message.
- __( 'Failed to create merge field %1$s. Reason: %2$s', 'newspack-newsletters' ),
- $key,
- $created_merge_field['detail'] ?? $created_merge_field['title']
- )
- );
- continue;
- }
- $update_payload['merge_fields'][ $created_merge_field['tag'] ] = (string) $value;
- }
+ $merge_fields = $this->prepare_merge_fields( $list_id, $contact['metadata'] );
+ if ( ! empty( $merge_fields ) ) {
+ $update_payload['merge_fields'] = $merge_fields;
}
}
@@ -1474,7 +1537,7 @@ public function update_contact_lists( $email, $lists_to_add = [], $lists_to_remo
$contact = $this->get_contact_data( $email );
if ( is_wp_error( $contact ) ) {
/** Create contact */
- $result = Newspack_Newsletters_Subscription::add_contact( [ 'email' => $email ], $lists_to_add );
+ $result = Newspack_Newsletters_Contacts::upsert( [ 'email' => $email ], $lists_to_add );
if ( is_wp_error( $result ) ) {
return $result;
}
diff --git a/phpcs.xml b/phpcs.xml
index 6a6550f75..d3c35d007 100644
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -6,6 +6,9 @@
+
+
+
diff --git a/phpcsSniffs/Sniffs/NewspackNewslettersContactsMethodsSniff.php b/phpcsSniffs/Sniffs/NewspackNewslettersContactsMethodsSniff.php
new file mode 100644
index 000000000..1c78b0705
--- /dev/null
+++ b/phpcsSniffs/Sniffs/NewspackNewslettersContactsMethodsSniff.php
@@ -0,0 +1,122 @@
+methods and check if they are called from the allowed classes.
+ * They are also allowed to be called from within the service-providers directory.
+ *
+ * @param PHP_CodeSniffer_File $phpcs_file The file where the token was found.
+ * @param int $stack_ptr The position in the stack where the token was found.
+ */
+ public function process( PHP_CodeSniffer_File $phpcs_file, $stack_ptr ) {
+
+ $path_parts = explode( DIRECTORY_SEPARATOR, $phpcs_file->path );
+
+ $possible_provider_dirs = [
+ $path_parts[ count( $path_parts ) - 2 ],
+ $path_parts[ count( $path_parts ) - 3 ],
+ ];
+
+ if ( in_array( 'service-providers', $possible_provider_dirs, true ) ) {
+ return;
+ }
+
+ $tokens = $phpcs_file->getTokens();
+ $token = $tokens[ $stack_ptr ];
+
+ if ( $token['code'] === T_CLASS ) {
+ $this->current_class = $tokens[ $stack_ptr + 2 ]['content'];
+ return;
+ }
+
+ if ( in_array( $token['content'], $this->methods, true ) ) {
+ $operator = $tokens[ $stack_ptr - 1 ];
+ if ( $operator['type'] === 'T_DOUBLE_COLON' || $operator['type'] === 'T_OBJECT_OPERATOR' ) {
+
+ if ( ! in_array( $this->current_class, $this->allowed_classes, true ) ) {
+
+ $method_name = $tokens[ $stack_ptr - 2 ]['content'] . $tokens[ $stack_ptr - 1 ]['content'] . $token['content'] . '()';
+
+ $phpcs_file->addError(
+ sprintf( self::ERROR_MESSAGE, $method_name ),
+ $stack_ptr,
+ self::ERROR_CODE
+ );
+ }
+ }
+ }
+ }
+}
diff --git a/phpcsSniffs/ruleset.xml b/phpcsSniffs/ruleset.xml
new file mode 100644
index 000000000..9e78e9282
--- /dev/null
+++ b/phpcsSniffs/ruleset.xml
@@ -0,0 +1,4 @@
+
+
+ Add Newspack Newsletters specific rules.
+
diff --git a/src/blocks/subscribe/index.php b/src/blocks/subscribe/index.php
index dbed4a359..298f6b88c 100644
--- a/src/blocks/subscribe/index.php
+++ b/src/blocks/subscribe/index.php
@@ -498,7 +498,7 @@ function process_form() {
\Newspack\Reader_Activation::register_reader( $email, $name, true, $metadata );
}
- $result = \Newspack_Newsletters_Subscription::add_contact(
+ $result = \Newspack_Newsletters_Contacts::upsert(
[
'name' => $name ?? null,
'email' => $email,
diff --git a/tests/test-esp-add-contact.php b/tests/test-esp-add-contact.php
index 89f97348b..601bb441d 100644
--- a/tests/test-esp-add-contact.php
+++ b/tests/test-esp-add-contact.php
@@ -22,7 +22,7 @@ public static function set_up_before_class() {
* Add a contact to a single list.
*/
public function test_add_contact_to_single_list() {
- $result = Newspack_Newsletters_Subscription::add_contact(
+ $result = Newspack_Newsletters_Contacts::upsert(
[
'email' => 'test@example.com',
],
@@ -43,7 +43,7 @@ public function test_add_contact_to_single_list() {
* Add a contact to multiple lists.
*/
public function test_add_contact_to_multiple_lists() {
- $result = Newspack_Newsletters_Subscription::add_contact(
+ $result = Newspack_Newsletters_Contacts::upsert(
[
'email' => 'test@example.com',
],
@@ -68,7 +68,7 @@ public function test_add_contact_to_multiple_lists() {
* Add a contact to lists and sublists.
*/
public function test_add_contact_to_lists_and_sublists() {
- $result = Newspack_Newsletters_Subscription::add_contact(
+ $result = Newspack_Newsletters_Contacts::upsert(
[
'email' => 'test@example.com',
],
@@ -96,7 +96,7 @@ public function test_add_contact_to_lists_and_sublists() {
* Add a contact to multiple lists and sublists.
*/
public function test_add_contact_to_multiple_lists_and_sublists() {
- $result = Newspack_Newsletters_Subscription::add_contact(
+ $result = Newspack_Newsletters_Contacts::upsert(
[
'email' => 'test@example.com',
],