-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: consolidate data flows (#1602)
* feat: refactor add_contact * feat: add methods that were in mailchimp data connector * test: fix tests * fix: fix delete_user_subscription method * feat: add changes from #1567 * feat: deprecate update_contact_lists In the last PR this method was forgotten * feat: add logging POC * feat: update calls to the new method * feat: move RAS specific code to main plugin * feat: remove redundant code * Revert "feat: remove redundant code" This reverts commit 83e9ed1. * feat: add logging context to calls * feat: remove result from logs - too verbose * fix: method signature * fix: passed arguments * feat: update call on newspack_registered_reader * feat: add mailchimp notes logging * test: fix tests * Update includes/service-providers/mailchimp/class-newspack-newsletters-mailchimp-notes.php Co-authored-by: Claudiu Lodromanean <[email protected]> * feat: improve message in mailchimp notes * Update includes/class-newspack-newsletters-contacts.php Co-authored-by: Derrick Koo <[email protected]> * feat: introduce "subscribe" method (#1593) * fix: "My Account" newsletters subscription (#1601) * fix: only send status_if_new when informed * fix: improve code readability * fix: hook on mailchimp notes * feat: update status stragegy * test: fix mock class --------- Co-authored-by: dkoo <[email protected]> Co-authored-by: Claudiu Lodromanean <[email protected]> Co-authored-by: Derrick Koo <[email protected]> Co-authored-by: Miguel Peixe <[email protected]> Co-authored-by: Miguel Peixe <[email protected]>
- Loading branch information
1 parent
ab30422
commit dd56ab8
Showing
14 changed files
with
989 additions
and
500 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,374 @@ | ||
<?php | ||
/** | ||
* Newspack Newsletters Contacts class. | ||
* | ||
* This class holds the methods for managing contacts. It's meant to be used by this plugin and by external integrations. | ||
* | ||
* @package Newspack | ||
*/ | ||
|
||
defined( 'ABSPATH' ) || exit; | ||
|
||
/** | ||
* Class Newspack_Newsletters_Contacts | ||
*/ | ||
class Newspack_Newsletters_Contacts { | ||
|
||
/** | ||
* Subscribe a contact to lists. | ||
* | ||
* This method uses an upsert strategy, which means the contact will be | ||
* created if it doesn't exist, or updated if it does. | ||
* | ||
* A contact can be added asynchronously, which means the request will return | ||
* immediately and the contact will be added in the background. In this case | ||
* the response will be `true` and the caller must handle it optimistically. | ||
* NEWSPACK_NEWSLETTERS_ASYNC_SUBSCRIPTION_ENABLED must be defined as true for | ||
* this feature to be available. | ||
* | ||
* @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. If empty or false, contact will be created but not subscribed to any lists. | ||
* @param bool $async Whether to add the contact asynchronously. Default is false. | ||
* @param string $context Context of the update for logging purposes. | ||
* | ||
* @return array|WP_Error|true Contact data if it was added, or error otherwise. True if async. | ||
*/ | ||
public static function subscribe( $contact, $lists = false, $async = false, $context = 'Subscribe contact' ) { | ||
$provider = Newspack_Newsletters::get_service_provider(); | ||
|
||
if ( defined( 'NEWSPACK_NEWSLETTERS_ASYNC_SUBSCRIPTION_ENABLED' ) && NEWSPACK_NEWSLETTERS_ASYNC_SUBSCRIPTION_ENABLED && true === $async ) { | ||
Newspack_Newsletters_Subscription::add_subscription_intent( $contact, $lists, $context ); | ||
return true; | ||
} | ||
|
||
$existing_contact = Newspack_Newsletters_Subscription::get_contact_data( $contact['email'], true ); | ||
$is_updating = \is_wp_error( $existing_contact ) ? false : true; | ||
|
||
$result = self::upsert( $contact, $lists, $context, $existing_contact ); | ||
|
||
if ( is_wp_error( $result ) ) { | ||
return $result; | ||
} | ||
|
||
/** | ||
* Fires after a contact subscribes. | ||
* | ||
* @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|null $is_updating Whether the contact is being updated. If false, the contact is being created. | ||
* @param string $context Context of the update for logging purposes. | ||
*/ | ||
do_action( 'newspack_newsletters_contact_subscribed', $provider->service, $contact, $lists, $result, $is_updating, $context ); | ||
|
||
return $result; | ||
} | ||
|
||
/** | ||
* Upserts a contact to lists. | ||
* | ||
* @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. If empty or false, contact will be created but not subscribed to any lists. | ||
* @param string $context Context of the update for logging purposes. | ||
* @param array|null $existing_contact Optional existing contact data. | ||
* | ||
* @return array|WP_Error|true Contact data if it was added, or error otherwise. True if async. | ||
*/ | ||
public static function upsert( $contact, $lists = false, $context = 'Unknown', $existing_contact = null ) { | ||
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 . '.' ); | ||
} | ||
|
||
if ( null !== $existing_contact ) { | ||
$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 ); | ||
|
||
$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 upserted. | ||
* | ||
* @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. | ||
* @param string $context Context of the update for logging purposes. | ||
*/ | ||
do_action( 'newspack_newsletters_upsert', $provider->service, $contact, $lists, $result, $is_updating, $context ); | ||
|
||
do_action( | ||
'newspack_log', | ||
'newspack_esp_sync_upsert_contact', | ||
$context, | ||
[ | ||
'type' => $errors->has_errors() ? 'error' : 'debug', | ||
'data' => [ | ||
'provider' => $provider->service, | ||
'lists' => $lists, | ||
'contact' => $contact, | ||
'errors' => $errors->get_error_messages(), | ||
], | ||
'user_email' => $contact['email'], | ||
'file' => 'newspack_esp_sync', | ||
] | ||
); | ||
|
||
if ( $errors->has_errors() ) { | ||
return $errors; | ||
} | ||
|
||
return $result; | ||
} | ||
|
||
/** | ||
* Permanently delete a user subscription. | ||
* | ||
* @param int $user_id User ID. | ||
* @param string $context Context of the update for logging purposes. | ||
* | ||
* @return bool|WP_Error Whether the contact was deleted or error. | ||
*/ | ||
public static function delete( $user_id, $context = 'Unknown' ) { | ||
$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.' ) ); | ||
} | ||
$result = $provider->delete_contact( $user->user_email ); | ||
|
||
do_action( | ||
'newspack_log', | ||
'newspack_esp_sync_delete_contact', | ||
$context, | ||
[ | ||
'type' => is_wp_error( $result ) ? 'error' : 'debug', | ||
'data' => [ | ||
'provider' => $provider->service, | ||
'errors' => is_wp_error( $result ) ? $result->get_error_message() : [], | ||
], | ||
'user_email' => $user->user_email, | ||
'file' => 'newspack_esp_sync', | ||
] | ||
); | ||
|
||
return $result; | ||
} | ||
|
||
/** | ||
* 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. | ||
* @param string $context Context of the update for logging purposes. | ||
* | ||
* @return bool|WP_Error Whether the contact was updated or error. | ||
*/ | ||
public static function update_lists( $email, $lists = [], $context = 'Unknown' ) { | ||
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; | ||
} | ||
|
||
return self::add_and_remove_lists( $email, $lists_to_add, $lists_to_remove, $context ); | ||
} | ||
|
||
/** | ||
* Add and remove a contact from lists. | ||
* | ||
* @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 string $context Context of the update for logging purposes. | ||
* | ||
* @return bool|WP_Error Whether the contact was updated or error. | ||
*/ | ||
public static function add_and_remove_lists( $email, $lists_to_add = [], $lists_to_remove = [], $context = 'Unknown' ) { | ||
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(); | ||
|
||
$result = $provider->update_contact_lists_handling_local( $email, $lists_to_add, $lists_to_remove, $context ); | ||
|
||
/** | ||
* 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. | ||
* @param string $context Context of the update for logging purposes. | ||
*/ | ||
do_action( 'newspack_newsletters_update_contact_lists', $provider->service, $email, $lists_to_add, $lists_to_remove, $result, $context ); | ||
|
||
do_action( | ||
'newspack_log', | ||
'newspack_esp_sync_update_lists', | ||
$context, | ||
[ | ||
'type' => is_wp_error( $result ) ? 'error' : 'debug', | ||
'data' => [ | ||
'provider' => $provider->service, | ||
'lists_to_add' => $lists_to_add, | ||
'lists_to_remove' => $lists_to_remove, | ||
'errors' => is_wp_error( $result ) ? $result->get_error_messages() : [], | ||
], | ||
'user_email' => $email, | ||
'file' => 'newspack_esp_sync', | ||
] | ||
); | ||
|
||
return $result; | ||
} | ||
} |
Oops, something went wrong.