diff --git a/includes/class-newspack.php b/includes/class-newspack.php index a98fcee997..9f494fe7fc 100644 --- a/includes/class-newspack.php +++ b/includes/class-newspack.php @@ -82,6 +82,7 @@ private function includes() { include_once NEWSPACK_ABSPATH . 'includes/data-events/class-webhooks.php'; include_once NEWSPACK_ABSPATH . 'includes/data-events/class-api.php'; include_once NEWSPACK_ABSPATH . 'includes/data-events/listeners.php'; + include_once NEWSPACK_ABSPATH . 'includes/data-events/connectors/class-mailchimp.php'; include_once NEWSPACK_ABSPATH . 'includes/class-api.php'; include_once NEWSPACK_ABSPATH . 'includes/class-profile.php'; include_once NEWSPACK_ABSPATH . 'includes/analytics/class-analytics.php'; diff --git a/includes/data-events/connectors/class-mailchimp.php b/includes/data-events/connectors/class-mailchimp.php new file mode 100644 index 0000000000..7b54d60547 --- /dev/null +++ b/includes/data-events/connectors/class-mailchimp.php @@ -0,0 +1,234 @@ + $field_name, + 'type' => self::get_merge_field_type( $data[ $field_name ] ), + ] + ); + $merge_fields[ $created_field['tag'] ] = $data[ $field_name ]; + $fields_ids[ $created_field['merge_id'] ] = $field_name; + } + + // Store fields IDs for future use. + \update_option( $fields_option_name, $fields_ids ); + return $merge_fields; + } + + /** + * Update a Mailchimp contact + * + * @param string $email Email address. + * @param array $data Data to update. + */ + private static function put( $email, $data = [] ) { + $audience_id = self::get_audience_id(); + if ( ! $audience_id ) { + return; + } + $hash = md5( strtolower( $email ) ); + $payload = [ + 'email_address' => $email, + 'status_if_new' => 'transactional', + ]; + + $merge_fields = self::get_merge_fields( $audience_id, $data ); + if ( ! empty( $merge_fields ) ) { + $payload['merge_fields'] = $merge_fields; + } + + // Upsert the contact. + Mailchimp_API::put( + "lists/$audience_id/members/$hash", + $payload + ); + } + + /** + * Handle a reader registering. + * + * @param int $timestamp Timestamp of the event. + * @param array $data Data associated with the event. + * @param int $client_id ID of the client that triggered the event. + */ + public static function reader_registered( $timestamp, $data, $client_id ) { + $metadata = [ + 'NP_Account' => $data['user_id'], + ]; + if ( isset( $data['metadata']['current_page_url'] ) ) { + $metadata['NP_Registration Page'] = $data['metadata']['current_page_url']; + } + if ( isset( $data['metadata']['registration_method'] ) ) { + $metadata['NP_Registration Method'] = $data['metadata']['registration_method']; + } + self::put( $data['email'], $metadata ); + } + + /** + * Handle a donation being made. + * + * @param int $timestamp Timestamp of the event. + * @param array $data Data associated with the event. + * @param int $client_id ID of the client that triggered the event. + */ + public static function donation_new( $timestamp, $data, $client_id ) { + if ( ! isset( $data['platform_data']['order_id'] ) ) { + return; + } + + $order_id = $data['platform_data']['order_id']; + $contact = WooCommerce_Connection::get_contact_from_order( $order_id ); + + if ( ! $contact ) { + return; + } + + $email = $contact['email']; + $metadata = $contact['metadata']; + $keys = Newspack_Newsletters::$metadata_keys; + + // Only use metadata defined in 'Newspack_Newsletters'. + $metadata = array_intersect_key( $metadata, array_flip( $keys ) ); + + // Remove "product name" from metadata, we'll use + // 'donation_subscription_new' action for this data. + unset( $metadata[ $keys['product_name'] ] ); + + self::put( $email, $metadata ); + } + + /** + * Handle a new subscription. + * + * @param int $timestamp Timestamp of the event. + * @param array $data Data associated with the event. + * @param int $client_id ID of the client that triggered the event. + */ + public static function donation_subscription_new( $timestamp, $data, $client_id ) { + if ( empty( $data['platform_data']['order_id'] ) ) { + return; + } + $metadata = [ + 'NP_Account' => $data['user_id'], + ]; + $order_id = $data['platform_data']['order_id']; + $product_id = Donations::get_order_donation_product_id( $order_id ); + $product_name = get_the_title( $product_id ); + + $metadata['NP_Product Name'] = $product_name; + + self::put( $data['email'], $metadata ); + } +} +new Mailchimp(); diff --git a/includes/oauth/class-mailchimp-api.php b/includes/oauth/class-mailchimp-api.php index e84a8c72bd..f39dea6112 100644 --- a/includes/oauth/class-mailchimp-api.php +++ b/includes/oauth/class-mailchimp-api.php @@ -80,18 +80,7 @@ public static function register_api_endpoints() { * @return WP_REST_Response */ public static function api_mailchimp_auth_status() { - // 'newspack_mailchimp_api_key' is a new option introduced to manage MC API key accross Newspack plugins. - // Keeping the old option for backwards compatibility. - $mailchimp_api_key = get_option( 'newspack_mailchimp_api_key', get_option( 'newspack_newsletters_mailchimp_api_key' ) ); - $endpoint = self::get_api_endpoint_from_key( $mailchimp_api_key ); - - if ( ! $mailchimp_api_key || ! $endpoint ) { - return \rest_ensure_response( [] ); - } - - $key_is_valid_response = self::is_valid_api_key( $endpoint, $mailchimp_api_key ); - - return $key_is_valid_response; + return self::is_valid_api_key(); } /** @@ -106,7 +95,7 @@ public static function api_mailchimp_save_key( $request ) { return new \WP_Error( 'wrong_parameter', __( 'Invalid Mailchimp API Key.', 'newspack' ) ); } - $key_is_valid_response = self::is_valid_api_key( $endpoint, $request['api_key'] ); + $key_is_valid_response = self::is_valid_api_key( $request['api_key'] ); if ( ! is_wp_error( $key_is_valid_response ) ) { update_option( 'newspack_mailchimp_api_key', $request['api_key'] ); @@ -143,37 +132,98 @@ private static function get_api_endpoint_from_key( $api_key ) { /** * Check API Key validity * - * @param string $endpoint Mailchimp API Endpoint. * @param string $api_key Mailchimp API Key. * @return WP_REST_Response|WP_Error */ - private static function is_valid_api_key( $endpoint, $api_key ) { + private static function is_valid_api_key( $api_key = '' ) { // Mailchimp API root endpoint returns details about the Mailchimp user account. - $response = wp_safe_remote_get( - $endpoint, - [ - 'headers' => [ - 'Accept' => 'application/json', - 'Content-Type' => 'application/json', - 'Authorization' => "Basic $api_key", - ], - ] - ); - + $response = self::request( 'GET', '', [], $api_key ); if ( is_wp_error( $response ) ) { return $response; } - if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { - $parsed_response = json_decode( $response['body'], true ); + return \rest_ensure_response( [ 'username' => $response['username'] ] ); + } + + /** + * Perform Mailchimp API Request + * + * @param string $method HTTP method. + * @param string $path API path. + * @param array $data Data to send. + * @param string $api_key Optional API key to override the default one. + * + * @return array|WP_Error response body or error. + */ + public static function request( $method, $path = '', $data = [], $api_key = '' ) { + if ( empty( $api_key ) ) { + // 'newspack_mailchimp_api_key' is a new option introduced to manage MC API key accross Newspack plugins. + // Keeping the old option for backwards compatibility. + $api_key = \get_option( 'newspack_mailchimp_api_key', get_option( 'newspack_newsletters_mailchimp_api_key' ) ); + } + $endpoint = self::get_api_endpoint_from_key( $api_key ); + if ( ! $endpoint ) { + return new \WP_Error( 'wrong_parameter', __( 'Invalid Mailchimp API Key.', 'newspack' ) ); + } + $url = $endpoint . '/' . $path; + $config = [ + 'method' => $method, + 'headers' => [ + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + 'Authorization' => "Basic $api_key", + ], + ]; + if ( ! empty( $data ) ) { + $config['body'] = \wp_json_encode( $data ); + } + $response = \wp_safe_remote_request( $url, $config ); + if ( \is_wp_error( $response ) ) { + return $response; + } + $parsed_response = json_decode( $response['body'], true ); + if ( 200 !== \wp_remote_retrieve_response_code( $response ) ) { return new \WP_Error( 'newspack_mailchimp_api', array_key_exists( 'title', $parsed_response ) ? $parsed_response['title'] : __( 'Request failed.', 'newspack' ) ); } + return $parsed_response; + } + + /** + * Perform a GET request to Mailchimp's API + * + * @param string $path API path. + * + * @return array|WP_Error API response or error. + */ + public static function get( $path = '' ) { + return self::request( 'GET', $path ); + } - $response_body = json_decode( $response['body'], true ); - return \rest_ensure_response( [ 'username' => $response_body['username'] ] ); + /** + * Perform a PUT request to Mailchimp's API + * + * @param string $path API path. + * @param array $data Data to send. + * + * @return array|WP_Error API response or error. + */ + public static function put( $path = '', $data = [] ) { + return self::request( 'PUT', $path, $data ); + } + + /** + * Perform a POST request to Mailchimp's API + * + * @param string $path API path. + * @param array $data Data to send. + * + * @return array|WP_Error API response or error. + */ + public static function post( $path = '', $data = [] ) { + return self::request( 'POST', $path, $data ); } } diff --git a/includes/reader-activation/class-reader-activation.php b/includes/reader-activation/class-reader-activation.php index efe78f0a11..d7506521fd 100644 --- a/includes/reader-activation/class-reader-activation.php +++ b/includes/reader-activation/class-reader-activation.php @@ -174,6 +174,7 @@ private static function get_settings_config() { 'sync_esp' => true, 'sync_esp_delete' => true, 'active_campaign_master_list' => '', + 'mailchimp_audience_id' => '', 'emails' => Emails::get_emails( array_values( Reader_Activation_Emails::EMAIL_TYPES ), false ), 'sender_name' => Emails::get_from_name(), 'sender_email_address' => Emails::get_from_email(), diff --git a/includes/reader-revenue/class-woocommerce-connection.php b/includes/reader-revenue/class-woocommerce-connection.php index 8138730088..a641c6a88e 100644 --- a/includes/reader-revenue/class-woocommerce-connection.php +++ b/includes/reader-revenue/class-woocommerce-connection.php @@ -163,24 +163,20 @@ public static function sync_reader_on_customer_login( $user_login, $user ) { } /** - * Sync a customer to the ESP from an order. + * Get the contact data from a WooCommerce order. * - * @param WC_Order $order Order object. + * @param \WC_Order|int $order WooCommerce order or order ID. + * + * @return array|false Contact data or false. */ - public static function sync_reader_from_order( $order ) { - if ( ! self::can_sync_customers() ) { - return; + public static function get_contact_from_order( $order ) { + if ( is_integer( $order ) ) { + $order = new \WC_Order( $order ); } - - if ( self::CREATED_VIA_NAME === $order->get_created_via() ) { - // Only sync orders not created via the Stripe integration. - return; - } - $metadata_keys = Newspack_Newsletters::$metadata_keys; $user_id = $order->get_customer_id(); if ( ! $user_id ) { - return; + return false; } $customer = new \WC_Customer( $user_id ); @@ -261,11 +257,33 @@ public static function sync_reader_from_order( $order ) { $first_name = $order->get_billing_first_name(); $last_name = $order->get_billing_last_name(); - $contact = [ + return [ 'email' => $order->get_billing_email(), 'name' => "$first_name $last_name", 'metadata' => $metadata, ]; + } + + /** + * Sync a customer to the ESP from an order. + * + * @param WC_Order $order Order object. + */ + public static function sync_reader_from_order( $order ) { + if ( ! self::can_sync_customers() ) { + return; + } + + if ( self::CREATED_VIA_NAME === $order->get_created_via() ) { + // Only sync orders not created via the Stripe integration. + return; + } + + $contact = self::get_contact_from_order( $order ); + if ( ! $contact ) { + return; + } + \Newspack_Newsletters_Subscription::add_contact( $contact ); }