diff --git a/README.md b/README.md index 25b1787..f9fee6e 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ The plugin provides an SDK client to easily integrate with Omnisend API. > To use in your plugin you must check if wp-omnisend plugin is installed. > Provided client will send data to Omnisend if it is connected. -You can find function references in the [client folder](https://github.com/omnisend/wp-omnisend/tree/main/omnisend/includes/Public/V1). +You can find function references in the [client folder](https://github.com/omnisend/wp-omnisend/tree/main/omnisend/includes/SDK/V1). ### Examples @@ -46,33 +46,54 @@ This is done by getting an actual client Here is how you can create a basic client & submit contact. ```php - $contact = new Contact(); - - $contact->set_email( $email ); - if ( $phone_number != '' ) { - $contact->set_phone( $phone_number ); - } - $contact->set_first_name( $first_name ); - $contact->set_last_name( $last_name ); - $contact->set_birthday( $birthday ); - $contact->set_postal_code( $postal_code ); - $contact->set_address( $address ); - $contact->set_state( $state ); - $contact->set_country( $country ); - $contact->set_city( $city ); - if ( $email_consent ) { - $contact->set_email_consent( 'actual_email_consent_for_gdrp' ); - $contact->set_email_opt_in( 'where user opted to become subscriber' ); - } - - $client = \Omnisend\SDK\V1\Omnisend::get_client( 'integration name', 'integration version' ); - - $response = $client->create_contact( $contact ); +$contact = new Contact(); + +$contact->set_email( $email ); +if ( $phone_number != '' ) { + $contact->set_phone( $phone_number ); +} +$contact->set_first_name( $first_name ); +$contact->set_last_name( $last_name ); +$contact->set_birthday( $birthday ); +$contact->set_postal_code( $postal_code ); +$contact->set_address( $address ); +$contact->set_state( $state ); +$contact->set_country( $country ); +$contact->set_city( $city ); +if ( $email_consent ) { + $contact->set_email_consent( 'actual_email_consent_for_gdrp' ); + $contact->set_email_opt_in( 'where user opted to become subscriber' ); +} +$client = \Omnisend\SDK\V1\Omnisend::get_client( 'integration name', 'integration version' ); + +$response = $client->create_contact( $contact ); ``` +#### Customer events + +Here is how you can send customer events. + +```php +$contact = new Contact(); +$contact->set_email( $email ); + +$event = new Event(); +$event->set_contact( $contact ); +$event->set_origin( 'api' ); +$event->set_event_name( 'something hapened' ); +$event->add_properties( 'pageUrl', $pageUrl ); +$event->add_properties( 'pageTitle', $pageTitle ); + +$client = \Omnisend\SDK\V1\Omnisend::get_client( 'integration name', 'integration version' ); + +$response = $client->send_customer_event($event); +``` + +You can send contact identifiers and if contact exists, then event will be attributed for this contact, if not - new contact will be created and event will be attributed to this new contact + #### Error handling -If data provided is invalid or creation fails, then +If data provided is invalid or contact creation fails, then ```php $response = $client->create_contact($contact) @@ -87,6 +108,21 @@ Will return `CreateContactResponse`. Depending on your integration logic you sho } ``` +If data provided is invalid or sending customer event fails, then + +```php +$response = $client->send_customer_event($event); +``` + +Will return `SendCustomerEventResponse`. Depending on your integration logic you should handle the error i.e + +```php + if ( $response->get_wp_error()->has_errors() ) { + error_log( 'Error in after_submission: ' . $response->get_wp_error()->get_error_message()); + return; + } +``` + ## PHP Linting WordPress.org team mandates our plugin to be linted diff --git a/omnisend/class-omnisend-core-bootstrap.php b/omnisend/class-omnisend-core-bootstrap.php index dc092b6..237b5c3 100644 --- a/omnisend/class-omnisend-core-bootstrap.php +++ b/omnisend/class-omnisend-core-bootstrap.php @@ -34,6 +34,7 @@ // Change for different environment. const OMNISEND_CORE_API_V3 = 'https://api.omnisend.com/v3'; +const OMNISEND_CORE_API_V5 = 'https://api.omnisend.com/v5'; const OMNISEND_CORE_SNIPPET_URL = 'https://omnisnippet1.com/inshop/launcher-v2.js'; // Omnisend for Woo plugin. diff --git a/omnisend/includes/Internal/V1/class-client.php b/omnisend/includes/Internal/V1/class-client.php index a3b9833..1937251 100644 --- a/omnisend/includes/Internal/V1/class-client.php +++ b/omnisend/includes/Internal/V1/class-client.php @@ -9,12 +9,17 @@ use Omnisend\SDK\V1\Contact; use Omnisend\SDK\V1\CreateContactResponse; +use Omnisend\SDK\V1\Event; +use Omnisend\SDK\V1\SendCustomerEventResponse; use WP_Error; defined( 'ABSPATH' ) || die( 'no direct access' ); class Client implements \Omnisend\SDK\V1\Client { + + + private string $api_key; private string $plugin_name; private string $plugin_version; @@ -47,7 +52,7 @@ public function create_contact( $contact ): CreateContactResponse { } $response = wp_remote_post( - OMNISEND_CORE_API_V3 . '/contacts', + OMNISEND_CORE_API_V5 . '/contacts', array( 'body' => wp_json_encode( $contact->to_array() ), 'headers' => array( @@ -89,6 +94,50 @@ public function create_contact( $contact ): CreateContactResponse { return new CreateContactResponse( (string) $arr['contactID'], $error ); } + public function send_customer_event( $event ): SendCustomerEventResponse { + $error = new WP_Error(); + + if ( $event instanceof Event ) { + $error->merge_from( $event->validate() ); + } else { + $error->add( 'event', 'Event is not instance of Omnisend\SDK\V1\Event.' ); + } + + $error->merge_from( $this->check_setup() ); + + if ( $error->has_errors() ) { + return new SendCustomerEventResponse( $error ); + } + + $response = wp_remote_post( + OMNISEND_CORE_API_V5 . '/events', + array( + 'body' => wp_json_encode( $event->to_array() ), + 'headers' => array( + 'Content-Type' => 'application/json', + 'X-API-Key' => $this->api_key, + 'X-INTEGRATION-NAME' => $this->plugin_name, + 'X-INTEGRATION-VERSION' => $this->plugin_version, + ), + 'timeout' => 10, + ) + ); + + if ( is_wp_error( $response ) ) { + error_log('wp_remote_post error: ' . $response->get_error_message()); // phpcs:ignore + return new SendCustomerEventResponse( $response ); + } + + $http_code = wp_remote_retrieve_response_code( $response ); + if ( $http_code >= 400 ) { + $body = wp_remote_retrieve_body( $response ); + $err_msg = "HTTP error: {$http_code} - " . wp_remote_retrieve_response_message( $response ) . " - {$body}"; + $error->add( 'omnisend_api', $err_msg ); + } + + return new SendCustomerEventResponse( $error ); + } + /** * @return WP_Error */ diff --git a/omnisend/includes/SDK/V1/class-client.php b/omnisend/includes/SDK/V1/class-client.php index 9a0cd88..a3c3e6a 100644 --- a/omnisend/includes/SDK/V1/class-client.php +++ b/omnisend/includes/SDK/V1/class-client.php @@ -7,8 +7,6 @@ namespace Omnisend\SDK\V1; -use WP_Error; - defined( 'ABSPATH' ) || die( 'no direct access' ); /** @@ -25,4 +23,13 @@ interface Client { * @return CreateContactResponse */ public function create_contact( $contact ): CreateContactResponse; + + /** + * Send customer event to Omnisend. Customer events are used to track customer behavior and trigger automations based on that behavior. + * + * @param Event $event + * + * @return SendCustomerEventResponse + */ + public function send_customer_event( $event ): SendCustomerEventResponse; } diff --git a/omnisend/includes/SDK/V1/class-contact.php b/omnisend/includes/SDK/V1/class-contact.php index 01c037d..488f859 100644 --- a/omnisend/includes/SDK/V1/class-contact.php +++ b/omnisend/includes/SDK/V1/class-contact.php @@ -17,6 +17,8 @@ * */ class Contact { + + private $id = null; private $first_name = null; private $last_name = null; private $email = null; @@ -81,8 +83,8 @@ public function validate(): WP_Error { $error->add( 'send_welcome_email', 'Not a valid boolean.' ); } - if ( $this->phone == null && $this->email == null ) { - $error->add( 'identifier', 'Phone or email must be set.' ); + if ( $this->phone == null && $this->email == null && $this->id == null ) { + $error->add( 'identifier', 'Phone, email or ID must be set.' ); } if ( $this->gender != null && ! in_array( $this->gender, array( 'm', 'f' ) ) ) { @@ -175,6 +177,10 @@ public function to_array(): array { $arr['identifiers'][] = $phone_identifier; } + if ( $this->id ) { + $arr['contactID'] = $this->id; + } + if ( $this->first_name ) { $arr['firstName'] = $this->first_name; } @@ -219,6 +225,128 @@ public function to_array(): array { } + /** + * Convert contact to array for events. + * + * If contact is valid it will be transformed to array that can be sent to Omnisend while creating event. + * + * @return array + */ + public function to_array_for_event(): array { + if ( $this->validate()->has_errors() ) { + return array(); + } + + $time_now = gmdate( 'c' ); + + $user_agent = sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ?? 'user agent not found' ) ); + $ip = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ?? 'ip not found' ) ); + + $arr = array( + 'consents' => array(), + 'optIns' => array(), + 'tags' => array_values( array_unique( $this->tags ) ), + ); + + if ( $this->email ) { + $arr['email'] = $this->email; + + if ( $this->email_consent ) { + $email_cahnnel_consent = array( + 'channel' => 'email', + 'source' => $this->email_consent, + 'createdAt' => $time_now, + 'ip' => $ip, + 'userAgent' => $user_agent, + ); + + $arr['consents'][] = $email_cahnnel_consent; + } + + if ( $this->email_opt_in_source ) { + $email_cahnnel_opt_in = array( + 'channel' => 'email', + 'createdAt' => $time_now, + 'source' => $this->email_opt_in_source, + ); + + $arr['optIns'][] = $email_cahnnel_opt_in; + } + } + + if ( $this->custom_properties ) { + $arr['customProperties'] = $this->custom_properties; + } + + if ( $this->phone ) { + $arr['phone'] = $this->phone; + + if ( $this->phone_consent ) { + $phone_channel_consent = array( + 'channel' => 'phone', + 'source' => $this->phone_consent, + 'createdAt' => $time_now, + 'ip' => $ip, + 'userAgent' => $user_agent, + ); + $arr['consents'][] = $phone_channel_consent; + } + + if ( $this->phone_opt_in_source ) { + $phone_cahnnel_opt_in = array( + 'channel' => 'phone', + 'createdAt' => $time_now, + 'source' => $this->phone_opt_in_source, + ); + + $arr['optIns'][] = $phone_cahnnel_opt_in; + } + } + + if ( $this->id ) { + $arr['id'] = $this->id; + } + + if ( $this->first_name ) { + $arr['firstName'] = $this->first_name; + } + + if ( $this->last_name ) { + $arr['lastName'] = $this->last_name; + } + + if ( $this->address ) { + $arr['address'] = $this->address; + } + + if ( $this->city ) { + $arr['city'] = $this->city; + } + + if ( $this->state ) { + $arr['state'] = $this->state; + } + + if ( $this->country ) { + $arr['country'] = $this->country; + } + + if ( $this->postal_code ) { + $arr['postalCode'] = $this->postal_code; + } + + if ( $this->birthday ) { + $arr['birthdate'] = $this->birthday; + } + + if ( $this->gender ) { + $arr['gender'] = $this->gender; + } + + return $arr; + } + + /** * Sets contact email. * @@ -232,6 +360,19 @@ public function set_email( $email ): void { } } + /** + * Sets contact id. + * + * @param $id + * + * @return void + */ + public function set_id( $id ): void { + if ( $id && is_string( $id ) ) { + $this->id = $id; + } + } + /** * Sets contact gender. It can be "m" or "f". * @@ -400,15 +541,15 @@ public function set_email_consent( $consent_text ): void { $this->email_consent = $consent_text; } - /** - * Sets email concent status. It's needed for GDPR compliance. - * - * Common format is `form:form_name` or `popup:popup_name`. - * - * @param $consent_text - * - * @return void - */ + /** + * Sets email concent status. It's needed for GDPR compliance. + * + * Common format is `form:form_name` or `popup:popup_name`. + * + * @param $consent_text + * + * @return void + */ public function set_phone_consent( $consent_text ): void { $this->phone_consent = $consent_text; } diff --git a/omnisend/includes/SDK/V1/class-event.php b/omnisend/includes/SDK/V1/class-event.php new file mode 100644 index 0000000..ece56bf --- /dev/null +++ b/omnisend/includes/SDK/V1/class-event.php @@ -0,0 +1,162 @@ +<?php +/** + * Omnisend Client + * + * @package OmnisendClient + */ + +namespace Omnisend\SDK\V1; + +use Omnisend\SDK\V1\Contact; +use WP_Error; + +defined( 'ABSPATH' ) || die( 'no direct access' ); + +/** + * Omnisend Event class. It's should be used with Omnisend Client. + * + */ +class Event { + + private $contact = null; + private $event_name = null; + private $event_version = null; + private $origin = null; + private array $properties = array(); + + /** + * Validate event properties. + * + * It ensures that all required properties are set + * + * @return WP_Error + */ + public function validate(): WP_Error { + + $error = new WP_Error(); + if ( $this->contact instanceof Contact ) { + $error->merge_from( $this->contact->validate() ); + } + + if ( $this->event_name == null ) { + $error->add( 'event_name', 'Is required.' ); + } + + if ( $this->event_name != null && ! is_string( $this->event_name ) ) { + $error->add( 'event_name', 'Not a string.' ); + } + + if ( $this->event_version != null && ! is_string( $this->event_version ) ) { + $error->add( 'event_version', 'Not a string.' ); + } + + if ( $this->origin != null && ! is_string( $this->origin ) ) { + $error->add( 'origin', 'Not a string.' ); + } + + foreach ( $this->properties as $name ) { + if ( ! is_string( $name ) ) { + $error->add( $name, 'Not a string.' ); + } + } + + return $error; + } + + /** + * Sets event name. + * + * @param $event_name + * + * @return void + */ + public function set_event_name( $event_name ): void { + $this->event_name = $event_name; + } + + /** + * Sets event version. + * + * @param $event_version + * + * @return void + */ + public function set_event_version( $event_version ): void { + $this->event_version = $event_version; + } + + + /** + * Sets event origin. Default value is api + * + * @param $origin + * + * @return void + */ + public function set_origin( $origin ): void { + $this->origin = $origin; + } + + /** + * @param $key + * @param $value + * + * @return void + */ + public function add_properties( $key, $value ): void { + if ( $key == '' ) { + return; + } + + $this->properties[ $key ] = $value; + } + + /** + * Sets contact. + * + * @param $contact + * + * @return void + */ + public function set_contact( $contact ): void { + $this->contact = $contact; + } + + /** + * Convert event to array. + * + * If event is valid it will be transformed to array that can be sent to Omnisend. + * + * @return array + */ + public function to_array(): array { + if ( $this->validate()->has_errors() ) { + return array(); + } + + $arr = array(); + + if ( $this->contact ) { + $arr['contact'] = $this->contact->to_array_for_event(); + } + + if ( $this->event_name ) { + $arr['eventName'] = $this->event_name; + } + + $arr['origin'] = 'api'; + if ( $this->origin ) { + $arr['origin'] = $this->origin; + } + + if ( $this->event_version ) { + $arr['eventVersion'] = $this->event_version; + } + + if ( $this->properties ) { + $arr['properties'] = $this->properties; + } + + return $arr; + } +} diff --git a/omnisend/includes/SDK/V1/class-sendcustomereventresponse.php b/omnisend/includes/SDK/V1/class-sendcustomereventresponse.php new file mode 100644 index 0000000..d70ff68 --- /dev/null +++ b/omnisend/includes/SDK/V1/class-sendcustomereventresponse.php @@ -0,0 +1,28 @@ +<?php +/** + * Omnisend Client + * + * @package OmnisendClient + */ + +namespace Omnisend\SDK\V1; + +use WP_Error; + +defined( 'ABSPATH' ) || die( 'no direct access' ); + +class SendCustomerEventResponse { + + private WP_Error $wp_error; + + /** + * @param WP_Error $wp_error + */ + public function __construct( WP_Error $wp_error ) { + $this->wp_error = $wp_error; + } + + public function get_wp_error(): WP_Error { + return $this->wp_error; + } +}